From f6a1e123fc646f4d804cf3d5039b3d36f25cef3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 19 Aug 2024 11:14:38 +0800 Subject: [PATCH 0001/1272] Make linter happy --- experimental/libbox/dns.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index e1f8bcc3b6..a46d9b4241 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -69,7 +69,8 @@ func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS. context: ctx, } var responseMessage *mDNS.Msg - return responseMessage, task.Run(ctx, func() error { + var group task.Group + group.Append0(func(ctx context.Context) error { err = p.iif.Exchange(response, messageBytes) if err != nil { return err @@ -80,6 +81,11 @@ func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS. responseMessage = &response.message return nil }) + err = group.Run(ctx) + if err != nil { + return nil, err + } + return responseMessage, nil } func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { @@ -96,7 +102,8 @@ func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, s context: ctx, } var responseAddr []netip.Addr - return responseAddr, task.Run(ctx, func() error { + var group task.Group + group.Append0(func(ctx context.Context) error { err := p.iif.Lookup(response, network, domain) if err != nil { return err @@ -121,6 +128,11 @@ func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, s }*/ return nil }) + err := group.Run(ctx) + if err != nil { + return nil, err + } + return responseAddr, nil } type Func interface { From 064fb9b873170595ace70b57b61914f1ff4e15f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 20 Aug 2024 18:56:50 +0800 Subject: [PATCH 0002/1272] Fix direct dialer not resolving domain --- common/dialer/dialer.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index a1721b281f..56c5f2ad15 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -28,13 +28,12 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) } else { dialer = NewDetour(router, options.Detour) } - domainStrategy := dns.DomainStrategy(options.DomainStrategy) - if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" { + if options.Detour == "" { dialer = NewResolveDialer( router, dialer, options.Detour == "" && !options.TCPFastOpen, - domainStrategy, + dns.DomainStrategy(options.DomainStrategy), time.Duration(options.FallbackDelay)) } return dialer, nil From db5719e22fac3ec80d5aabf3647b76f89e48703a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 20 Aug 2024 23:17:02 +0800 Subject: [PATCH 0003/1272] Fix no error return when empty DNS cache retrieved --- route/router_dns.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/route/router_dns.go b/route/router_dns.go index a8c4f44148..e0055009b0 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -188,6 +188,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS ) responseAddrs, cached = r.dnsClient.LookupCache(ctx, domain, strategy) if cached { + if len(responseAddrs) == 0 { + return nil, dns.RCodeNameError + } return responseAddrs, nil } r.dnsLogger.DebugContext(ctx, "lookup domain ", domain) From 090494faf5adea84b4d81bbe65c29a70c82a2ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 21 Aug 2024 08:09:01 +0800 Subject: [PATCH 0004/1272] Do not close bug and enhancement issues --- .github/workflows/stale.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b6307da29f..1715a9434d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,4 +12,5 @@ jobs: with: stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days' days-before-stale: 60 - days-before-close: 5 \ No newline at end of file + days-before-close: 5 + exempt-issue-labels: 'bug,enhancement' From aae3fded3209b7ba8f23123ab016e11be61e3a30 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Tue, 20 Aug 2024 22:23:24 +0800 Subject: [PATCH 0005/1272] documentation: Two updates * Copyedit documentation Close #1378 * remove yum, go full on dnf fixes #2049 --- docs/installation/package-manager.md | 10 ++-------- docs/installation/package-manager.zh.md | 10 ++-------- docs/manual/proxy-protocol/hysteria2.md | 15 ++++++++------- docs/manual/proxy-protocol/shadowsocks.md | 21 ++++++++++++--------- docs/manual/proxy-protocol/trojan.md | 15 +++++++-------- 5 files changed, 31 insertions(+), 40 deletions(-) diff --git a/docs/installation/package-manager.md b/docs/installation/package-manager.md index bb5b6abb47..a56142af03 100644 --- a/docs/installation/package-manager.md +++ b/docs/installation/package-manager.md @@ -24,14 +24,7 @@ icon: material/package sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo sudo dnf install sing-box # or sing-box-beta ``` - -=== ":material-redhat: CentOS / YUM" - - ```bash - sudo yum install -y yum-utils - sudo yum-config-manager --add-repo https://sing-box.app/sing-box.repo - sudo yum install sing-box # or sing-box-beta - ``` + (This applies to any distribution that uses `dnf` as the package manager: Fedora, CentOS, even OpenSUSE with DNF installed.) ## :material-download-box: Manual Installation @@ -46,6 +39,7 @@ icon: material/package ```bash bash <(curl -fsSL https://sing-box.app/rpm-install.sh) ``` + (This applies to any distribution that uses `rpm` and `systemd`. Because of how `rpm` defines dependencies, if it installs, it probably works.) === ":simple-archlinux: Archlinux / PKG" diff --git a/docs/installation/package-manager.zh.md b/docs/installation/package-manager.zh.md index a786450a0d..f4e628eb97 100644 --- a/docs/installation/package-manager.zh.md +++ b/docs/installation/package-manager.zh.md @@ -24,14 +24,7 @@ icon: material/package sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo sudo dnf install sing-box # or sing-box-beta ``` - -=== ":material-redhat: CentOS / YUM" - - ```bash - sudo yum install -y yum-utils - sudo yum-config-manager --add-repo https://sing-box.app/sing-box.repo - sudo yum install sing-box # or sing-box-beta - ``` + (这适用于任何使用 `dnf` 作为包管理器的发行版:Fedora、CentOS,甚至安装了 DNF 的 OpenSUSE。) ## :material-download-box: 手动安装 @@ -46,6 +39,7 @@ icon: material/package ```bash bash <(curl -fsSL https://sing-box.app/rpm-install.sh) ``` + (这适用于任何使用 `rpm` 和 `systemd` 的发行版。由于 `rpm` 定义依赖关系的方式,如果安装成功,就多半能用。) === ":simple-archlinux: Archlinux / PKG" diff --git a/docs/manual/proxy-protocol/hysteria2.md b/docs/manual/proxy-protocol/hysteria2.md index 9cc07e6f12..fe605b4576 100644 --- a/docs/manual/proxy-protocol/hysteria2.md +++ b/docs/manual/proxy-protocol/hysteria2.md @@ -4,16 +4,17 @@ icon: material/lightning-bolt # Hysteria 2 -The most popular Chinese-made simple protocol based on QUIC, the selling point is Brutal, -a congestion control algorithm that can resist packet loss by manually specifying the required rate by the user. +Hysteria 2 is a simple, Chinese-made protocol based on QUIC. +The selling point is Brutal, a congestion control algorithm that +tries to achieve a user-defined bandwidth despite packet loss. !!! warning - Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more characteristics than TCP based proxies. + Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more obvious characteristics than TCP based proxies. -| Specification | Binary Characteristics | Active Detect Hiddenness | -|---------------------------------------------------------------------------|------------------------|--------------------------| -| [hysteria.network](https://v2.hysteria.network/docs/developers/Protocol/) | :material-alert: | :material-check: | +| Specification | Resists passive detection | Resists active probes | +|---------------------------------------------------------------------------|---------------------------|-----------------------| +| [hysteria.network](https://v2.hysteria.network/docs/developers/Protocol/) | :material-alert: | :material-check: | ## :material-text-box-check: Password Generator @@ -44,7 +45,7 @@ To use sing-box with the official program, you need to fill in that combination Replace `up_mbps` and `down_mbps` values with the actual bandwidth of your server. === ":material-harddisk: With local certificate" - + ```json { "inbounds": [ diff --git a/docs/manual/proxy-protocol/shadowsocks.md b/docs/manual/proxy-protocol/shadowsocks.md index 94821d83d1..19f9d4cf93 100644 --- a/docs/manual/proxy-protocol/shadowsocks.md +++ b/docs/manual/proxy-protocol/shadowsocks.md @@ -4,15 +4,18 @@ icon: material/send # Shadowsocks -As the most well-known Chinese-made proxy protocol, -Shadowsocks exists in multiple versions, -but only AEAD 2022 ciphers TCP with multiplexing is recommended. - -| Ciphers | Specification | Cryptographic Security | Binary Characteristics | Active Detect Hiddenness | -|----------------|------------------------------------------------------------|------------------------|------------------------|--------------------------| -| Stream Ciphers | [shadowsocks.org](https://shadowsocks.org/doc/stream.html) | :material-alert: | :material-alert: | :material-alert: | -| AEAD | [shadowsocks.org](https://shadowsocks.org/doc/aead.html) | :material-check: | :material-alert: | :material-alert: | -| AEAD 2022 | [shadowsocks.org](https://shadowsocks.org/doc/sip022.html) | :material-check: | :material-check: | :material-help: | +Shadowsocks is the most well-known Chinese-made proxy protocol. +It exists in multiple versions, but only AEAD 2022 ciphers +over TCP with multiplexing is recommended. + +| Ciphers | Specification | Cryptographically sound | Resists passive detection | Resists active probes | +|----------------|------------------------------------------------------------|-------------------------|---------------------------|-----------------------| +| Stream Ciphers | [shadowsocks.org](https://shadowsocks.org/doc/stream.html) | :material-alert: | :material-alert: | :material-alert: | +| AEAD | [shadowsocks.org](https://shadowsocks.org/doc/aead.html) | :material-check: | :material-alert: | :material-alert: | +| AEAD 2022 | [shadowsocks.org](https://shadowsocks.org/doc/sip022.html) | :material-check: | :material-check: | :material-help: | + +(We strongly recommend using multiplexing to send UDP traffic over TCP, because +doing otherwise is vulnerable to passive detection.) ## :material-text-box-check: Password Generator diff --git a/docs/manual/proxy-protocol/trojan.md b/docs/manual/proxy-protocol/trojan.md index 4c2e16f17e..731322f3a0 100644 --- a/docs/manual/proxy-protocol/trojan.md +++ b/docs/manual/proxy-protocol/trojan.md @@ -4,15 +4,15 @@ icon: material/horse # Trojan -As the most commonly used TLS proxy made in China, Trojan can be used in various combinations, +Torjan is the most commonly used TLS proxy made in China. It can be used in various combinations, but only the combination of uTLS and multiplexing is recommended. -| Protocol and implementation combination | Specification | Binary Characteristics | Active Detect Hiddenness | -|-----------------------------------------|----------------------------------------------------------------------|------------------------|--------------------------| -| Origin / trojan-gfw | [trojan-gfw.github.io](https://trojan-gfw.github.io/trojan/protocol) | :material-check: | :material-check: | -| Basic Go implementation | / | :material-alert: | :material-check: | -| with privates transport by V2Ray | No formal definition | :material-alert: | :material-alert: | -| with uTLS enabled | No formal definition | :material-help: | :material-check: | +| Protocol and implementation combination | Specification | Resists passive detection | Resists active probes | +|-----------------------------------------|----------------------------------------------------------------------|---------------------------|-----------------------| +| Origin / trojan-gfw | [trojan-gfw.github.io](https://trojan-gfw.github.io/trojan/protocol) | :material-check: | :material-check: | +| Basic Go implementation | / | :material-alert: | :material-check: | +| with privates transport by V2Ray | No formal definition | :material-alert: | :material-alert: | +| with uTLS enabled | No formal definition | :material-help: | :material-check: | ## :material-text-box-check: Password Generator @@ -211,4 +211,3 @@ but only the combination of uTLS and multiplexing is recommended. ] } ``` - From 45f3234c730baca3e36914f8016e54c7943b0df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 21 Aug 2024 11:39:01 +0800 Subject: [PATCH 0006/1272] documentation: Update package status --- docs/installation/package-manager.md | 19 +++++++++++++++++++ docs/installation/package-manager.zh.md | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/docs/installation/package-manager.md b/docs/installation/package-manager.md index a56142af03..b4140666f5 100644 --- a/docs/installation/package-manager.md +++ b/docs/installation/package-manager.md @@ -57,6 +57,7 @@ icon: material/package | nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/sing-box.svg)][nixpkgs] | | Homebrew | macOS / Linux | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] | | APK | Alpine | `apk add sing-box` | [![Alpine Linux Edge package](https://repology.org/badge/version-for-repo/alpine_edge/sing-box.svg)][alpine] | + | DEB | AOSC | `apt install sing-box` | [![AOSC package](https://repology.org/badge/version-for-repo/aosc/sing-box.svg)][aosc] | === ":material-apple: macOS" @@ -84,6 +85,22 @@ icon: material/package |------------|----------|------------------------|--------------------------------------------------------------------------------------------| | FreshPorts | FreeBSD | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] | +## :material-alert: Problematic Sources + +| Type | Platform | Link | Promblem(s) | +|------------|----------|-------------------------------------------------------------------------------------------|------------------------------------------------------------------| +| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification; Not actively maintained | +| Homebrew | / | [homebrew-core][brew] | Problematic build tag list modification | +| Termux | Android | [termux-packages][termux] | Problematic build tag list modification | +| FreshPorts | FreeBSD | [FreeBSD ports][ports] | Old Go (go1.20) | + +If you are a user of them, please report issues to them: + +1. Please do not modify release build tags without full understanding of the related functionality: enabling non-default + labels may result in decreased performance; the lack of default labels may cause user confusion. +2. sing-box supports compiling with some older Go versions, but it is not recommended (especially versions that are no + longer supported by Go). + ## :material-book-multiple: Service Management For Linux systems with [systemd][systemd], usually the installation already includes a sing-box service, @@ -122,4 +139,6 @@ you can manage the service using the following command: [ports]: https://www.freshports.org/net/sing-box +[aosc]: https://packages.aosc.io/packages/sing-box + [systemd]: https://systemd.io/ diff --git a/docs/installation/package-manager.zh.md b/docs/installation/package-manager.zh.md index f4e628eb97..3bc03e571c 100644 --- a/docs/installation/package-manager.zh.md +++ b/docs/installation/package-manager.zh.md @@ -57,6 +57,7 @@ icon: material/package | nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/sing-box.svg)][nixpkgs] | | Homebrew | macOS / Linux | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] | | APK | Alpine | `apk add sing-box` | [![Alpine Linux Edge package](https://repology.org/badge/version-for-repo/alpine_edge/sing-box.svg)][alpine] | + | DEB | AOSC | `apt install sing-box` | [![AOSC package](https://repology.org/badge/version-for-repo/aosc/sing-box.svg)][aosc] | === ":material-apple: macOS" @@ -84,6 +85,21 @@ icon: material/package |------------|---------|------------------------|--------------------------------------------------------------------------------------------| | FreshPorts | FreeBSD | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] | + +## :material-alert: 存在问题的源 + +| 类型 | 平台 | 链接 | 原因 | +|------------|---------|-------------------------------------------------------------------------------------------|-----------------------| +| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改; 没有活跃维护 | +| Homebrew | / | [homebrew-core][brew] | 存在问题的构建标志列表修改 | +| Termux | Android | [termux-packages][termux] | 存在问题的构建标志列表修改 | +| FreshPorts | FreeBSD | [FreeBSD ports][ports] | 太旧的 Go (go1.20) | + +如果您是其用户,请向他们报告问题: + +1. 在未完全了解相关功能的情况下,请勿修改发布版本标签:启用非默认标签可能会导致性能下降;缺少默认标签可能会引起用户混淆。 +2. sing-box 支持使用一些较旧的 Go 版本进行编译,但不推荐使用(特别是已不再受 Go 支持的版本)。 + ## :material-book-multiple: 服务管理 对于带有 [systemd][systemd] 的 Linux 系统,通常安装已经包含 sing-box 服务, @@ -118,4 +134,6 @@ icon: material/package [ports]: https://www.freshports.org/net/sing-box +[aosc]: https://packages.aosc.io/packages/sing-box + [systemd]: https://systemd.io/ From f1147965ddf7120c166f61302dce239bd17123c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 21 Aug 2024 17:38:33 +0800 Subject: [PATCH 0007/1272] documentation: Fix missing zh headline --- docs/index.zh.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/index.zh.md b/docs/index.zh.md index 72877118f3..d2f253dde3 100644 --- a/docs/index.zh.md +++ b/docs/index.zh.md @@ -4,6 +4,13 @@ description: 欢迎来到该 sing-box 项目的文档页。 # :material-home: 开始 +!!! failure "需要帮助" + + 由于我们的 Apple 开发者账户出现问题,Apple 平台上的 sing-box 应用暂时无法下载或更新。 + + 如果您的公司或组织愿意帮助我们重返 App Store,请[联系我们](mailto:contact@sagernet.org)。 + + 欢迎来到该 sing-box 项目的文档页。 通用代理平台。 From b57abcc73c110520b7cd355aa14b30feaa86824b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 27 Aug 2024 11:23:20 +0800 Subject: [PATCH 0008/1272] tfo: Fix build with go1.23 --- .goreleaser.yaml | 4 +++- common/dialer/default_go1.20.go | 2 +- common/dialer/tfo.go | 3 ++- go.mod | 2 +- go.sum | 4 ++-- inbound/default_tcp_go1.20.go | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 64c23c9d3e..d9218cacf2 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -7,7 +7,9 @@ builds: - -v - -trimpath ldflags: - - -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid= + - -X github.com/sagernet/sing-box/constant.Version={{ .Version }} + - -s + - -buildid= tags: - with_gvisor - with_quic diff --git a/common/dialer/default_go1.20.go b/common/dialer/default_go1.20.go index 8c3507c213..a9f7b612b8 100644 --- a/common/dialer/default_go1.20.go +++ b/common/dialer/default_go1.20.go @@ -5,7 +5,7 @@ package dialer import ( "net" - "github.com/sagernet/tfo-go" + "github.com/metacubex/tfo-go" ) type tcpDialer = tfo.Dialer diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go index 82e5e9c119..9f72208dba 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -15,7 +15,8 @@ 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/tfo-go" + + "github.com/metacubex/tfo-go" ) type slowOpenConn struct { diff --git a/go.mod b/go.mod index 9999f4744e..d9193ba163 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/libdns/alidns v1.0.3 github.com/libdns/cloudflare v0.1.1 github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.59 github.com/ooni/go-libtor v1.1.8 @@ -36,7 +37,6 @@ require ( github.com/sagernet/sing-tun v0.3.2 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 - github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 github.com/sagernet/utls v1.5.4 github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 diff --git a/go.sum b/go.sum index 78b7cb63a1..40f303fa75 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,8 @@ github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/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/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U= +github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= @@ -126,8 +128,6 @@ github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEY github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= 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/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI= -github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY= github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= diff --git a/inbound/default_tcp_go1.20.go b/inbound/default_tcp_go1.20.go index ee3731ecda..23949b067e 100644 --- a/inbound/default_tcp_go1.20.go +++ b/inbound/default_tcp_go1.20.go @@ -6,7 +6,7 @@ import ( "context" "net" - "github.com/sagernet/tfo-go" + "github.com/metacubex/tfo-go" ) const go120Available = true From 27d6b63e71cae71f8b8b5c8ba27f2aceb8ebbc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 28 Aug 2024 11:40:43 +0800 Subject: [PATCH 0009/1272] Fix stream sniffer --- common/sniff/sniff.go | 49 ++++++++++++++++++++++++++----------------- route/router.go | 2 +- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 424311a86e..3e21b69f92 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -18,33 +18,44 @@ type ( PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error) ) +func Skip(metadata adapter.InboundContext) bool { + // skip server first protocols + switch metadata.Destination.Port { + case 25, 465, 587: + // SMTP + return true + case 143, 993: + // IMAP + return true + case 110, 995: + // POP3 + return true + } + return false +} + func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) (*adapter.InboundContext, error) { if timeout == 0 { timeout = C.ReadPayloadTimeout } deadline := time.Now().Add(timeout) var errors []error - - for i := 0; i < 3; i++ { - err := conn.SetReadDeadline(deadline) - if err != nil { - return nil, E.Cause(err, "set read deadline") - } - _, err = buffer.ReadOnceFrom(conn) - err = E.Errors(err, conn.SetReadDeadline(time.Time{})) - if err != nil { - if i > 0 { - break - } - return nil, E.Cause(err, "read payload") + err := conn.SetReadDeadline(deadline) + if err != nil { + return nil, E.Cause(err, "set read deadline") + } + defer conn.SetReadDeadline(time.Time{}) + var metadata *adapter.InboundContext + for _, sniffer := range sniffers { + if buffer.IsEmpty() { + metadata, err = sniffer(ctx, io.TeeReader(conn, buffer)) + } else { + metadata, err = sniffer(ctx, io.MultiReader(bytes.NewReader(buffer.Bytes()), io.TeeReader(conn, buffer))) } - for _, sniffer := range sniffers { - metadata, err := sniffer(ctx, bytes.NewReader(buffer.Bytes())) - if metadata != nil { - return metadata, nil - } - errors = append(errors, err) + if metadata != nil { + return metadata, nil } + errors = append(errors, err) } return nil, E.Errors(errors...) } diff --git a/route/router.go b/route/router.go index 0c13b1986b..5d89b11853 100644 --- a/route/router.go +++ b/route/router.go @@ -832,7 +832,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad conn = deadline.NewConn(conn) } - if metadata.InboundOptions.SniffEnabled { + if metadata.InboundOptions.SniffEnabled && !sniff.Skip(metadata) { buffer := buf.NewPacket() sniffMetadata, err := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost) if sniffMetadata != nil { From ceda5cc95d02e4d4213ad938654a35a7962cf501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 23 Aug 2024 13:38:27 +0800 Subject: [PATCH 0010/1272] clash-api: Fix bad redirect --- experimental/clashapi/server.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 1eec8448af..375045ac62 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -308,10 +308,11 @@ func authentication(serverSecret string) func(next http.Handler) http.Handler { func hello(redirect bool) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - if redirect { - http.Redirect(w, r, "/ui/", http.StatusTemporaryRedirect) - } else { + contentType := r.Header.Get("Content-Type") + if !redirect || contentType == "application/json" { render.JSON(w, r, render.M{"hello": "clash"}) + } else { + http.Redirect(w, r, "/ui/", http.StatusTemporaryRedirect) } } } From 9bac18bcd1c921dd6f82ae8e4333eae71f40c176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 26 Aug 2024 14:01:32 +0800 Subject: [PATCH 0011/1272] wireguard: Fix events chan leak --- outbound/wireguard.go | 7 ++- transport/wireguard/device_stack.go | 8 +-- transport/wireguard/device_system.go | 91 ++++++++++++++-------------- 3 files changed, 54 insertions(+), 52 deletions(-) diff --git a/outbound/wireguard.go b/outbound/wireguard.go index 7805e165a1..3ae3f63b36 100644 --- a/outbound/wireguard.go +++ b/outbound/wireguard.go @@ -152,6 +152,10 @@ func (w *WireGuard) start() error { } bind = wireguard.NewClientBind(w.ctx, w, w.listener, isConnect, connectAddr, reserved) } + err = w.tunDevice.Start() + if err != nil { + return err + } wgDevice := device.NewDevice(w.tunDevice, bind, &device.Logger{ Verbosef: func(format string, args ...interface{}) { w.logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) @@ -170,7 +174,7 @@ func (w *WireGuard) start() error { } w.device = wgDevice w.pauseCallback = w.pauseManager.RegisterCallback(w.onPauseUpdated) - return w.tunDevice.Start() + return nil } func (w *WireGuard) Close() error { @@ -180,7 +184,6 @@ func (w *WireGuard) Close() error { if w.pauseCallback != nil { w.pauseManager.UnregisterCallback(w.pauseCallback) } - w.tunDevice.Close() return nil } diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index 7f57b7c73a..d5770419e2 100644 --- a/transport/wireguard/device_stack.go +++ b/transport/wireguard/device_stack.go @@ -230,17 +230,13 @@ func (w *StackDevice) Events() <-chan wgTun.Event { } func (w *StackDevice) Close() error { - select { - case <-w.done: - return os.ErrClosed - default: - } + close(w.done) + close(w.events) w.stack.Close() for _, endpoint := range w.stack.CleanupEndpoints() { endpoint.Abort() } w.stack.Wait() - close(w.done) return nil } diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index 49acc5b90e..2c16c53dfc 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -6,6 +6,7 @@ import ( "net" "net/netip" "os" + "sync" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" @@ -21,14 +22,16 @@ import ( var _ Device = (*SystemDevice)(nil) type SystemDevice struct { - dialer N.Dialer - device tun.Tun - batchDevice tun.LinuxTUN - name string - mtu int - events chan wgTun.Event - addr4 netip.Addr - addr6 netip.Addr + dialer N.Dialer + device tun.Tun + batchDevice tun.LinuxTUN + name string + mtu uint32 + inet4Addresses []netip.Prefix + inet6Addresses []netip.Prefix + gso bool + events chan wgTun.Event + closeOnce sync.Once } func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes []netip.Prefix, mtu uint32, gso bool) (*SystemDevice, error) { @@ -44,43 +47,17 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes if interfaceName == "" { interfaceName = tun.CalculateInterfaceName("wg") } - tunInterface, err := tun.New(tun.Options{ - Name: interfaceName, - Inet4Address: inet4Addresses, - Inet6Address: inet6Addresses, - MTU: mtu, - GSO: gso, - }) - if err != nil { - return nil, err - } - var inet4Address netip.Addr - var inet6Address netip.Addr - if len(inet4Addresses) > 0 { - inet4Address = inet4Addresses[0].Addr() - } - if len(inet6Addresses) > 0 { - inet6Address = inet6Addresses[0].Addr() - } - var batchDevice tun.LinuxTUN - if gso { - batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN) - if !isBatchTUN { - return nil, E.New("GSO is not supported on current platform") - } - batchDevice = batchTUN - } + return &SystemDevice{ dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{ BindInterface: interfaceName, })), - device: tunInterface, - batchDevice: batchDevice, - name: interfaceName, - mtu: int(mtu), - events: make(chan wgTun.Event), - addr4: inet4Address, - addr6: inet6Address, + name: interfaceName, + mtu: mtu, + inet4Addresses: inet4Addresses, + inet6Addresses: inet6Addresses, + gso: gso, + events: make(chan wgTun.Event), }, nil } @@ -93,14 +70,39 @@ func (w *SystemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr } func (w *SystemDevice) Inet4Address() netip.Addr { - return w.addr4 + if len(w.inet4Addresses) == 0 { + return netip.Addr{} + } + return w.inet4Addresses[0].Addr() } func (w *SystemDevice) Inet6Address() netip.Addr { - return w.addr6 + if len(w.inet6Addresses) == 0 { + return netip.Addr{} + } + return w.inet6Addresses[0].Addr() } func (w *SystemDevice) Start() error { + tunInterface, err := tun.New(tun.Options{ + Name: w.name, + Inet4Address: w.inet4Addresses, + Inet6Address: w.inet6Addresses, + MTU: w.mtu, + GSO: w.gso, + }) + if err != nil { + return err + } + w.device = tunInterface + if w.gso { + batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN) + if !isBatchTUN { + tunInterface.Close() + return E.New("GSO is not supported on current platform") + } + w.batchDevice = batchTUN + } w.events <- wgTun.EventUp return nil } @@ -143,7 +145,7 @@ func (w *SystemDevice) Flush() error { } func (w *SystemDevice) MTU() (int, error) { - return w.mtu, nil + return int(w.mtu), nil } func (w *SystemDevice) Name() (string, error) { @@ -155,6 +157,7 @@ func (w *SystemDevice) Events() <-chan wgTun.Event { } func (w *SystemDevice) Close() error { + close(w.events) return w.device.Close() } From c1c30976dcd97d15c736bcfc0aecd3e9d4deab38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 7 Sep 2024 10:21:58 +0800 Subject: [PATCH 0012/1272] Improve docker workflow --- .github/workflows/docker.yml | 110 +++++++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 25 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8e75954d22..137a4aa68f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,16 +1,82 @@ -name: Build Docker Images +name: Publish Docker Images on: release: types: - - released + - published workflow_dispatch: inputs: tag: description: "The tag version you want to build" + +env: + REGISTRY_IMAGE: ghcr.io/sagernet/sing-box + jobs: build: runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + platform: + - linux/amd64 + - linux/arm/v6 + - linux/arm/v7 + - linux/arm64 + - linux/386 + - linux/ppc64le + - linux/riscv64 + - linux/s390x + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + with: + ref: ${{ steps.ref.outputs.ref }} + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY_IMAGE }} + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + platforms: ${{ matrix.platform }} + context: . + build-args: | + BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + merge: + runs-on: ubuntu-latest + needs: + - build steps: - name: Get commit to build id: ref @@ -29,34 +95,28 @@ jobs: fi echo "latest=$latest" echo "latest=$latest" >> $GITHUB_OUTPUT - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + - name: Download digests + uses: actions/download-artifact@v4 with: - ref: ${{ steps.ref.outputs.ref }} - - name: Setup Docker Buildx + path: /tmp/digests + pattern: digests-* + merge-multiple: true + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Setup QEMU for Docker Buildx - uses: docker/setup-qemu-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker metadata - id: metadata - uses: docker/metadata-action@v5 - with: - images: ghcr.io/sagernet/sing-box - - name: Build and release Docker images - uses: docker/build-push-action@v6 - with: - platforms: linux/386,linux/amd64,linux/arm64,linux/s390x - context: . - target: dist - build-args: | - BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 - tags: | - ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.latest }} - ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.ref }} - push: true + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + -t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \ + -t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \ + $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }} + docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }} From 87be6dc235cf23209225086bf2676dc82cc53ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 9 Sep 2024 08:48:19 +0800 Subject: [PATCH 0013/1272] Update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 27ad0919dd..7b1c833a42 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ The universal proxy platform. [![Packaging status](https://repology.org/badge/vertical-allrepos/sing-box.svg)](https://repology.org/project/sing-box/versions) -## Support +## Documentation -https://community.sagernet.org/c/sing-box/ +https://sing-box.sagernet.org ## License From 02bc3e0a0a3c323c518cb2634f80483dbdce8f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 9 Sep 2024 14:45:46 +0800 Subject: [PATCH 0014/1272] Update quic-go to v0.47.0 --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index d9193ba163..9fbc59276f 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/gomobile v0.1.3 github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f - github.com/sagernet/quic-go v0.46.0-beta.4 + github.com/sagernet/quic-go v0.47.0-beta.2 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.4.2 github.com/sagernet/sing-dns v0.2.3 @@ -46,7 +46,7 @@ require ( go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.23.0 golang.org/x/net v0.25.0 - golang.org/x/sys v0.21.0 + golang.org/x/sys v0.25.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 @@ -86,8 +86,8 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect diff --git a/go.sum b/go.sum index 40f303fa75..c9c4f2267c 100644 --- a/go.sum +++ b/go.sum @@ -103,8 +103,8 @@ github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.46.0-beta.4 h1:k9f7VSKaM47AY6MPND0Qf1KRN7HwimPg9zdOFTXTiCk= -github.com/sagernet/quic-go v0.46.0-beta.4/go.mod h1:zJmVdJUNqEDXfubf4KtIOUHHerggjBduiGRLNzJspcM= +github.com/sagernet/quic-go v0.47.0-beta.2 h1:1tCGWFOSaXIeuQaHrwOMJIYvlupjTcaVInGQw5ArULU= +github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYigzQ5lQu3Nh4wNh8Q= 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= @@ -173,8 +173,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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -184,14 +184,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= 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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 9e243e0ff90b56b7c23fed31d9c0de51411634b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 10 Sep 2024 23:04:44 +0800 Subject: [PATCH 0015/1272] gomobile: Fix go mod version --- Makefile | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index f6815470ae..723a785091 100644 --- a/Makefile +++ b/Makefile @@ -193,8 +193,8 @@ lib: go run ./cmd/internal/build_libbox -target ios lib_install: - go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.3 - go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3 + go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.4 + go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.4 docs: venv/bin/mkdocs serve diff --git a/go.mod b/go.mod index 9fbc59276f..b4ab0eeaca 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 - github.com/sagernet/gomobile v0.1.3 + github.com/sagernet/gomobile v0.1.4 github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f github.com/sagernet/quic-go v0.47.0-beta.2 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 diff --git a/go.sum b/go.sum index c9c4f2267c..aa8e926f35 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,8 @@ 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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= -github.com/sagernet/gomobile v0.1.3 h1:ohjIb1Ou2+1558PnZour3od69suSuvkdSVOlO1tC4B8= -github.com/sagernet/gomobile v0.1.3/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= +github.com/sagernet/gomobile v0.1.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY= +github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= From d7205ecc6026122c6fbc910ae928c7d899ac1ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Sep 2024 11:38:14 +0800 Subject: [PATCH 0016/1272] Fix Makefile --- Makefile | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 723a785091..64c57a489f 100644 --- a/Makefile +++ b/Makefile @@ -104,10 +104,12 @@ publish_android: publish_android_appcenter: cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease + +# TODO: find why and remove `-destination 'generic/platform=iOS'` build_ios: cd ../sing-box-for-apple && \ rm -rf build/SFI.xcarchive && \ - xcodebuild archive -scheme SFI -configuration Release -archivePath build/SFI.xcarchive + xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates upload_ios_app_store: cd ../sing-box-for-apple && \ @@ -118,55 +120,55 @@ 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 + xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates upload_macos_app_store: cd ../sing-box-for-apple && \ - xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates + xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates release_macos: build_macos upload_macos_app_store -build_macos_independent: +build_macos_standalone: cd ../sing-box-for-apple && \ rm -rf build/SFT.System.xcarchive && \ - xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive + xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates -notarize_macos_independent: +notarize_macos_standalone: cd ../sing-box-for-apple && \ - xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates + xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates -wait_notarize_macos_independent: +wait_notarize_macos_standalone: sleep 60 -export_macos_independent: +export_macos_standalone: rm -rf dist/SFM mkdir -p dist/SFM cd ../sing-box-for-apple && \ xcodebuild -exportNotarizedApp -archivePath build/SFM.System.xcarchive -exportPath "../sing-box/dist/SFM" -upload_macos_independent: +upload_macos_standalone: cd dist/SFM && \ rm -f *.zip && \ zip -ry "SFM-${VERSION}-universal.zip" SFM.app && \ ghr --replace --draft --prerelease "v${VERSION}" *.zip -release_macos_independent: build_macos_independent notarize_macos_independent wait_notarize_macos_independent export_macos_independent upload_macos_independent +release_macos_standalone: build_macos_standalone notarize_macos_standalone wait_notarize_macos_standalone export_macos_standalone upload_macos_standalone build_tvos: cd ../sing-box-for-apple && \ rm -rf build/SFT.xcarchive && \ - xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive + xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates upload_tvos_app_store: cd ../sing-box-for-apple && \ - xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates + xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates release_tvos: build_tvos upload_tvos_app_store update_apple_version: go run ./cmd/internal/update_apple_version -release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent +release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone release_apple_beta: update_apple_version release_ios release_macos release_tvos From 6ddbe19bc022a2cd9b78bcd0e7b43d3b29f310dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Sep 2024 13:58:38 +0800 Subject: [PATCH 0017/1272] platform: Update bundle id --- 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 6a20da6125..238d964f1b 100644 --- a/cmd/internal/update_apple_version/main.go +++ b/cmd/internal/update_apple_version/main.go @@ -26,8 +26,8 @@ func main() { common.Must(decoder.Decode(&project)) objectsMap := project["objects"].(map[string]any) projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj"))) - newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString()) - newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String()) + newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString()) + newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.standalone", "io.nekohasekai.sfa.system"}, newVersion.String()) if updated0 || updated1 { log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")") } From 1f4ed6ff8f42877820350ca47844dff8d942b7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Sep 2024 14:20:36 +0800 Subject: [PATCH 0018/1272] documentation: Update client status --- docs/changelog.md | 6 ------ docs/clients/apple/index.md | 11 +++++------ docs/clients/index.md | 4 ++-- docs/clients/index.zh.md | 10 +++++----- docs/index.md | 6 ------ docs/index.zh.md | 7 ------- 6 files changed, 12 insertions(+), 32 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 2ba895beb9..dbb66ba8cd 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,12 +2,6 @@ icon: material/alert-decagram --- -!!! failure "Help needed" - - Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update. - - If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org). - ### 1.9.4 * Update quic-go to v0.46.0 diff --git a/docs/clients/apple/index.md b/docs/clients/apple/index.md index 98cc342602..53e141f2b4 100644 --- a/docs/clients/apple/index.md +++ b/docs/clients/apple/index.md @@ -11,8 +11,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update. - If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org). - + We are working on getting sing-box apps back on the App Store, which should be completed within a month (TestFlight is already available). ## :material-graph: Requirements @@ -21,13 +20,13 @@ platform-specific function implementation, such as TUN transparent proxy impleme ## :material-download: Download -* [App Store](https://apps.apple.com/us/app/sing-box/id6451272673) -* ~~TestFlight (Beta)~~ +* ~~[App Store](https://apps.apple.com/us/app/sing-box/id6451272673)~~ +* TestFlight (Beta) TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai) (one-time sponsorships are accepted). -Once you donate, you can get an invitation by sending us your Apple ID [via email](mailto:contact@sagernet.org), -or join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot). +Once you donate, you can get an invitation by join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot) +or sending us your Apple ID [via email](mailto:contact@sagernet.org). ## :material-file-download: Download (macOS standalone version) diff --git a/docs/clients/index.md b/docs/clients/index.md index 9dacff6e60..45d2c9a948 100644 --- a/docs/clients/index.md +++ b/docs/clients/index.md @@ -3,9 +3,9 @@ Maintained by Project S to provide a unified experience and platform-specific functionality. | Platform | Client | -| ------------------------------------- | ---------------------------------------- | +|---------------------------------------|------------------------------------------| | :material-android: Android | [sing-box for Android](./android/) | -| :material-apple: iOS/macOS/Apple tvOS | :material-alert: [Unavailable](./apple/) | +| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) | | :material-laptop: Desktop | Working in progress | Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core diff --git a/docs/clients/index.zh.md b/docs/clients/index.zh.md index 6717fa6106..736b42ea20 100644 --- a/docs/clients/index.zh.md +++ b/docs/clients/index.zh.md @@ -2,11 +2,11 @@ 由 Project S 维护,提供统一的体验与平台特定的功能。 -| 平台 | 客户端 | -| ------------------------------------- | ----------------------------------- | -| :material-android: Android | [sing-box for Android](./android/) | -| :material-apple: iOS/macOS/Apple tvOS | :material-alert: [不可用](./apple/) | -| :material-laptop: Desktop | 施工中 | +| 平台 | 客户端 | +|---------------------------------------|------------------------------------------| +| :material-android: Android | [sing-box for Android](./android/) | +| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) | +| :material-laptop: Desktop | 施工中 | 此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业 VPN 客户端功能, 但代码质量很差且包含广告。 diff --git a/docs/index.md b/docs/index.md index 8260109683..2af7222e7b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,12 +4,6 @@ description: Welcome to the wiki page for the sing-box project. # :material-home: Home -!!! failure "Help needed" - - Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update. - - If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org). - Welcome to the wiki page for the sing-box project. The universal proxy platform. diff --git a/docs/index.zh.md b/docs/index.zh.md index d2f253dde3..72877118f3 100644 --- a/docs/index.zh.md +++ b/docs/index.zh.md @@ -4,13 +4,6 @@ description: 欢迎来到该 sing-box 项目的文档页。 # :material-home: 开始 -!!! failure "需要帮助" - - 由于我们的 Apple 开发者账户出现问题,Apple 平台上的 sing-box 应用暂时无法下载或更新。 - - 如果您的公司或组织愿意帮助我们重返 App Store,请[联系我们](mailto:contact@sagernet.org)。 - - 欢迎来到该 sing-box 项目的文档页。 通用代理平台。 From 3dbdda9555a5472f271eb7d4ec31701d1a55df69 Mon Sep 17 00:00:00 2001 From: Monica <1379531829@qq.com> Date: Sun, 15 Sep 2024 11:47:04 +0800 Subject: [PATCH 0019/1272] documentation: Fix dial.zh.md The Chinese documentation incorrectly stated that the default value for the domain_strategy field in the direct outbound module is dns.strategy. The correct value should be inbound.domain_strategy, as specified in the English documentation. This commit corrects the Chinese documentation to align with the accurate behavior described in the English version. Signed-off-by: Monica <1379531829@qq.com> --- docs/configuration/shared/dial.zh.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 300e99ff7a..ccb61c7a44 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -83,7 +83,10 @@ 如果设置,域名将在请求发出之前解析为 IP。 -默认使用 `dns.strategy`。 +| 出站 | 受影响的域名 | 默认回退值 | +|----------|--------------------------|-------------------------------------------| +| `direct` | 请求中的域名 | `inbound.domain_strategy` | +| others | 服务器地址中的域名 | / | #### fallback_delay From 09d4e91b770b4fe6b1252a79bc64f880e39c3398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Sep 2024 11:56:04 +0800 Subject: [PATCH 0020/1272] Fix cached conn eats up read deadlines --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b4ab0eeaca..0cded55e42 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f github.com/sagernet/quic-go v0.47.0-beta.2 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.4.2 + github.com/sagernet/sing v0.4.3 github.com/sagernet/sing-dns v0.2.3 github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-quic v0.2.2 diff --git a/go.sum b/go.sum index aa8e926f35..61165bd180 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYi 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.4.2 h1:jzGNJdZVRI0xlAfFugsIQUPvyB9SuWvbJK7zQCXc4QM= -github.com/sagernet/sing v0.4.2/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= +github.com/sagernet/sing v0.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8= +github.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k= github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= From ad97d4e11fd029ffc016ed7c388b5d48eb9d5368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Sep 2024 11:59:32 +0800 Subject: [PATCH 0021/1272] Fix disconnected interface selected as default in windows --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0cded55e42..d471b4433b 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.3.2 + github.com/sagernet/sing-tun v0.3.3 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.5.4 diff --git a/go.sum b/go.sum index 61165bd180..e6147304a0 100644 --- a/go.sum +++ b/go.sum @@ -122,8 +122,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY= -github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= +github.com/sagernet/sing-tun v0.3.3 h1:LZnQNmfGcNG2KPTPkLgc+Lo7k606QJVkPp2DnjriwUk= +github.com/sagernet/sing-tun v0.3.3/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= From 5385f75f5308dd2146119658058a9a10e25940be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Sep 2024 12:10:00 +0800 Subject: [PATCH 0022/1272] documentation: Update build requirements --- docs/installation/build-from-source.md | 24 ++++++++--------------- docs/installation/build-from-source.zh.md | 24 ++++++++--------------- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/docs/installation/build-from-source.md b/docs/installation/build-from-source.md index 3ee5b6c55f..e7c92ef4b1 100644 --- a/docs/installation/build-from-source.md +++ b/docs/installation/build-from-source.md @@ -6,26 +6,18 @@ icon: material/file-code ## :material-graph: Requirements -Before sing-box 1.4.0: +### sing-box 1.10 -* Go 1.18.5 - 1.20.x - -Since sing-box 1.4.0: - -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ with tag `with_quic` enabled - -Since sing-box 1.5.0: - -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ with tag `with_quic` or `with_ech` enabled - -Since sing-box 1.8.0: - -* Go 1.18.5 - ~ +* Go 1.20.0 - ~ * Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled * Go 1.21.0 - ~ with tag `with_ech` enabled +### sing-box 1.9 + +* Go 1.18.5 - 1.22.x +* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled +* Go 1.21.0 - 1.22.x with tag `with_ech` enabled + You can download and install Go from: https://go.dev/doc/install, latest version is recommended. ## :material-fast-forward: Simple Build diff --git a/docs/installation/build-from-source.zh.md b/docs/installation/build-from-source.zh.md index e5da3e3379..f0bcc49baa 100644 --- a/docs/installation/build-from-source.zh.md +++ b/docs/installation/build-from-source.zh.md @@ -6,25 +6,17 @@ icon: material/file-code ## :material-graph: 要求 -sing-box 1.4.0 前: +### sing-box 1.10 -* Go 1.18.5 - 1.20.x +* Go 1.20.0 - ~ +* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled +* Go 1.21.0 - ~ with tag `with_ech` enabled -从 sing-box 1.4.0: +### sing-box 1.9 -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` - -从 sing-box 1.5.0: - -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_ech` - -从 sing-box 1.8.0: - -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_utls` -* Go 1.20.1 - ~ 如果启用构建标记 `with_ech` +* Go 1.18.5 - 1.22.x +* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled +* Go 1.21.0 - 1.22.x with tag `with_ech` enabled 您可以从 https://go.dev/doc/install 下载并安装 Go,推荐使用最新版本。 From 1ed6654ad4e3fcd74481f781a470d2b117d0e259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Sep 2024 12:12:25 +0800 Subject: [PATCH 0023/1272] Add mips64 build --- .github/workflows/docker.yml | 1 + .goreleaser.fury.yaml | 1 + .goreleaser.yaml | 2 ++ 3 files changed, 4 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 137a4aa68f..a42a3075a7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -27,6 +27,7 @@ jobs: - linux/ppc64le - linux/riscv64 - linux/s390x + - linux/mips64le steps: - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml index 831595d36d..6c7dbc27bf 100644 --- a/.goreleaser.fury.yaml +++ b/.goreleaser.fury.yaml @@ -26,6 +26,7 @@ builds: - linux_arm_7 - linux_s390x - linux_riscv64 + - linux_mips64le mod_timestamp: '{{ .CommitTimestamp }}' snapshot: name_template: "{{ .Version }}.{{ .ShortCommit }}" diff --git a/.goreleaser.yaml b/.goreleaser.yaml index d9218cacf2..b196462876 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -27,9 +27,11 @@ builds: - linux_amd64_v1 - linux_amd64_v3 - linux_arm64 + - linux_arm_6 - linux_arm_7 - linux_s390x - linux_riscv64 + - linux_mips64le - windows_amd64_v1 - windows_amd64_v3 - windows_386 From 97ccd2ca047928e9d6075095b6952734cb341d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 17 Sep 2024 18:47:10 +0800 Subject: [PATCH 0024/1272] documentation: Add sponsors page --- docs/sponsors.md | 26 ++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 27 insertions(+) create mode 100644 docs/sponsors.md diff --git a/docs/sponsors.md b/docs/sponsors.md new file mode 100644 index 0000000000..d40d77096f --- /dev/null +++ b/docs/sponsors.md @@ -0,0 +1,26 @@ +--- +icon: material/hand-coin +--- + +# Sponsors + +Do you or your friends use sing-box? + +You can help keep the project bug-free and feature rich by sponsoring +the project maintainer via [GitHub Sponsors](https://github.com/sponsors/nekohasekai). + +![](https://nekohasekai.github.io/sponsor-images/sponsors.svg) + +### Special Sponsors + +**Viral Tech, Inc.** + +Helping us re-list sing-box apps on the Apple Store. + +--- + +[![JetBrains logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://www.jetbrains.com) + +Free license for the amazing IDEs. + +--- diff --git a/mkdocs.yml b/mkdocs.yml index d5218f4d53..f8a40765f2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,7 @@ nav: - Migration: migration.md - Deprecated: deprecated.md - Support: support.md + - Sponsors: sponsors.md - Installation: - Package Manager: installation/package-manager.md - Docker: installation/docker.md From 4d0362d5305718638ae1390b61e8d02310949199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 17 Sep 2024 18:47:20 +0800 Subject: [PATCH 0025/1272] Update macOS build workflow --- Makefile | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 64c57a489f..d987263c99 100644 --- a/Makefile +++ b/Makefile @@ -130,29 +130,36 @@ release_macos: build_macos upload_macos_app_store build_macos_standalone: cd ../sing-box-for-apple && \ - rm -rf build/SFT.System.xcarchive && \ + rm -rf build/SFM.System.xcarchive && \ xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates -notarize_macos_standalone: - cd ../sing-box-for-apple && \ - xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates - -wait_notarize_macos_standalone: - sleep 60 - -export_macos_standalone: +build_macos_dmg: rm -rf dist/SFM mkdir -p dist/SFM cd ../sing-box-for-apple && \ - xcodebuild -exportNotarizedApp -archivePath build/SFM.System.xcarchive -exportPath "../sing-box/dist/SFM" - -upload_macos_standalone: + 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 \ + --codesign "B2324162A090F01F96111CF802A83F0F36674F80" \ + --notarize "notarytool-password" \ + "../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app" + +upload_macos_dmg: cd dist/SFM && \ - rm -f *.zip && \ - zip -ry "SFM-${VERSION}-universal.zip" SFM.app && \ - ghr --replace --draft --prerelease "v${VERSION}" *.zip + cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \ + ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg" -release_macos_standalone: build_macos_standalone notarize_macos_standalone wait_notarize_macos_standalone export_macos_standalone upload_macos_standalone +release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg build_tvos: cd ../sing-box-for-apple && \ @@ -168,7 +175,7 @@ release_tvos: build_tvos upload_tvos_app_store update_apple_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_ios update_apple_version release_ios release_macos release_tvos release_apple_beta: update_apple_version release_ios release_macos release_tvos From f5554dd8b819d21711f5a07554e66e595fa39066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Sep 2024 12:42:27 +0800 Subject: [PATCH 0026/1272] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 27 +++++++++++++++++++++++++++ docs/clients/apple/index.md | 4 ++-- docs/migration.md | 17 +++++++++++++++++ docs/migration.zh.md | 16 ++++++++++++++++ 6 files changed, 64 insertions(+), 4 deletions(-) diff --git a/clients/android b/clients/android index 440aaa9a1a..dcf6e1f20a 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 440aaa9a1a7504293758243b18478592aaf670b7 +Subproject commit dcf6e1f20a3789c4e4bccafba183d56e8a0947a7 diff --git a/clients/apple b/clients/apple index aa4ce98421..8b5ad1928f 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit aa4ce9842174af85e4d3b2bd5e1651e4f87d3526 +Subproject commit 8b5ad1928fcfec923086963b0415672714cfd86f diff --git a/docs/changelog.md b/docs/changelog.md index dbb66ba8cd..6cf9f5faa1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,28 @@ icon: material/alert-decagram --- +### 1.9.5 + +* Update quic-go to v0.47.0 +* Fix direct dialer not resolving domain +* Fix no error return when empty DNS cache retrieved +* Fix build with go1.23 +* Fix stream sniffer +* Fix bad redirect in clash-api +* Fix wireguard events chan leak +* Fix cached conn eats up read deadlines +* Fix disconnected interface selected as default in windows +* Update Bundle Identifiers for Apple platform clients **1** + +**1**: + +See [Migration](/migration/#bundle-identifier-updates-in-apple-platform-clients). + +We are still working on getting all sing-box apps back on the App Store. + +This work is expected to be completed within a week +(SFI on the App Store and others on TestFlight are already available). + ### 1.9.4 * Update quic-go to v0.46.0 @@ -17,6 +39,11 @@ icon: material/alert-decagram * Fix UDP connnection leak when sniffing * Fixes and improvements +_Due to problems with our Apple developer account, +sing-box apps on Apple platforms are temporarily unavailable for download or update. +If your company or organization is willing to help us return to the App Store, +please [contact us](mailto:contact@sagernet.org)._ + ### 1.9.3 * Fixes and improvements diff --git a/docs/clients/apple/index.md b/docs/clients/apple/index.md index 53e141f2b4..f5b754c1b7 100644 --- a/docs/clients/apple/index.md +++ b/docs/clients/apple/index.md @@ -11,7 +11,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update. - We are working on getting sing-box apps back on the App Store, which should be completed within a month (TestFlight is already available). + We are working on getting sing-box apps back on the App Store, which should be completed within a week (SFI on the App Store and others on TestFlight are already available). ## :material-graph: Requirements @@ -20,7 +20,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme ## :material-download: Download -* ~~[App Store](https://apps.apple.com/us/app/sing-box/id6451272673)~~ +* [App Store](https://apps.apple.com/app/sing-box-vt/id6673731168) * TestFlight (Beta) TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai) diff --git a/docs/migration.md b/docs/migration.md index efe92dfd6a..6ea0de4927 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -2,6 +2,23 @@ icon: material/arrange-bring-forward --- +## 1.9.5 + +### Bundle Identifier updates in Apple platform clients + +Due to problems with our old Apple developer account, +we can only change Bundle Identifiers to re-list sing-box apps, +which means the data will not be automatically inherited. + +For iOS, you need to back up your old data yourself (if you still have access to it); +for tvOS, you need to re-import profiles from your iPhone or iPad or create it manually; +for macOS, you can migrate the data folder using the following command: + +```bash +cd ~/Library/Group\ Containers && \ + mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt +``` + ## 1.9.0 ### `domain_suffix` behavior update diff --git a/docs/migration.zh.md b/docs/migration.zh.md index ce23875a93..2cbbba6f4b 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -2,6 +2,22 @@ icon: material/arrange-bring-forward --- +## 1.9.5 + +### Apple 平台客户端的 Bundle Identifier 更新 + +由于我们旧的苹果开发者账户存在问题,我们只能通过更新 Bundle Identifiers +来重新上架 sing-box 应用, 这意味着数据不会自动继承。 + +对于 iOS,您需要自行备份旧的数据(如果您仍然可以访问); +对于 Apple tvOS,您需要从 iPhone 或 iPad 重新导入配置或者手动创建; +对于 macOS,您可以使用以下命令迁移数据文件夹: + +```bash +cd ~/Library/Group\ Containers && \ + mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt +``` + ## 1.9.0 ### `domain_suffix` 行为更新 From 3b0cba0852a1238222e1e1b470a88abe07b9dfce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Sep 2024 20:12:12 +0800 Subject: [PATCH 0027/1272] Fix wireguard start --- 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 2c16c53dfc..36c270f9d3 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -57,7 +57,7 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes inet4Addresses: inet4Addresses, inet6Addresses: inet6Addresses, gso: gso, - events: make(chan wgTun.Event), + events: make(chan wgTun.Event, 1), }, nil } From 700a8eb4253c55e7335fbecec92636c33c37d481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Sep 2024 20:13:14 +0800 Subject: [PATCH 0028/1272] Minor fixes --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index d987263c99..6ede76485c 100644 --- a/Makefile +++ b/Makefile @@ -150,7 +150,6 @@ build_macos_dmg: --hide-extension "SFM.app" \ --app-drop-link 0 0 \ --skip-jenkins \ - --codesign "B2324162A090F01F96111CF802A83F0F36674F80" \ --notarize "notarytool-password" \ "../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app" From e2077009c4d3400b6982c6782798cd01e14db8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Sep 2024 20:13:55 +0800 Subject: [PATCH 0029/1272] documentation: Update client status --- docs/clients/apple/index.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/clients/apple/index.md b/docs/clients/apple/index.md index f5b754c1b7..dc7be7ecda 100644 --- a/docs/clients/apple/index.md +++ b/docs/clients/apple/index.md @@ -7,12 +7,6 @@ icon: material/apple SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides platform-specific function implementation, such as TUN transparent proxy implementation. -!!! failure "Unavailable" - - Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update. - - We are working on getting sing-box apps back on the App Store, which should be completed within a week (SFI on the App Store and others on TestFlight are already available). - ## :material-graph: Requirements * iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+ From 39d712765115d979e55057ebd12a55986939ff45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Sep 2024 20:40:02 +0800 Subject: [PATCH 0030/1272] Revert "Fix stream sniffer" --- common/sniff/sniff.go | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 3e21b69f92..2d2999a83e 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -40,22 +40,28 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout } deadline := time.Now().Add(timeout) var errors []error - err := conn.SetReadDeadline(deadline) - if err != nil { - return nil, E.Cause(err, "set read deadline") - } - defer conn.SetReadDeadline(time.Time{}) - var metadata *adapter.InboundContext - for _, sniffer := range sniffers { - if buffer.IsEmpty() { - metadata, err = sniffer(ctx, io.TeeReader(conn, buffer)) - } else { - metadata, err = sniffer(ctx, io.MultiReader(bytes.NewReader(buffer.Bytes()), io.TeeReader(conn, buffer))) + for i := 0; ; i++ { + err := conn.SetReadDeadline(deadline) + if err != nil { + return nil, E.Cause(err, "set read deadline") } - if metadata != nil { - return metadata, nil + _, err = buffer.ReadOnceFrom(conn) + _ = conn.SetReadDeadline(time.Time{}) + if err != nil { + if i > 0 { + break + } + return nil, E.Cause(err, "read payload") + } + errors = nil + var metadata *adapter.InboundContext + for _, sniffer := range sniffers { + metadata, err = sniffer(ctx, bytes.NewReader(buffer.Bytes())) + if metadata != nil { + return metadata, nil + } + errors = append(errors, err) } - errors = append(errors, err) } return nil, E.Errors(errors...) } From 8464c8cb7cf49b6086de3651c63cd7c68d03496b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Sep 2024 21:10:08 +0800 Subject: [PATCH 0031/1272] Fix version script --- cmd/internal/update_apple_version/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/internal/update_apple_version/main.go b/cmd/internal/update_apple_version/main.go index 238d964f1b..0da0f64976 100644 --- a/cmd/internal/update_apple_version/main.go +++ b/cmd/internal/update_apple_version/main.go @@ -27,7 +27,7 @@ func main() { objectsMap := project["objects"].(map[string]any) projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj"))) newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString()) - newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.standalone", "io.nekohasekai.sfa.system"}, newVersion.String()) + newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String()) if updated0 || updated1 { log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")") } From 8c7eaa4477fbff8b4c24178646d1821395f35045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 18 Sep 2024 14:25:33 +0800 Subject: [PATCH 0032/1272] Fix docker build --- .github/workflows/docker.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a42a3075a7..61260b0fb5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -27,12 +27,22 @@ jobs: - linux/ppc64le - linux/riscv64 - linux/s390x - - linux/mips64le 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@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 with: ref: ${{ steps.ref.outputs.ref }} + fetch-depth: 0 - name: Prepare run: | platform=${{ matrix.platform }} From e586d9e9bc88427e1b1de9013d531597c9900ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Sep 2024 21:08:26 +0800 Subject: [PATCH 0033/1272] 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 dcf6e1f20a..defe07a26f 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit dcf6e1f20a3789c4e4bccafba183d56e8a0947a7 +Subproject commit defe07a26fed49181e6c43ebe504301452335bc9 diff --git a/clients/apple b/clients/apple index 8b5ad1928f..41ed30a5fa 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 8b5ad1928fcfec923086963b0415672714cfd86f +Subproject commit 41ed30a5faa14437104f83c947ab5aae028e3c13 diff --git a/docs/changelog.md b/docs/changelog.md index 6cf9f5faa1..80ee793c3d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +### 1.9.6 + +* Fixes and improvements + ### 1.9.5 * Update quic-go to v0.47.0 From 95606191d8be5c878ddac784aa88848808c90c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 23 Sep 2024 22:12:19 +0800 Subject: [PATCH 0034/1272] Add completions for linux packages --- .goreleaser.fury.yaml | 9 + .goreleaser.yaml | 53 +- Makefile | 4 +- cmd/sing-box/cmd.go | 68 ++ cmd/sing-box/generate_completions.go | 28 + cmd/sing-box/main.go | 69 +- release/completions/sing-box.bash | 1549 ++++++++++++++++++++++++++ release/completions/sing-box.fish | 235 ++++ release/completions/sing-box.zsh | 212 ++++ release/config/openwrt.conf | 5 + release/config/openwrt.init | 31 + release/config/sing-box.initd | 18 + 12 files changed, 2202 insertions(+), 79 deletions(-) create mode 100644 cmd/sing-box/cmd.go create mode 100644 cmd/sing-box/generate_completions.go create mode 100644 release/completions/sing-box.bash create mode 100644 release/completions/sing-box.fish create mode 100644 release/completions/sing-box.zsh create mode 100644 release/config/openwrt.conf create mode 100644 release/config/openwrt.init create mode 100644 release/config/sing-box.initd diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml index 6c7dbc27bf..fbd1ae427d 100644 --- a/.goreleaser.fury.yaml +++ b/.goreleaser.fury.yaml @@ -49,10 +49,19 @@ nfpms: - src: release/config/config.json dst: /etc/sing-box/config.json type: config + - 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/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: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b196462876..2d0b234f81 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,3 +1,4 @@ +version: 2 project_name: sing-box builds: - &template @@ -25,7 +26,6 @@ builds: targets: - linux_386 - linux_amd64_v1 - - linux_amd64_v3 - linux_arm64 - linux_arm_6 - linux_arm_7 @@ -33,7 +33,6 @@ builds: - linux_riscv64 - linux_mips64le - windows_amd64_v1 - - windows_amd64_v3 - windows_386 - windows_arm64 - darwin_amd64_v1 @@ -90,8 +89,6 @@ builds: - android_arm64 - android_386 - android_amd64 -snapshot: - name_template: "{{ .Version }}.{{ .ShortCommit }}" archives: - &template id: archive @@ -105,7 +102,7 @@ archives: wrap_in_directory: true files: - LICENSE - name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' + 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: @@ -114,7 +111,7 @@ archives: nfpms: - 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 }}' + 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/ @@ -125,15 +122,26 @@ nfpms: - deb - rpm - archlinux +# - apk +# - ipk priority: extra contents: - src: release/config/config.json dst: /etc/sing-box/config.json type: config + - 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/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: @@ -145,13 +153,34 @@ nfpms: signature: key_file: "{{ .Env.NFPM_KEY_PATH }}" overrides: - deb: - conflicts: - - sing-box-beta - rpm: - conflicts: - - sing-box-beta + 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' diff --git a/Makefile b/Makefile index 6ede76485c..4918bf25d1 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,9 @@ ci_build: go build $(PARAMS) $(MAIN) go build $(MAIN_PARAMS) $(MAIN) +generate_completions: + go run -v --tags generate,generate_completions $(MAIN) + install: go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) @@ -71,7 +74,6 @@ release: dist/*.deb \ dist/*.rpm \ dist/*_amd64.pkg.tar.zst \ - dist/*_amd64v3.pkg.tar.zst \ dist/*_arm64.pkg.tar.zst \ dist/release ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go new file mode 100644 index 0000000000..f6c37ff73e --- /dev/null +++ b/cmd/sing-box/cmd.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "os" + "os/user" + "strconv" + "time" + + _ "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/service/filemanager" + + "github.com/spf13/cobra" +) + +var ( + globalCtx context.Context + configPaths []string + configDirectories []string + workingDir string + disableColor bool +) + +var mainCommand = &cobra.Command{ + Use: "sing-box", + PersistentPreRun: preRun, +} + +func init() { + mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path") + mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path") + mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory") + mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output") +} + +func preRun(cmd *cobra.Command, args []string) { + globalCtx = context.Background() + sudoUser := os.Getenv("SUDO_USER") + sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID")) + sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID")) + if sudoUID == 0 && sudoGID == 0 && sudoUser != "" { + sudoUserObject, _ := user.Lookup(sudoUser) + if sudoUserObject != nil { + sudoUID, _ = strconv.Atoi(sudoUserObject.Uid) + sudoGID, _ = strconv.Atoi(sudoUserObject.Gid) + } + } + if sudoUID > 0 && sudoGID > 0 { + globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID) + } + if disableColor { + log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger()) + } + if workingDir != "" { + _, err := os.Stat(workingDir) + if err != nil { + filemanager.MkdirAll(globalCtx, workingDir, 0o777) + } + err = os.Chdir(workingDir) + if err != nil { + log.Fatal(err) + } + } + if len(configPaths) == 0 && len(configDirectories) == 0 { + configPaths = append(configPaths, "config.json") + } +} diff --git a/cmd/sing-box/generate_completions.go b/cmd/sing-box/generate_completions.go new file mode 100644 index 0000000000..6ab0cade9e --- /dev/null +++ b/cmd/sing-box/generate_completions.go @@ -0,0 +1,28 @@ +//go:build generate && generate_completions + +package main + +import "github.com/sagernet/sing-box/log" + +func main() { + err := generateCompletions() + if err != nil { + log.Fatal(err) + } +} + +func generateCompletions() error { + err := mainCommand.GenBashCompletionFile("release/completions/sing-box.bash") + if err != nil { + return err + } + err = mainCommand.GenFishCompletionFile("release/completions/sing-box.fish", true) + if err != nil { + return err + } + err = mainCommand.GenZshCompletionFile("release/completions/sing-box.zsh") + if err != nil { + return err + } + return nil +} diff --git a/cmd/sing-box/main.go b/cmd/sing-box/main.go index 66b7daa1d9..fd55c7d319 100644 --- a/cmd/sing-box/main.go +++ b/cmd/sing-box/main.go @@ -1,74 +1,11 @@ -package main - -import ( - "context" - "os" - "os/user" - "strconv" - "time" - - _ "github.com/sagernet/sing-box/include" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/service/filemanager" - - "github.com/spf13/cobra" -) +//go:build !generate -var ( - globalCtx context.Context - configPaths []string - configDirectories []string - workingDir string - disableColor bool -) - -var mainCommand = &cobra.Command{ - Use: "sing-box", - PersistentPreRun: preRun, -} +package main -func init() { - mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path") - mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path") - mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory") - mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output") -} +import "github.com/sagernet/sing-box/log" func main() { if err := mainCommand.Execute(); err != nil { log.Fatal(err) } } - -func preRun(cmd *cobra.Command, args []string) { - globalCtx = context.Background() - sudoUser := os.Getenv("SUDO_USER") - sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID")) - sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID")) - if sudoUID == 0 && sudoGID == 0 && sudoUser != "" { - sudoUserObject, _ := user.Lookup(sudoUser) - if sudoUserObject != nil { - sudoUID, _ = strconv.Atoi(sudoUserObject.Uid) - sudoGID, _ = strconv.Atoi(sudoUserObject.Gid) - } - } - if sudoUID > 0 && sudoGID > 0 { - globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID) - } - if disableColor { - log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger()) - } - if workingDir != "" { - _, err := os.Stat(workingDir) - if err != nil { - filemanager.MkdirAll(globalCtx, workingDir, 0o777) - } - err = os.Chdir(workingDir) - if err != nil { - log.Fatal(err) - } - } - if len(configPaths) == 0 && len(configDirectories) == 0 { - configPaths = append(configPaths, "config.json") - } -} diff --git a/release/completions/sing-box.bash b/release/completions/sing-box.bash new file mode 100644 index 0000000000..ab1029770b --- /dev/null +++ b/release/completions/sing-box.bash @@ -0,0 +1,1549 @@ +# bash completion for sing-box -*- shell-script -*- + +__sing-box_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__sing-box_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__sing-box_index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__sing-box_contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__sing-box_handle_go_custom_completion() +{ + __sing-box_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" + + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + + local out requestComp lastParam lastChar comp directive args + + # Prepare the command to request completions for the program. + # Calling ${words[0]} instead of directly sing-box allows handling aliases + args=("${words[@]:1}") + # Disable ActiveHelp which is not supported for bash completion v1 + requestComp="SING_BOX_ACTIVE_HELP=0 ${words[0]} __completeNoDesc ${args[*]}" + + lastParam=${words[$((${#words[@]}-1))]} + lastChar=${lastParam:$((${#lastParam}-1)):1} + __sing-box_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" + + if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __sing-box_debug "${FUNCNAME[0]}: Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __sing-box_debug "${FUNCNAME[0]}: calling ${requestComp}" + # Use eval to handle any environment variables and such + out=$(eval "${requestComp}" 2>/dev/null) + + # Extract the directive integer at the very end of the output following a colon (:) + directive=${out##*:} + # Remove the directive + out=${out%:*} + if [ "${directive}" = "${out}" ]; then + # There is not directive specified + directive=0 + fi + __sing-box_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" + __sing-box_debug "${FUNCNAME[0]}: the completions are: ${out}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + # Error code. No completion. + __sing-box_debug "${FUNCNAME[0]}: received error from custom completion go code" + return + else + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __sing-box_debug "${FUNCNAME[0]}: activating no space" + compopt -o nospace + fi + fi + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __sing-box_debug "${FUNCNAME[0]}: activating no file completion" + compopt +o default + fi + fi + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local fullFilter filter filteringCmd + # Do not use quotes around the $out variable or else newline + # characters will be kept. + for filter in ${out}; do + fullFilter+="$filter|" + done + + filteringCmd="_filedir $fullFilter" + __sing-box_debug "File filtering command: $filteringCmd" + $filteringCmd + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subdir + # Use printf to strip any trailing newline + subdir=$(printf "%s" "${out}") + if [ -n "$subdir" ]; then + __sing-box_debug "Listing directories in $subdir" + __sing-box_handle_subdirs_in_dir_flag "$subdir" + else + __sing-box_debug "Listing directories in ." + _filedir -d + fi + else + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${out}" -- "$cur") + fi +} + +__sing-box_handle_reply() +{ + __sing-box_debug "${FUNCNAME[0]}" + local comp + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${allflags[*]}" -- "$cur") + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%=*}" + __sing-box_index_of_word "${flag}" "${flags_with_completion[@]}" + COMPREPLY=() + if [[ ${index} -ge 0 ]]; then + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION:-}" ]; then + # zsh completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + + if [[ -z "${flag_parsing_disabled}" ]]; then + # If flag parsing is enabled, we have completed the flags and can return. + # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough + # to possibly call handle_go_custom_completion. + return 0; + fi + ;; + esac + + # check if we are handling a flag with special work handling + local index + __sing-box_index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions+=("${must_have_one_noun[@]}") + elif [[ -n "${has_completion_function}" ]]; then + # if a go completion function is provided, defer to that function + __sing-box_handle_go_custom_completion + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${completions[*]}" -- "$cur") + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${noun_aliases[*]}" -- "$cur") + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + if declare -F __sing-box_custom_func >/dev/null; then + # try command name qualified custom func + __sing-box_custom_func + else + # otherwise fall back to unqualified for compatibility + declare -F __custom_func >/dev/null && __custom_func + fi + fi + + # available in bash-completion >= 2, not always present on macOS + if declare -F __ltrim_colon_completions >/dev/null; then + __ltrim_colon_completions "$cur" + fi + + # If there is only 1 completion and it is a flag with an = it will be completed + # but we don't want a space after the = + if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then + compopt -o nospace + fi +} + +# The arguments should be in the form "ext1|ext2|extn" +__sing-box_handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__sing-box_handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return +} + +__sing-box_handle_flag() +{ + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue="" + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __sing-box_debug "${FUNCNAME[0]}: looking for ${flagname}" + if __sing-box_contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __sing-box_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + # flaghash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + fi + + # skip the argument to a two word flag + if [[ ${words[c]} != *"="* ]] && __sing-box_contains_word "${words[c]}" "${two_word_flags[@]}"; then + __sing-box_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__sing-box_handle_noun() +{ + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __sing-box_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __sing-box_contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__sing-box_handle_command() +{ + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_sing-box_root_command" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __sing-box_debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F "$next_command" >/dev/null && $next_command +} + +__sing-box_handle_word() +{ + if [[ $c -ge $cword ]]; then + __sing-box_handle_reply + return + fi + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __sing-box_handle_flag + elif __sing-box_contains_word "${words[c]}" "${commands[@]}"; then + __sing-box_handle_command + elif [[ $c -eq 0 ]]; then + __sing-box_handle_command + elif __sing-box_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __sing-box_handle_command + else + __sing-box_handle_noun + fi + else + __sing-box_handle_noun + fi + __sing-box_handle_word +} + +_sing-box_check() +{ + last_command="sing-box_check" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_format() +{ + last_command="sing-box_format" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_ech-keypair() +{ + last_command="sing-box_generate_ech-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--pq-signature-schemes-enabled") + local_nonpersistent_flags+=("--pq-signature-schemes-enabled") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_rand() +{ + last_command="sing-box_generate_rand" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--base64") + local_nonpersistent_flags+=("--base64") + flags+=("--hex") + local_nonpersistent_flags+=("--hex") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_reality-keypair() +{ + last_command="sing-box_generate_reality-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_tls-keypair() +{ + last_command="sing-box_generate_tls-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--months=") + two_word_flags+=("--months") + two_word_flags+=("-m") + local_nonpersistent_flags+=("--months") + local_nonpersistent_flags+=("--months=") + local_nonpersistent_flags+=("-m") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_uuid() +{ + last_command="sing-box_generate_uuid" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_vapid-keypair() +{ + last_command="sing-box_generate_vapid-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_wg-keypair() +{ + last_command="sing-box_generate_wg-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate() +{ + last_command="sing-box_generate" + + command_aliases=() + + commands=() + commands+=("ech-keypair") + commands+=("rand") + commands+=("reality-keypair") + commands+=("tls-keypair") + commands+=("uuid") + commands+=("vapid-keypair") + commands+=("wg-keypair") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip_export() +{ + last_command="sing-box_geoip_export" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip_list() +{ + last_command="sing-box_geoip_list" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip_lookup() +{ + last_command="sing-box_geoip_lookup" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip() +{ + last_command="sing-box_geoip" + + command_aliases=() + + commands=() + commands+=("export") + commands+=("list") + commands+=("lookup") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite_export() +{ + last_command="sing-box_geosite_export" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite_list() +{ + last_command="sing-box_geosite_list" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite_lookup() +{ + last_command="sing-box_geosite_lookup" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite() +{ + last_command="sing-box_geosite" + + command_aliases=() + + commands=() + commands+=("export") + commands+=("list") + commands+=("lookup") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_merge() +{ + last_command="sing-box_merge" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_compile() +{ + last_command="sing-box_rule-set_compile" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_convert() +{ + last_command="sing-box_rule-set_convert" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--type=") + two_word_flags+=("--type") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--type") + local_nonpersistent_flags+=("--type=") + local_nonpersistent_flags+=("-t") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_decompile() +{ + last_command="sing-box_rule-set_decompile" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_format() +{ + last_command="sing-box_rule-set_format" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_match() +{ + last_command="sing-box_rule-set_match" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--format=") + two_word_flags+=("--format") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + local_nonpersistent_flags+=("-f") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_upgrade() +{ + last_command="sing-box_rule-set_upgrade" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set() +{ + last_command="sing-box_rule-set" + + command_aliases=() + + commands=() + commands+=("compile") + commands+=("convert") + commands+=("decompile") + commands+=("format") + commands+=("match") + commands+=("upgrade") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_run() +{ + last_command="sing-box_run" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools_connect() +{ + last_command="sing-box_tools_connect" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--network=") + two_word_flags+=("--network") + two_word_flags+=("-n") + local_nonpersistent_flags+=("--network") + local_nonpersistent_flags+=("--network=") + local_nonpersistent_flags+=("-n") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools_fetch() +{ + last_command="sing-box_tools_fetch" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools_synctime() +{ + last_command="sing-box_tools_synctime" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--format=") + two_word_flags+=("--format") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + local_nonpersistent_flags+=("-f") + flags+=("--server=") + two_word_flags+=("--server") + two_word_flags+=("-s") + local_nonpersistent_flags+=("--server") + local_nonpersistent_flags+=("--server=") + local_nonpersistent_flags+=("-s") + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools() +{ + last_command="sing-box_tools" + + command_aliases=() + + commands=() + commands+=("connect") + commands+=("fetch") + commands+=("synctime") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_version() +{ + last_command="sing-box_version" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--name") + flags+=("-n") + local_nonpersistent_flags+=("--name") + local_nonpersistent_flags+=("-n") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_root_command() +{ + last_command="sing-box" + + command_aliases=() + + commands=() + commands+=("check") + commands+=("format") + commands+=("generate") + commands+=("geoip") + commands+=("geosite") + commands+=("merge") + commands+=("rule-set") + commands+=("run") + commands+=("tools") + commands+=("version") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +__start_sing-box() +{ + local cur prev words cword split + declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __sing-box_init_completion -n "=" || return + fi + + local c=0 + local flag_parsing_disabled= + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("sing-box") + local command_aliases=() + local must_have_one_flag=() + local must_have_one_noun=() + local has_completion_function="" + local last_command="" + local nouns=() + local noun_aliases=() + + __sing-box_handle_word +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_sing-box sing-box +else + complete -o default -o nospace -F __start_sing-box sing-box +fi + +# ex: ts=4 sw=4 et filetype=sh diff --git a/release/completions/sing-box.fish b/release/completions/sing-box.fish new file mode 100644 index 0000000000..2aec1e1d29 --- /dev/null +++ b/release/completions/sing-box.fish @@ -0,0 +1,235 @@ +# fish completion for sing-box -*- shell-script -*- + +function __sing_box_debug + set -l file "$BASH_COMP_DEBUG_FILE" + if test -n "$file" + echo "$argv" >> $file + end +end + +function __sing_box_perform_completion + __sing_box_debug "Starting __sing_box_perform_completion" + + # Extract all args except the last one + set -l args (commandline -opc) + # Extract the last arg and escape it in case it is a space + set -l lastArg (string escape -- (commandline -ct)) + + __sing_box_debug "args: $args" + __sing_box_debug "last arg: $lastArg" + + # Disable ActiveHelp which is not supported for fish shell + set -l requestComp "SING_BOX_ACTIVE_HELP=0 $args[1] __complete $args[2..-1] $lastArg" + + __sing_box_debug "Calling $requestComp" + set -l results (eval $requestComp 2> /dev/null) + + # Some programs may output extra empty lines after the directive. + # Let's ignore them or else it will break completion. + # Ref: https://github.com/spf13/cobra/issues/1279 + for line in $results[-1..1] + if test (string trim -- $line) = "" + # Found an empty line, remove it + set results $results[1..-2] + else + # Found non-empty line, we have our proper output + break + end + end + + set -l comps $results[1..-2] + set -l directiveLine $results[-1] + + # For Fish, when completing a flag with an = (e.g., -n=) + # completions must be prefixed with the flag + set -l flagPrefix (string match -r -- '-.*=' "$lastArg") + + __sing_box_debug "Comps: $comps" + __sing_box_debug "DirectiveLine: $directiveLine" + __sing_box_debug "flagPrefix: $flagPrefix" + + for comp in $comps + printf "%s%s\n" "$flagPrefix" "$comp" + end + + printf "%s\n" "$directiveLine" +end + +# this function limits calls to __sing_box_perform_completion, by caching the result behind $__sing_box_perform_completion_once_result +function __sing_box_perform_completion_once + __sing_box_debug "Starting __sing_box_perform_completion_once" + + if test -n "$__sing_box_perform_completion_once_result" + __sing_box_debug "Seems like a valid result already exists, skipping __sing_box_perform_completion" + return 0 + end + + set --global __sing_box_perform_completion_once_result (__sing_box_perform_completion) + if test -z "$__sing_box_perform_completion_once_result" + __sing_box_debug "No completions, probably due to a failure" + return 1 + end + + __sing_box_debug "Performed completions and set __sing_box_perform_completion_once_result" + return 0 +end + +# this function is used to clear the $__sing_box_perform_completion_once_result variable after completions are run +function __sing_box_clear_perform_completion_once_result + __sing_box_debug "" + __sing_box_debug "========= clearing previously set __sing_box_perform_completion_once_result variable ==========" + set --erase __sing_box_perform_completion_once_result + __sing_box_debug "Successfully erased the variable __sing_box_perform_completion_once_result" +end + +function __sing_box_requires_order_preservation + __sing_box_debug "" + __sing_box_debug "========= checking if order preservation is required ==========" + + __sing_box_perform_completion_once + if test -z "$__sing_box_perform_completion_once_result" + __sing_box_debug "Error determining if order preservation is required" + return 1 + end + + set -l directive (string sub --start 2 $__sing_box_perform_completion_once_result[-1]) + __sing_box_debug "Directive is: $directive" + + set -l shellCompDirectiveKeepOrder 32 + set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) % 2) + __sing_box_debug "Keeporder is: $keeporder" + + if test $keeporder -ne 0 + __sing_box_debug "This does require order preservation" + return 0 + end + + __sing_box_debug "This doesn't require order preservation" + return 1 +end + + +# This function does two things: +# - Obtain the completions and store them in the global __sing_box_comp_results +# - Return false if file completion should be performed +function __sing_box_prepare_completions + __sing_box_debug "" + __sing_box_debug "========= starting completion logic ==========" + + # Start fresh + set --erase __sing_box_comp_results + + __sing_box_perform_completion_once + __sing_box_debug "Completion results: $__sing_box_perform_completion_once_result" + + if test -z "$__sing_box_perform_completion_once_result" + __sing_box_debug "No completion, probably due to a failure" + # Might as well do file completion, in case it helps + return 1 + end + + set -l directive (string sub --start 2 $__sing_box_perform_completion_once_result[-1]) + set --global __sing_box_comp_results $__sing_box_perform_completion_once_result[1..-2] + + __sing_box_debug "Completions are: $__sing_box_comp_results" + __sing_box_debug "Directive is: $directive" + + set -l shellCompDirectiveError 1 + set -l shellCompDirectiveNoSpace 2 + set -l shellCompDirectiveNoFileComp 4 + set -l shellCompDirectiveFilterFileExt 8 + set -l shellCompDirectiveFilterDirs 16 + + if test -z "$directive" + set directive 0 + end + + set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2) + if test $compErr -eq 1 + __sing_box_debug "Received error directive: aborting." + # Might as well do file completion, in case it helps + return 1 + end + + set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2) + set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2) + if test $filefilter -eq 1; or test $dirfilter -eq 1 + __sing_box_debug "File extension filtering or directory filtering not supported" + # Do full file completion instead + return 1 + end + + set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2) + set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2) + + __sing_box_debug "nospace: $nospace, nofiles: $nofiles" + + # If we want to prevent a space, or if file completion is NOT disabled, + # we need to count the number of valid completions. + # To do so, we will filter on prefix as the completions we have received + # may not already be filtered so as to allow fish to match on different + # criteria than the prefix. + if test $nospace -ne 0; or test $nofiles -eq 0 + set -l prefix (commandline -t | string escape --style=regex) + __sing_box_debug "prefix: $prefix" + + set -l completions (string match -r -- "^$prefix.*" $__sing_box_comp_results) + set --global __sing_box_comp_results $completions + __sing_box_debug "Filtered completions are: $__sing_box_comp_results" + + # Important not to quote the variable for count to work + set -l numComps (count $__sing_box_comp_results) + __sing_box_debug "numComps: $numComps" + + if test $numComps -eq 1; and test $nospace -ne 0 + # We must first split on \t to get rid of the descriptions to be + # able to check what the actual completion will be. + # We don't need descriptions anyway since there is only a single + # real completion which the shell will expand immediately. + set -l split (string split --max 1 \t $__sing_box_comp_results[1]) + + # Fish won't add a space if the completion ends with any + # of the following characters: @=/:., + set -l lastChar (string sub -s -1 -- $split) + if not string match -r -q "[@=/:.,]" -- "$lastChar" + # In other cases, to support the "nospace" directive we trick the shell + # by outputting an extra, longer completion. + __sing_box_debug "Adding second completion to perform nospace directive" + set --global __sing_box_comp_results $split[1] $split[1]. + __sing_box_debug "Completions are now: $__sing_box_comp_results" + end + end + + if test $numComps -eq 0; and test $nofiles -eq 0 + # To be consistent with bash and zsh, we only trigger file + # completion when there are no other completions + __sing_box_debug "Requesting file completion" + return 1 + end + end + + return 0 +end + +# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves +# so we can properly delete any completions provided by another script. +# Only do this if the program can be found, or else fish may print some errors; besides, +# the existing completions will only be loaded if the program can be found. +if type -q "sing-box" + # The space after the program name is essential to trigger completion for the program + # and not completion of the program name itself. + # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. + complete --do-complete "sing-box " > /dev/null 2>&1 +end + +# Remove any pre-existing completions for the program since we will be handling all of them. +complete -c sing-box -e + +# this will get called after the two calls below and clear the $__sing_box_perform_completion_once_result global +complete -c sing-box -n '__sing_box_clear_perform_completion_once_result' +# The call to __sing_box_prepare_completions will setup __sing_box_comp_results +# which provides the program's completion choices. +# If this doesn't require order preservation, we don't use the -k flag +complete -c sing-box -n 'not __sing_box_requires_order_preservation && __sing_box_prepare_completions' -f -a '$__sing_box_comp_results' +# otherwise we use the -k flag +complete -k -c sing-box -n '__sing_box_requires_order_preservation && __sing_box_prepare_completions' -f -a '$__sing_box_comp_results' diff --git a/release/completions/sing-box.zsh b/release/completions/sing-box.zsh new file mode 100644 index 0000000000..962005560a --- /dev/null +++ b/release/completions/sing-box.zsh @@ -0,0 +1,212 @@ +#compdef sing-box +compdef _sing-box sing-box + +# zsh completion for sing-box -*- shell-script -*- + +__sing-box_debug() +{ + local file="$BASH_COMP_DEBUG_FILE" + if [[ -n ${file} ]]; then + echo "$*" >> "${file}" + fi +} + +_sing-box() +{ + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + local shellCompDirectiveKeepOrder=32 + + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder + local -a completions + + __sing-box_debug "\n========= starting completion logic ==========" + __sing-box_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") + __sing-box_debug "Truncated words[*]: ${words[*]}," + + lastParam=${words[-1]} + lastChar=${lastParam[-1]} + __sing-box_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" + + # For zsh, when completing a flag with an = (e.g., sing-box -n=) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[1]} __complete ${words[2,-1]}" + if [ "${lastChar}" = "" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go completion code. + __sing-box_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __sing-box_debug "About to call: eval ${requestComp}" + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + __sing-box_debug "completion output: ${out}" + + # Extract the directive integer following a : from the last line + local lastLine + while IFS='\n' read -r line; do + lastLine=${line} + done < <(printf "%s\n" "${out[@]}") + __sing-box_debug "last line: ${lastLine}" + + if [ "${lastLine[1]}" = : ]; then + directive=${lastLine[2,-1]} + # Remove the directive including the : and the newline + local suffix + (( suffix=${#lastLine}+2)) + out=${out[1,-$suffix]} + else + # There is no directive specified. Leave $out as is. + __sing-box_debug "No directive found. Setting do default" + directive=0 + fi + + __sing-box_debug "directive: ${directive}" + __sing-box_debug "completions: ${out}" + __sing-box_debug "flagPrefix: ${flagPrefix}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + __sing-box_debug "Completion received error. Ignoring completions." + return + fi + + local activeHelpMarker="_activeHelp_ " + local endIndex=${#activeHelpMarker} + local startIndex=$((${#activeHelpMarker}+1)) + local hasActiveHelp=0 + while IFS='\n' read -r comp; do + # Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker) + if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then + __sing-box_debug "ActiveHelp found: $comp" + comp="${comp[$startIndex,-1]}" + if [ -n "$comp" ]; then + compadd -x "${comp}" + __sing-box_debug "ActiveHelp will need delimiter" + hasActiveHelp=1 + fi + + continue + fi + + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + + local tab="$(printf '\t')" + comp=${comp//$tab/:} + + __sing-box_debug "Adding completion: ${comp}" + completions+=${comp} + lastComp=$comp + fi + done < <(printf "%s\n" "${out[@]}") + + # Add a delimiter after the activeHelp statements, but only if: + # - there are completions following the activeHelp statements, or + # - file completion will be performed (so there will be choices after the activeHelp) + if [ $hasActiveHelp -eq 1 ]; then + if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then + __sing-box_debug "Adding activeHelp delimiter" + compadd -x "--" + hasActiveHelp=0 + fi + fi + + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + __sing-box_debug "Activating nospace." + noSpace="-S ''" + fi + + if [ $((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then + __sing-box_debug "Activating keep order." + keepOrder="-V" + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local filteringCmd + filteringCmd='_files' + for filter in ${completions[@]}; do + if [ ${filter[1]} != '*' ]; then + # zsh requires a glob pattern to do file filtering + filter="\*.$filter" + fi + filteringCmd+=" -g $filter" + done + filteringCmd+=" ${flagPrefix}" + + __sing-box_debug "File filtering command: $filteringCmd" + _arguments '*:filename:'"$filteringCmd" + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subdir + subdir="${completions[1]}" + if [ -n "$subdir" ]; then + __sing-box_debug "Listing directories in $subdir" + pushd "${subdir}" >/dev/null 2>&1 + else + __sing-box_debug "Listing directories in ." + fi + + local result + _arguments '*:dirname:_files -/'" ${flagPrefix}" + result=$? + if [ -n "$subdir" ]; then + popd >/dev/null 2>&1 + fi + return $result + else + __sing-box_debug "Calling _describe" + if eval _describe $keepOrder "completions" completions $flagPrefix $noSpace; then + __sing-box_debug "_describe found some completions" + + # Return the success of having called _describe + return 0 + else + __sing-box_debug "_describe did not find completions." + __sing-box_debug "Checking if we should do file completion." + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __sing-box_debug "deactivating file completion" + + # We must return an error code here to let zsh know that there were no + # completions found by _describe; this is what will trigger other + # matching algorithms to attempt to find completions. + # For example zsh can match letters in the middle of words. + return 1 + else + # Perform file completion + __sing-box_debug "Activating file completion" + + # We must return the result of this command, so it must be the + # last command, or else we must store its result to return it. + _arguments '*:filename:_files'" ${flagPrefix}" + fi + fi + fi +} + +# don't run the completion function when being source-ed or eval-ed +if [ "$funcstack[1]" = "_sing-box" ]; then + _sing-box +fi diff --git a/release/config/openwrt.conf b/release/config/openwrt.conf new file mode 100644 index 0000000000..1ce4c77d29 --- /dev/null +++ b/release/config/openwrt.conf @@ -0,0 +1,5 @@ +config sing-box 'main' + option enabled '1' + option conffile '/etc/sing-box/config.json' + option workdir '/usr/share/sing-box' + option log_stderr '1' diff --git a/release/config/openwrt.init b/release/config/openwrt.init new file mode 100644 index 0000000000..bae70e4b2a --- /dev/null +++ b/release/config/openwrt.init @@ -0,0 +1,31 @@ +#!/bin/sh /etc/rc.common + +PROG="/usr/bin/sing-box" + +start_service() { + config_load "sing-box" + + local enabled config_file working_directory + local log_stdout log_stderr + config_get_bool enabled "main" "enabled" "0" + [ "$enabled" -eq "1" ] || return 0 + + config_get config_file "main" "conffile" "/etc/sing-box/config.json" + config_get working_directory "main" "workdir" "/usr/share/sing-box" + config_get_bool log_stdout "main" "log_stdout" "1" + config_get_bool log_stderr "main" "log_stderr" "1" + + procd_open_instance + procd_swet_param command "$PROG" run -c "$conffile" -D "$workdir" + procd_set_param file "$conffile" + procd_set_param stderr "$log_stderr" + procd_set_param limits core="unlimited" + sprocd_set_param limits nofile="1000000 1000000" + procd_set_param respawn + + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "sing-box" +} \ No newline at end of file diff --git a/release/config/sing-box.initd b/release/config/sing-box.initd new file mode 100644 index 0000000000..db96e47865 --- /dev/null +++ b/release/config/sing-box.initd @@ -0,0 +1,18 @@ +#!/sbin/openrc-run + +name=$RC_SVCNAME +description="sing-box service" +supervisor="supervise-daemon" +command="/usr/bin/sing-box" +command_args="-D /var/lib/sing-box -C /etc/sing-box run" +extra_started_commands="reload" + +depend() { + after net dns +} + +reload() { + ebegin "Reloading $RC_SVCNAME" + $supervisor "$RC_SVCNAME" --signal HUP + eend $? +} \ No newline at end of file From 9415444ebdb454c6bb8ab0adf457118c90f34a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 5 Oct 2024 09:28:58 +0800 Subject: [PATCH 0035/1272] Fix base path not applied to local rule-sets --- route/rule_set.go | 2 +- route/rule_set_local.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/route/rule_set.go b/route/rule_set.go index f644fb406f..fda3d1056c 100644 --- a/route/rule_set.go +++ b/route/rule_set.go @@ -18,7 +18,7 @@ import ( func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) { switch options.Type { case C.RuleSetTypeLocal: - return NewLocalRuleSet(router, options) + return NewLocalRuleSet(ctx, router, options) case C.RuleSetTypeRemote: return NewRemoteRuleSet(ctx, router, logger, options), nil default: diff --git a/route/rule_set_local.go b/route/rule_set_local.go index 3945826708..07839c65b3 100644 --- a/route/rule_set_local.go +++ b/route/rule_set_local.go @@ -12,6 +12,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/service/filemanager" ) var _ adapter.RuleSet = (*LocalRuleSet)(nil) @@ -21,11 +22,11 @@ type LocalRuleSet struct { metadata adapter.RuleSetMetadata } -func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) { +func NewLocalRuleSet(ctx context.Context, router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) { var plainRuleSet option.PlainRuleSet switch options.Format { case C.RuleSetFormatSource, "": - content, err := os.ReadFile(options.LocalOptions.Path) + content, err := os.ReadFile(filemanager.BasePath(ctx, options.LocalOptions.Path)) if err != nil { return nil, err } @@ -35,7 +36,7 @@ func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleS } plainRuleSet = compat.Upgrade() case C.RuleSetFormatBinary: - setFile, err := os.Open(options.LocalOptions.Path) + setFile, err := os.Open(filemanager.BasePath(ctx, options.LocalOptions.Path)) if err != nil { return nil, err } From b624c2dcc7db614ca28a23c242d781291352ec11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 5 Oct 2024 18:05:58 +0800 Subject: [PATCH 0036/1272] Fix context used by DNS outbounds --- outbound/dns.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/outbound/dns.go b/outbound/dns.go index b18b901e7a..6ad4a0f8ce 100644 --- a/outbound/dns.go +++ b/outbound/dns.go @@ -120,7 +120,7 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada fastClose, cancel := common.ContextWithCancelCause(ctx) timeout := canceler.New(fastClose, cancel, C.DNSTimeout) var group task.Group - group.Append0(func(ctx context.Context) error { + group.Append0(func(_ context.Context) error { for { var message mDNS.Msg var destination M.Socksaddr @@ -185,11 +185,10 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada } func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { - ctx = adapter.WithContext(ctx, &metadata) fastClose, cancel := common.ContextWithCancelCause(ctx) timeout := canceler.New(fastClose, cancel, C.DNSTimeout) var group task.Group - group.Append0(func(ctx context.Context) error { + group.Append0(func(_ context.Context) error { for { var ( message mDNS.Msg From d55c5b5caba4484aeb8644d08765167ea2163276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 5 Oct 2024 09:48:50 +0800 Subject: [PATCH 0037/1272] documentation: Update package status --- docs/installation/package-manager.md | 12 ++++++------ docs/installation/package-manager.zh.md | 13 ++++++------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/installation/package-manager.md b/docs/installation/package-manager.md index b4140666f5..5d6b3c17bd 100644 --- a/docs/installation/package-manager.md +++ b/docs/installation/package-manager.md @@ -87,12 +87,12 @@ icon: material/package ## :material-alert: Problematic Sources -| Type | Platform | Link | Promblem(s) | -|------------|----------|-------------------------------------------------------------------------------------------|------------------------------------------------------------------| -| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification; Not actively maintained | -| Homebrew | / | [homebrew-core][brew] | Problematic build tag list modification | -| Termux | Android | [termux-packages][termux] | Problematic build tag list modification | -| FreshPorts | FreeBSD | [FreeBSD ports][ports] | Old Go (go1.20) | +| Type | Platform | Link | Promblem(s) | +|------------|----------|-------------------------------------------------------------------------------------------|-----------------------------------------| +| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification | +| Homebrew | / | [homebrew-core][brew] | Problematic build tag list modification | +| Termux | Android | [termux-packages][termux] | Problematic build tag list modification | +| FreshPorts | FreeBSD | [FreeBSD ports][ports] | Old Go (go1.20) | If you are a user of them, please report issues to them: diff --git a/docs/installation/package-manager.zh.md b/docs/installation/package-manager.zh.md index 3bc03e571c..e4ff9da359 100644 --- a/docs/installation/package-manager.zh.md +++ b/docs/installation/package-manager.zh.md @@ -85,15 +85,14 @@ icon: material/package |------------|---------|------------------------|--------------------------------------------------------------------------------------------| | FreshPorts | FreeBSD | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] | - ## :material-alert: 存在问题的源 -| 类型 | 平台 | 链接 | 原因 | -|------------|---------|-------------------------------------------------------------------------------------------|-----------------------| -| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改; 没有活跃维护 | -| Homebrew | / | [homebrew-core][brew] | 存在问题的构建标志列表修改 | -| Termux | Android | [termux-packages][termux] | 存在问题的构建标志列表修改 | -| FreshPorts | FreeBSD | [FreeBSD ports][ports] | 太旧的 Go (go1.20) | +| 类型 | 平台 | 链接 | 原因 | +|------------|---------|-------------------------------------------------------------------------------------------|-----------------| +| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改 | +| Homebrew | / | [homebrew-core][brew] | 存在问题的构建标志列表修改 | +| Termux | Android | [termux-packages][termux] | 存在问题的构建标志列表修改 | +| FreshPorts | FreeBSD | [FreeBSD ports][ports] | 太旧的 Go (go1.20) | 如果您是其用户,请向他们报告问题: From 63cc6cc76c4e91968145117d87060f5cbf537f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 5 Oct 2024 10:35:55 +0800 Subject: [PATCH 0038/1272] Fix Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4918bf25d1..527a8f02cb 100644 --- a/Makefile +++ b/Makefile @@ -176,7 +176,7 @@ release_tvos: build_tvos upload_tvos_app_store update_apple_version: go run ./cmd/internal/update_apple_version -release_apple: lib_ios update_apple_version release_ios release_macos release_tvos +release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone release_apple_beta: update_apple_version release_ios release_macos release_tvos From a06d10c3bcc26d690d12e30f86a458bdbd12f441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 5 Oct 2024 09:57:15 +0800 Subject: [PATCH 0039/1272] 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 defe07a26f..2aa661d507 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit defe07a26fed49181e6c43ebe504301452335bc9 +Subproject commit 2aa661d5079fc3ad6857e0100fdb9418771f95b5 diff --git a/clients/apple b/clients/apple index 41ed30a5fa..c223f50ffb 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 41ed30a5faa14437104f83c947ab5aae028e3c13 +Subproject commit c223f50ffb8b81d4519b43c38c8cdb96b9ac3d5c diff --git a/docs/changelog.md b/docs/changelog.md index 80ee793c3d..f5eb714cde 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +### 1.9.7 + +* Fixes and improvements + ### 1.9.6 * Fixes and improvements From 0f7154afbd7d88f501664d1d2c5516ad5b638661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 20 Aug 2024 21:33:31 +0800 Subject: [PATCH 0040/1272] Update workflow to go1.23 --- .github/workflows/debug.yml | 23 +++++++++++++++++++++-- .github/workflows/lint.yml | 2 +- .github/workflows/linux.yml | 2 +- Dockerfile | 2 +- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml index 003caf5564..0b114b3d8b 100644 --- a/.github/workflows/debug.yml +++ b/.github/workflows/debug.yml @@ -28,8 +28,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.22 - continue-on-error: true + go-version: ^1.23 - name: Run Test run: | go test -v ./... @@ -93,6 +92,26 @@ jobs: key: go121-${{ hashFiles('**/go.sum') }} - name: Run Test run: make ci_build + build_go122: + name: Debug build (Go 1.22) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + with: + fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ~1.22 + - name: Cache go module + uses: actions/cache@v4 + with: + path: | + ~/go/pkg/mod + key: go122-${{ hashFiles('**/go.sum') }} + - name: Run Test + run: make ci_build cross: strategy: matrix: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3eb31561a3..08fb6c54d6 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.22 + go-version: ^1.23 - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1e6aeff363..bd2f1023f2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.22 + go-version: ^1.23 - name: Extract signing key run: |- mkdir -p $HOME/.gnupg diff --git a/Dockerfile b/Dockerfile index db890fd439..22f2dfb289 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder +FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder LABEL maintainer="nekohasekai " COPY . /go/src/github.com/sagernet/sing-box WORKDIR /go/src/github.com/sagernet/sing-box From 4b7a83da168c5f2ae9b999580517083ef1b10f4b Mon Sep 17 00:00:00 2001 From: iosmanthus Date: Mon, 27 May 2024 19:24:41 +0800 Subject: [PATCH 0041/1272] Introduce bittorrent related protocol sniffers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduce bittorrent related protocol sniffers including, sniffers of 1. BitTorrent Protocol (TCP) 2. uTorrent Transport Protocol (UDP) Signed-off-by: iosmanthus Co-authored-by: 世界 --- common/sniff/bittorrent.go | 101 +++++++++++++++++++++++++++ common/sniff/bittorrent_test.go | 70 +++++++++++++++++++ constant/protocol.go | 11 +-- docs/configuration/route/sniff.md | 15 ++-- docs/configuration/route/sniff.zh.md | 15 ++-- route/router.go | 21 +++++- 6 files changed, 212 insertions(+), 21 deletions(-) create mode 100644 common/sniff/bittorrent.go create mode 100644 common/sniff/bittorrent_test.go diff --git a/common/sniff/bittorrent.go b/common/sniff/bittorrent.go new file mode 100644 index 0000000000..debc35ca4f --- /dev/null +++ b/common/sniff/bittorrent.go @@ -0,0 +1,101 @@ +package sniff + +import ( + "bytes" + "context" + "encoding/binary" + "io" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" +) + +const ( + trackerConnectFlag = 0 + trackerProtocolID = 0x41727101980 + trackerConnectMinSize = 16 +) + +// BitTorrent detects if the stream is a BitTorrent connection. +// For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html +func BitTorrent(_ context.Context, reader io.Reader) (*adapter.InboundContext, error) { + var first byte + err := binary.Read(reader, binary.BigEndian, &first) + if err != nil { + return nil, err + } + + if first != 19 { + return nil, os.ErrInvalid + } + + var protocol [19]byte + _, err = reader.Read(protocol[:]) + if err != nil { + return nil, err + } + if string(protocol[:]) != "BitTorrent protocol" { + return nil, os.ErrInvalid + } + + return &adapter.InboundContext{ + Protocol: C.ProtocolBitTorrent, + }, nil +} + +// UTP detects if the packet is a uTP connection packet. +// For the uTP protocol specification, see +// 1. https://www.bittorrent.org/beps/bep_0029.html +// 2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112 +func UTP(_ context.Context, packet []byte) (*adapter.InboundContext, error) { + // A valid uTP packet must be at least 20 bytes long. + if len(packet) < 20 { + return nil, os.ErrInvalid + } + + version := packet[0] & 0x0F + ty := packet[0] >> 4 + if version != 1 || ty > 4 { + return nil, os.ErrInvalid + } + + // Validate the extensions + extension := packet[1] + reader := bytes.NewReader(packet[20:]) + for extension != 0 { + err := binary.Read(reader, binary.BigEndian, &extension) + if err != nil { + return nil, err + } + + var length byte + err = binary.Read(reader, binary.BigEndian, &length) + if err != nil { + return nil, err + } + _, err = reader.Seek(int64(length), io.SeekCurrent) + if err != nil { + return nil, err + } + } + + return &adapter.InboundContext{ + Protocol: C.ProtocolBitTorrent, + }, nil +} + +// UDPTracker detects if the packet is a UDP Tracker Protocol packet. +// For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html +func UDPTracker(_ context.Context, packet []byte) (*adapter.InboundContext, error) { + if len(packet) < trackerConnectMinSize { + return nil, os.ErrInvalid + } + if binary.BigEndian.Uint64(packet[:8]) != trackerProtocolID { + return nil, os.ErrInvalid + } + if binary.BigEndian.Uint32(packet[8:12]) != trackerConnectFlag { + return nil, os.ErrInvalid + } + return &adapter.InboundContext{Protocol: C.ProtocolBitTorrent}, nil +} diff --git a/common/sniff/bittorrent_test.go b/common/sniff/bittorrent_test.go new file mode 100644 index 0000000000..6b3ab64e95 --- /dev/null +++ b/common/sniff/bittorrent_test.go @@ -0,0 +1,70 @@ +package sniff_test + +import ( + "bytes" + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffBittorrent(t *testing.T) { + t.Parallel() + + packets := []string{ + "13426974546f7272656e742070726f746f636f6c0000000000100000e21ea9569b69bab33c97851d0298bdfa89bc90922d5554313631302dea812fcd6a3563e3be40c1d1", + "13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452333030302d653369733079647675763638", + "13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452343035302d6f7a316c6e79377931716130", + } + + for _, pkt := range packets { + pkt, err := hex.DecodeString(pkt) + require.NoError(t, err) + metadata, err := sniff.BitTorrent(context.TODO(), bytes.NewReader(pkt)) + require.NoError(t, err) + require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) + } +} + +func TestSniffUTP(t *testing.T) { + t.Parallel() + + packets := []string{ + "010041a282d7ee7b583afb160004000006d8318da776968f92d666f7963f32dae23ba0d2c810d8b8209cc4939f54fde9eeaa521c2c20c9ba7f43f4fb0375f28de06643b5e3ca4685ab7ac76adca99783be72ef05ed59ef4234f5712b75b4c7c0d7bee8fe2ca20ad626ba5bb0ffcc16bf06790896f888048cf72716419a07db1a3dca4550fbcea75b53e97235168a221cf3e553dfbb723961bd719fab038d86e0ecb74747f5a2cd669de1c4b9ad375f3a492d09d98cdfad745435625401315bbba98d35d32086299801377b93495a63a9efddb8d05f5b37a5c5b1c0a25e917f12007bb5e05013ada8aff544fab8cadf61d80ddb0b60f12741e44515a109d144fd53ef845acb4b5ccf0d6fc302d7003d76df3fc3423bb0237301c9e88f900c2d392a8e0fdb36d143cf7527a93fd0a2638b746e72f6699fffcd4fd15348fce780d4caa04382fd9faf1ca0ae377ca805da7536662b84f5ee18dd3ae38fcb095a7543e55f9069ae92c8cf54ae44e97b558d35e2545c66601ed2149cbc32bd6df199a2be7cf0da8b2ff137e0d23e776bc87248425013876d3a3cc31a83b424b752bd0346437f24b532978005d8f5b1b0be1a37a2489c32a18a9ad3118e3f9d30eb299bffae18e1f0677c2a5c185e62519093fe6bc2b7339299ea50a587989f726ca6443a75dd5bb936f6367c6355d80fae53ff529d740b2e5576e3eefdf1fdbfc69c3c8d8ac750512635de63e054bee1d3b689bc1b2bc3d2601e42a00b5c89066d173d4ae7ffedfd2274e5cf6d868fbe640aedb69b8246142f00b32d459974287537ddd5373460dcbc92f5cfdd7a3ed6020822ae922d947893752ca1983d0d32977374c384ac8f5ab566859019b7351526b9f13e932037a55bb052d9deb3b3c23317e0784fdc51a64f2159bfea3b069cf5caf02ee2c3c1a6b6b427bb16165713e8802d95b5c8ed77953690e994bd38c9ae113fedaf6ee7fc2b96c032ceafc2a530ad0422e84546b9c6ad8ef6ea02fa508abddd1805c38a7b42e9b7c971b1b636865ebec06ed754bb404cd6b4e6cc8cb77bd4a0c43410d5cd5ef8fe853a66d49b3b9e06cb141236cdbfdd5761601dc54d1250b86c660e0f898fe62526fdd9acf0eab60a3bbbb2151970461f28f10b31689594bea646c4b03ee197d63bdef4e5a7c22716b3bb9494a83b78ecd81b338b80ac6c09c43485b1b09ba41c74343832c78f0520c1d659ac9eb1502094141e82fb9e5e620970ebc0655514c43c294a7714cbf9a499d277daf089f556398a01589a77494bec8bfb60a108f3813b55368672b88c1af40f6b3c8b513f7c70c3e0efce85228b8b9ec67ba0393f9f7305024d8e2da6a26cf85613d14f249170ce1000089df4c9c260df7f8292aa2ecb5d5bac97656d59aa248caedea2d198e51ce87baece338716d114b458de02d65c9ff808ca5b5b73723b4d1e962d9ac2d98176544dc9984cf8554d07820ef3dd0861cfe57b478328046380de589adad94ee44743ffac73bb7361feca5d56f07cf8ce75080e261282ae30350d7882679b15cab9e7e53ddf93310b33f7390ae5d318bb53f387e6af5d0ef4f947fc9cb8e7e38b52c7f8d772ece6156b38d88796ea19df02c53723b44df7c76315a0de9462f27287e682d2b4cda1a68fe00d7e48c51ee981be44e1ca940fb5190c12655edb4a83c3a4f33e48a015692df4f0b3d61656e362aca657b5ae8c12db5a0db3db1e45135ee918b66918f40e53c4f83e9da0cddfe63f736ae751ab3837a30ae3220d8e8e311487093a7b90c7e7e40dd54ca750e19452f9193aa892aa6a6229ab493dadae988b1724f7898ee69c36d3eb7364c4adbeca811cfe2065873e78c2b6dfdf1595f7a7831c07e03cda82e4f86f76438dfb2b07c13638ce7b509cfa71b88b5102b39a203b423202088e1c2103319cb32c13c1e546ff8612fa194c95a7808ab767c265a1bd5fa0efed5c8ec1701876a00ec8", + "01001ecb68176f215d04326300100000dbcf30292d14b54e9ee2d115ee5b8ebc7fad3e882d4fcdd0c14c6b917c11cb4c6a9f410b52a33ae97c2ac77c7a2b122b8955e09af3c5c595f1b2e79ca57cfe44c44e069610773b9bc9ba223d7f6b383e3adddd03fb88a8476028e30979c2ef321ffc97c5c132bcf9ac5b410bbb5ec6cefca3c7209202a14c5ae922b6b157b0a80249d13ffe5b996af0bc8e54ba576d148372494303e7ead0602b05b9c8fc97d48508a028a04d63a1fd28b0edfcd5c51715f63188b53eefede98a76912dca98518551a8856567307a56a702cbfcc115ea0c755b418bc2c7b57721239b82f09fb24328a4b0ce0f109bcb2a64e04b8aadb1f8487585425acdf8fc4ec8ea93cfcec5ac098bb29d42ddef6e46b03f34a5de28316726699b7cb5195c33e5c48abe87d591d63f9991c84c30819d186d6e0e95fd83c8dff07aa669c4430989bcaccfeacb9bcadbdb4d8f1964dbeb9687745656edd30b21c66cc0a1d742a78717d134a19a7f02d285a4973b1a198c00cfdff4676608dc4f3e817e3463c3b4e2c80d3e8d4fbac541a58a2fb7ad6939f607f8144eff6c8b0adc28ee5609ea158987519892fb", + "21001ecb6817f2805d044fd700100000dbd03029", + "410277ef0b1fb1f60000000000040000c233000000080000000000000000", + } + + for _, pkt := range packets { + pkt, err := hex.DecodeString(pkt) + require.NoError(t, err) + + metadata, err := sniff.UTP(context.TODO(), pkt) + require.NoError(t, err) + require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) + } +} + +func TestSniffUDPTracker(t *testing.T) { + t.Parallel() + + connectPackets := []string{ + "00000417271019800000000078e90560", + "00000417271019800000000022c5d64d", + "000004172710198000000000b3863541", + } + + for _, pkt := range connectPackets { + pkt, err := hex.DecodeString(pkt) + require.NoError(t, err) + + metadata, err := sniff.UDPTracker(context.TODO(), pkt) + require.NoError(t, err) + require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) + } +} diff --git a/constant/protocol.go b/constant/protocol.go index 810c79ec99..2b7a9e0f50 100644 --- a/constant/protocol.go +++ b/constant/protocol.go @@ -1,9 +1,10 @@ package constant const ( - ProtocolTLS = "tls" - ProtocolHTTP = "http" - ProtocolQUIC = "quic" - ProtocolDNS = "dns" - ProtocolSTUN = "stun" + ProtocolTLS = "tls" + ProtocolHTTP = "http" + ProtocolQUIC = "quic" + ProtocolDNS = "dns" + ProtocolSTUN = "stun" + ProtocolBitTorrent = "bittorrent" ) diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index 2ba2c25135..7a3de02bc6 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -2,10 +2,11 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c #### Supported Protocols -| Network | Protocol | Domain Name | -|:-------:|:--------:|:-----------:| -| TCP | HTTP | Host | -| TCP | TLS | Server Name | -| UDP | QUIC | Server Name | -| UDP | STUN | / | -| TCP/UDP | DNS | / | \ No newline at end of file +| Network | Protocol | Domain Name | +|:-------:|:-----------:|:-----------:| +| TCP | HTTP | Host | +| TCP | TLS | Server Name | +| UDP | QUIC | Server Name | +| UDP | STUN | / | +| TCP/UDP | DNS | / | +| TCP/UDP | BitTorrent | / | \ No newline at end of file diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index c3cdcc4ee3..553c6ed76b 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -2,10 +2,11 @@ #### 支持的协议 -| 网络 | 协议 | 域名 | -|:-------:|:----:|:-----------:| -| TCP | HTTP | Host | -| TCP | TLS | Server Name | -| UDP | QUIC | Server Name | -| UDP | STUN | / | -| TCP/UDP | DNS | / | \ No newline at end of file +| 网络 | 协议 | 域名 | +|:-------:|:-----------:|:-----------:| +| TCP | HTTP | Host | +| TCP | TLS | Server Name | +| UDP | QUIC | Server Name | +| UDP | STUN | / | +| TCP/UDP | DNS | / | +| TCP/UDP | BitTorrent | / | \ No newline at end of file diff --git a/route/router.go b/route/router.go index 5d89b11853..ad4ac3da20 100644 --- a/route/router.go +++ b/route/router.go @@ -834,7 +834,16 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad if metadata.InboundOptions.SniffEnabled && !sniff.Skip(metadata) { buffer := buf.NewPacket() - sniffMetadata, err := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost) + sniffMetadata, err := sniff.PeekStream( + ctx, + conn, + buffer, + time.Duration(metadata.InboundOptions.SniffTimeout), + sniff.StreamDomainNameQuery, + sniff.TLSClientHello, + sniff.HTTPHost, + sniff.BitTorrent, + ) if sniffMetadata != nil { metadata.Protocol = sniffMetadata.Protocol metadata.Domain = sniffMetadata.Domain @@ -983,7 +992,15 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m metadata.Destination = destination } if metadata.InboundOptions.SniffEnabled { - sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage) + sniffMetadata, _ := sniff.PeekPacket( + ctx, + buffer.Bytes(), + sniff.DomainNameQuery, + sniff.QUICClientHello, + sniff.STUNMessage, + sniff.UTP, + sniff.UDPTracker, + ) if sniffMetadata != nil { metadata.Protocol = sniffMetadata.Protocol metadata.Domain = sniffMetadata.Domain From 369bc7cea3dc91cc3c680575211769cd23ee3909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 7 Jul 2024 15:45:50 +0800 Subject: [PATCH 0042/1272] Add DTLS sniffer --- common/sniff/dtls.go | 31 ++++++++++++++++++++++++++++ common/sniff/dtls_test.go | 30 +++++++++++++++++++++++++++ constant/protocol.go | 1 + docs/configuration/route/sniff.md | 26 ++++++++++++++++------- docs/configuration/route/sniff.zh.md | 26 ++++++++++++++++------- route/router.go | 1 + 6 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 common/sniff/dtls.go create mode 100644 common/sniff/dtls_test.go diff --git a/common/sniff/dtls.go b/common/sniff/dtls.go new file mode 100644 index 0000000000..6469b09709 --- /dev/null +++ b/common/sniff/dtls.go @@ -0,0 +1,31 @@ +package sniff + +import ( + "context" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" +) + +func DTLSRecord(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { + const fixedHeaderSize = 13 + if len(packet) < fixedHeaderSize { + return nil, os.ErrInvalid + } + contentType := packet[0] + switch contentType { + case 20, 21, 22, 23, 25: + default: + return nil, os.ErrInvalid + } + versionMajor := packet[1] + if versionMajor != 0xfe { + return nil, os.ErrInvalid + } + versionMinor := packet[2] + if versionMinor != 0xff && versionMinor != 0xfd { + return nil, os.ErrInvalid + } + return &adapter.InboundContext{Protocol: C.ProtocolDTLS}, nil +} diff --git a/common/sniff/dtls_test.go b/common/sniff/dtls_test.go new file mode 100644 index 0000000000..45f7712642 --- /dev/null +++ b/common/sniff/dtls_test.go @@ -0,0 +1,30 @@ +package sniff_test + +import ( + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffDTLSClientHello(t *testing.T) { + t.Parallel() + packet, err := hex.DecodeString("16fefd0000000000000000007e010000720000000000000072fefd668a43523798e064bd806d0c87660de9c611a59bbdfc3892c4e072d94f2cafc40000000cc02bc02fc00ac014c02cc0300100003c000d0010000e0403050306030401050106010807ff01000100000a00080006001d00170018000b00020100000e000900060008000700010000170000") + require.NoError(t, err) + metadata, err := sniff.DTLSRecord(context.Background(), packet) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolDTLS) +} + +func TestSniffDTLSClientApplicationData(t *testing.T) { + t.Parallel() + packet, err := hex.DecodeString("17fefd000100000000000100440001000000000001a4f682b77ecadd10f3f3a2f78d90566212366ff8209fd77314f5a49352f9bb9bd12f4daba0b4736ae29e46b9714d3b424b3e6d0234736619b5aa0d3f") + require.NoError(t, err) + metadata, err := sniff.DTLSRecord(context.Background(), packet) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolDTLS) +} diff --git a/constant/protocol.go b/constant/protocol.go index 2b7a9e0f50..915e64ca19 100644 --- a/constant/protocol.go +++ b/constant/protocol.go @@ -7,4 +7,5 @@ const ( ProtocolDNS = "dns" ProtocolSTUN = "stun" ProtocolBitTorrent = "bittorrent" + ProtocolDTLS = "dtls" ) diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index 7a3de02bc6..cc9a5c130d 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -1,12 +1,22 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: BitTorrent support + :material-plus: DTLS support + If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed. #### Supported Protocols -| Network | Protocol | Domain Name | -|:-------:|:-----------:|:-----------:| -| TCP | HTTP | Host | -| TCP | TLS | Server Name | -| UDP | QUIC | Server Name | -| UDP | STUN | / | -| TCP/UDP | DNS | / | -| TCP/UDP | BitTorrent | / | \ No newline at end of file +| Network | Protocol | Domain Name | +|:-------:|:------------:|:-----------:| +| TCP | `http` | Host | +| TCP | `tls` | Server Name | +| UDP | `quic` | Server Name | +| UDP | `stun` | / | +| TCP/UDP | `dns` | / | +| TCP/UDP | `bittorrent` | / | +| UDP | `dtls` | / | diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index 553c6ed76b..523ed44b90 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -1,12 +1,22 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: BitTorrent 支持 + :material-plus: DTLS 支持 + 如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。 #### 支持的协议 -| 网络 | 协议 | 域名 | -|:-------:|:-----------:|:-----------:| -| TCP | HTTP | Host | -| TCP | TLS | Server Name | -| UDP | QUIC | Server Name | -| UDP | STUN | / | -| TCP/UDP | DNS | / | -| TCP/UDP | BitTorrent | / | \ No newline at end of file +| 网络 | 协议 | 域名 | +|:-------:|:------------:|:-----------:| +| TCP | `http` | Host | +| TCP | `tls` | Server Name | +| UDP | `quic` | Server Name | +| UDP | `stun` | / | +| TCP/UDP | `dns` | / | +| TCP/UDP | `bittorrent` | / | +| UDP | `dtls` | / | diff --git a/route/router.go b/route/router.go index ad4ac3da20..a8698264cb 100644 --- a/route/router.go +++ b/route/router.go @@ -1000,6 +1000,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m sniff.STUNMessage, sniff.UTP, sniff.UDPTracker, + sniff.DTLSRecord, ) if sniffMetadata != nil { metadata.Protocol = sniffMetadata.Protocol From d44e7d98342d24ee25b1f0d70a81a046aa4c9d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 7 Jun 2024 16:08:07 +0800 Subject: [PATCH 0043/1272] Drop support for go1.18 and go1.19 --- .github/workflows/debug.yml | 20 -------------------- Makefile | 9 ++------- debug_go119.go => debug.go | 2 -- debug_go118.go | 36 ------------------------------------ docs/deprecated.md | 6 ++++++ docs/deprecated.zh.md | 6 ++++++ 6 files changed, 14 insertions(+), 65 deletions(-) rename debug_go119.go => debug.go (97%) delete mode 100644 debug_go118.go diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml index 0b114b3d8b..2bca96e050 100644 --- a/.github/workflows/debug.yml +++ b/.github/workflows/debug.yml @@ -32,26 +32,6 @@ jobs: - name: Run Test run: | go test -v ./... - build_go118: - name: Debug build (Go 1.18) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ~1.18 - - name: Cache go module - uses: actions/cache@v4 - with: - path: | - ~/go/pkg/mod - key: go118-${{ hashFiles('**/go.sum') }} - - name: Run Test - run: make ci_build_go118 build_go120: name: Debug build (Go 1.20) runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index 527a8f02cb..c945776319 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api -TAGS_GO120 = with_quic,with_utls +TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls TAGS_GO121 = with_ech TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121) TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server @@ -20,13 +19,9 @@ PREFIX ?= $(shell go env GOPATH) build: go build $(MAIN_PARAMS) $(MAIN) -ci_build_go118: - go build $(PARAMS) $(MAIN) - go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN) - ci_build_go120: go build $(PARAMS) $(MAIN) - go build $(PARAMS) -tags "$(TAGS_GO118),$(TAGS_GO120)" $(MAIN) + go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN) ci_build: go build $(PARAMS) $(MAIN) diff --git a/debug_go119.go b/debug.go similarity index 97% rename from debug_go119.go rename to debug.go index cf522afb72..2fa962d642 100644 --- a/debug_go119.go +++ b/debug.go @@ -1,5 +1,3 @@ -//go:build go1.19 - package box import ( diff --git a/debug_go118.go b/debug_go118.go deleted file mode 100644 index bb132efb98..0000000000 --- a/debug_go118.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build !go1.19 - -package box - -import ( - "runtime/debug" - - "github.com/sagernet/sing-box/common/conntrack" - "github.com/sagernet/sing-box/option" -) - -func applyDebugOptions(options option.DebugOptions) { - applyDebugListenOption(options) - if options.GCPercent != nil { - debug.SetGCPercent(*options.GCPercent) - } - if options.MaxStack != nil { - debug.SetMaxStack(*options.MaxStack) - } - if options.MaxThreads != nil { - debug.SetMaxThreads(*options.MaxThreads) - } - if options.PanicOnFault != nil { - debug.SetPanicOnFault(*options.PanicOnFault) - } - if options.TraceBack != "" { - debug.SetTraceback(options.TraceBack) - } - if options.MemoryLimit != 0 { - // debug.SetMemoryLimit(int64(options.MemoryLimit)) - conntrack.MemoryLimit = uint64(options.MemoryLimit) - } - if options.OOMKiller != nil { - conntrack.KillerEnabled = *options.OOMKiller - } -} diff --git a/docs/deprecated.md b/docs/deprecated.md index 270aaaea72..439bf7e8f5 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -4,6 +4,12 @@ icon: material/delete-alert # Deprecated Feature List +## 1.10.0 + +#### Drop support for go1.18 and go1.19 + +Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile. + ## 1.8.0 #### Cache file and related features in Clash API diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 69ec4bdd7f..76e7e7684c 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -4,6 +4,12 @@ icon: material/delete-alert # 废弃功能列表 +## 1.10.0 + +#### 移除对 go1.18 和 go1.19 的支持 + +由于维护困难,sing-box 1.10.0 要求至少 Go 1.20 才能编译。 + ## 1.8.0 #### Clash API 中的 Cache file 及相关功能 From b1d75812c545e21691e17c78ee8de7ae8ae79a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 11 Jun 2024 21:16:33 +0800 Subject: [PATCH 0044/1272] platform: Prepare connections list --- box.go | 1 + constant/proxy.go | 8 + experimental/clashapi/connections.go | 5 +- experimental/clashapi/server.go | 48 +--- .../clashapi/trafficontrol/manager.go | 91 ++++-- .../clashapi/trafficontrol/tracker.go | 249 +++++++++------- experimental/libbox/command.go | 2 + experimental/libbox/command_client.go | 8 + .../libbox/command_close_connection.go | 53 ++++ experimental/libbox/command_connections.go | 269 ++++++++++++++++++ experimental/libbox/command_group.go | 60 ++-- experimental/libbox/command_server.go | 6 + experimental/libbox/command_status.go | 2 +- experimental/libbox/iterator.go | 4 + experimental/libbox/service.go | 54 ++-- experimental/libbox/setup.go | 6 + inbound/builder.go | 34 +-- log/format.go | 6 +- 18 files changed, 652 insertions(+), 254 deletions(-) create mode 100644 experimental/libbox/command_close_connection.go create mode 100644 experimental/libbox/command_connections.go diff --git a/box.go b/box.go index 70235fd344..3c514cfee2 100644 --- a/box.go +++ b/box.go @@ -111,6 +111,7 @@ func New(options Options) (*Box, error) { ctx, router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), + tag, inboundOptions, options.PlatformInterface, ) diff --git a/constant/proxy.go b/constant/proxy.go index 1e9baee298..3197de6052 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -32,6 +32,12 @@ const ( func ProxyDisplayName(proxyType string) string { switch proxyType { + case TypeTun: + return "TUN" + case TypeRedirect: + return "Redirect" + case TypeTProxy: + return "TProxy" case TypeDirect: return "Direct" case TypeBlock: @@ -42,6 +48,8 @@ func ProxyDisplayName(proxyType string) string { return "SOCKS" case TypeHTTP: return "HTTP" + case TypeMixed: + return "Mixed" case TypeShadowsocks: return "Shadowsocks" case TypeVMess: diff --git a/experimental/clashapi/connections.go b/experimental/clashapi/connections.go index c9471207e8..999d589828 100644 --- a/experimental/clashapi/connections.go +++ b/experimental/clashapi/connections.go @@ -14,6 +14,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" + "github.com/gofrs/uuid/v5" ) func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler { @@ -76,10 +77,10 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW func closeConnection(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, "id") + id := uuid.FromStringOrNil(chi.URLParam(r, "id")) snapshot := trafficManager.Snapshot() for _, c := range snapshot.Connections { - if id == c.ID() { + if id == c.Metadata().ID { c.Close() break } diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 375045ac62..5ace3351e8 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -19,7 +19,6 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" @@ -218,58 +217,15 @@ func (s *Server) TrafficManager() *trafficontrol.Manager { } func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) { - tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule) + tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule) return tracker, tracker } func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) { - tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule) + tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.router, matchedRule) return tracker, tracker } -func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata { - var inbound string - if metadata.Inbound != "" { - inbound = metadata.InboundType + "/" + metadata.Inbound - } else { - inbound = metadata.InboundType - } - var domain string - if metadata.Domain != "" { - domain = metadata.Domain - } else { - domain = metadata.Destination.Fqdn - } - var processPath string - if metadata.ProcessInfo != nil { - if metadata.ProcessInfo.ProcessPath != "" { - processPath = metadata.ProcessInfo.ProcessPath - } else if metadata.ProcessInfo.PackageName != "" { - processPath = metadata.ProcessInfo.PackageName - } - if processPath == "" { - if metadata.ProcessInfo.UserId != -1 { - processPath = F.ToString(metadata.ProcessInfo.UserId) - } - } else if metadata.ProcessInfo.User != "" { - processPath = F.ToString(processPath, " (", metadata.ProcessInfo.User, ")") - } else if metadata.ProcessInfo.UserId != -1 { - processPath = F.ToString(processPath, " (", metadata.ProcessInfo.UserId, ")") - } - } - return trafficontrol.Metadata{ - NetWork: metadata.Network, - Type: inbound, - SrcIP: metadata.Source.Addr, - DstIP: metadata.Destination.Addr, - SrcPort: F.ToString(metadata.Source.Port), - DstPort: F.ToString(metadata.Destination.Port), - Host: domain, - DNSMode: "normal", - ProcessPath: processPath, - } -} - func authentication(serverSecret string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index eac7aee4d8..9b22f1e3d9 100644 --- a/experimental/clashapi/trafficontrol/manager.go +++ b/experimental/clashapi/trafficontrol/manager.go @@ -2,10 +2,17 @@ package trafficontrol import ( "runtime" + "sync" "time" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/clashapi/compatible" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/atomic" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/x/list" + + "github.com/gofrs/uuid/v5" ) type Manager struct { @@ -16,9 +23,11 @@ type Manager struct { uploadTotal atomic.Int64 downloadTotal atomic.Int64 - connections compatible.Map[string, tracker] - ticker *time.Ticker - done chan struct{} + connections compatible.Map[uuid.UUID, Tracker] + closedConnectionsAccess sync.Mutex + closedConnections list.List[TrackerMetadata] + ticker *time.Ticker + done chan struct{} // process *process.Process memory uint64 } @@ -33,12 +42,22 @@ func NewManager() *Manager { return manager } -func (m *Manager) Join(c tracker) { - m.connections.Store(c.ID(), c) +func (m *Manager) Join(c Tracker) { + m.connections.Store(c.Metadata().ID, c) } -func (m *Manager) Leave(c tracker) { - m.connections.Delete(c.ID()) +func (m *Manager) Leave(c Tracker) { + metadata := c.Metadata() + _, loaded := m.connections.LoadAndDelete(metadata.ID) + 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) + } } func (m *Manager) PushUploaded(size int64) { @@ -59,14 +78,39 @@ func (m *Manager) Total() (up int64, down int64) { return m.uploadTotal.Load(), m.downloadTotal.Load() } -func (m *Manager) Connections() int { +func (m *Manager) ConnectionsLen() int { return m.connections.Len() } +func (m *Manager) Connections() []TrackerMetadata { + var connections []TrackerMetadata + m.connections.Range(func(_ uuid.UUID, value Tracker) bool { + connections = append(connections, value.Metadata()) + return true + }) + return connections +} + +func (m *Manager) ClosedConnections() []TrackerMetadata { + m.closedConnectionsAccess.Lock() + defer m.closedConnectionsAccess.Unlock() + return m.closedConnections.Array() +} + +func (m *Manager) Connection(id uuid.UUID) Tracker { + connection, loaded := m.connections.Load(id) + if !loaded { + return nil + } + return connection +} + func (m *Manager) Snapshot() *Snapshot { - var connections []tracker - m.connections.Range(func(_ string, value tracker) bool { - connections = append(connections, value) + var connections []Tracker + m.connections.Range(func(_ uuid.UUID, value Tracker) bool { + if value.Metadata().OutboundType != C.TypeDNS { + connections = append(connections, value) + } return true }) @@ -75,10 +119,10 @@ func (m *Manager) Snapshot() *Snapshot { m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased return &Snapshot{ - UploadTotal: m.uploadTotal.Load(), - DownloadTotal: m.downloadTotal.Load(), - Connections: connections, - Memory: m.memory, + Upload: m.uploadTotal.Load(), + Download: m.downloadTotal.Load(), + Connections: connections, + Memory: m.memory, } } @@ -114,8 +158,17 @@ func (m *Manager) Close() error { } type Snapshot struct { - DownloadTotal int64 `json:"downloadTotal"` - UploadTotal int64 `json:"uploadTotal"` - Connections []tracker `json:"connections"` - Memory uint64 `json:"memory"` + Download int64 + Upload int64 + Connections []Tracker + Memory uint64 +} + +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() }), + "memory": s.Memory, + }) } diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index 4e635d1257..73c28e69bd 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -2,97 +2,135 @@ package trafficontrol import ( "net" - "net/netip" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" + F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" N "github.com/sagernet/sing/common/network" "github.com/gofrs/uuid/v5" ) -type Metadata struct { - NetWork string `json:"network"` - Type string `json:"type"` - SrcIP netip.Addr `json:"sourceIP"` - DstIP netip.Addr `json:"destinationIP"` - SrcPort string `json:"sourcePort"` - DstPort string `json:"destinationPort"` - Host string `json:"host"` - DNSMode string `json:"dnsMode"` - ProcessPath string `json:"processPath"` +type TrackerMetadata struct { + ID uuid.UUID + Metadata adapter.InboundContext + CreatedAt time.Time + ClosedAt time.Time + Upload *atomic.Int64 + Download *atomic.Int64 + Chain []string + Rule adapter.Rule + Outbound string + OutboundType string } -type tracker interface { - ID() string - Close() error - Leave() -} - -type trackerInfo struct { - UUID uuid.UUID `json:"id"` - Metadata Metadata `json:"metadata"` - UploadTotal *atomic.Int64 `json:"upload"` - DownloadTotal *atomic.Int64 `json:"download"` - Start time.Time `json:"start"` - Chain []string `json:"chains"` - Rule string `json:"rule"` - RulePayload string `json:"rulePayload"` -} - -func (t trackerInfo) MarshalJSON() ([]byte, error) { +func (t TrackerMetadata) MarshalJSON() ([]byte, error) { + var inbound string + if t.Metadata.Inbound != "" { + inbound = t.Metadata.InboundType + "/" + t.Metadata.Inbound + } else { + inbound = t.Metadata.InboundType + } + var domain string + if t.Metadata.Domain != "" { + domain = t.Metadata.Domain + } else { + domain = t.Metadata.Destination.Fqdn + } + var processPath string + 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 + } + 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.UserId != -1 { + processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserId, ")") + } + } + var rule string + if t.Rule != nil { + rule = F.ToString(t.Rule, " => ", t.Rule.Outbound()) + } else { + rule = "final" + } return json.Marshal(map[string]any{ - "id": t.UUID.String(), - "metadata": t.Metadata, - "upload": t.UploadTotal.Load(), - "download": t.DownloadTotal.Load(), - "start": t.Start, + "id": t.ID, + "metadata": map[string]any{ + "network": t.Metadata.Network, + "type": inbound, + "sourceIP": t.Metadata.Source.Addr, + "destinationIP": t.Metadata.Destination.Addr, + "sourcePort": F.ToString(t.Metadata.Source.Port), + "destinationPort": F.ToString(t.Metadata.Destination.Port), + "host": domain, + "dnsMode": "normal", + "processPath": processPath, + }, + "upload": t.Upload.Load(), + "download": t.Download.Load(), + "start": t.CreatedAt, "chains": t.Chain, - "rule": t.Rule, - "rulePayload": t.RulePayload, + "rule": rule, + "rulePayload": "", }) } -type tcpTracker struct { - N.ExtendedConn `json:"-"` - *trackerInfo - manager *Manager +type Tracker interface { + adapter.Tracker + Metadata() TrackerMetadata + Close() error +} + +type TCPConn struct { + N.ExtendedConn + metadata TrackerMetadata + manager *Manager } -func (tt *tcpTracker) ID() string { - return tt.UUID.String() +func (tt *TCPConn) Metadata() TrackerMetadata { + return tt.metadata } -func (tt *tcpTracker) Close() error { +func (tt *TCPConn) Close() error { tt.manager.Leave(tt) return tt.ExtendedConn.Close() } -func (tt *tcpTracker) Leave() { +func (tt *TCPConn) Leave() { tt.manager.Leave(tt) } -func (tt *tcpTracker) Upstream() any { +func (tt *TCPConn) Upstream() any { return tt.ExtendedConn } -func (tt *tcpTracker) ReaderReplaceable() bool { +func (tt *TCPConn) ReaderReplaceable() bool { return true } -func (tt *tcpTracker) WriterReplaceable() bool { +func (tt *TCPConn) WriterReplaceable() bool { return true } -func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *tcpTracker { - uuid, _ := uuid.NewV4() - - var chain []string - var next string +func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *TCPConn { + id, _ := uuid.NewV4() + var ( + chain []string + next string + outbound string + outboundType string + ) if rule == nil { if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { next = defaultOutbound.Tag() @@ -106,17 +144,17 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad if !loaded { break } + outbound = detour.Tag() + outboundType = detour.Type() group, isGroup := detour.(adapter.OutboundGroup) if !isGroup { break } next = group.Now() } - upload := new(atomic.Int64) download := new(atomic.Int64) - - t := &tcpTracker{ + tracker := &TCPConn{ ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) { upload.Add(n) manager.PushUploaded(n) @@ -124,64 +162,62 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad download.Add(n) manager.PushDownloaded(n) }}), - manager: manager, - trackerInfo: &trackerInfo{ - UUID: uuid, - Start: time.Now(), - Metadata: metadata, - Chain: common.Reverse(chain), - Rule: "", - UploadTotal: upload, - DownloadTotal: download, + metadata: TrackerMetadata{ + ID: id, + Metadata: metadata, + CreatedAt: time.Now(), + Upload: upload, + Download: download, + Chain: common.Reverse(chain), + Rule: rule, + Outbound: outbound, + OutboundType: outboundType, }, + manager: manager, } - - if rule != nil { - t.trackerInfo.Rule = rule.String() + " => " + rule.Outbound() - } else { - t.trackerInfo.Rule = "final" - } - - manager.Join(t) - return t + manager.Join(tracker) + return tracker } -type udpTracker struct { +type UDPConn struct { N.PacketConn `json:"-"` - *trackerInfo - manager *Manager + metadata TrackerMetadata + manager *Manager } -func (ut *udpTracker) ID() string { - return ut.UUID.String() +func (ut *UDPConn) Metadata() TrackerMetadata { + return ut.metadata } -func (ut *udpTracker) Close() error { +func (ut *UDPConn) Close() error { ut.manager.Leave(ut) return ut.PacketConn.Close() } -func (ut *udpTracker) Leave() { +func (ut *UDPConn) Leave() { ut.manager.Leave(ut) } -func (ut *udpTracker) Upstream() any { +func (ut *UDPConn) Upstream() any { return ut.PacketConn } -func (ut *udpTracker) ReaderReplaceable() bool { +func (ut *UDPConn) ReaderReplaceable() bool { return true } -func (ut *udpTracker) WriterReplaceable() bool { +func (ut *UDPConn) WriterReplaceable() bool { return true } -func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *udpTracker { - uuid, _ := uuid.NewV4() - - var chain []string - var next string +func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *UDPConn { + id, _ := uuid.NewV4() + var ( + chain []string + next string + outbound string + outboundType string + ) if rule == nil { if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil { next = defaultOutbound.Tag() @@ -195,17 +231,17 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route if !loaded { break } + outbound = detour.Tag() + outboundType = detour.Type() group, isGroup := detour.(adapter.OutboundGroup) if !isGroup { break } next = group.Now() } - upload := new(atomic.Int64) download := new(atomic.Int64) - - ut := &udpTracker{ + trackerConn := &UDPConn{ PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) { upload.Add(n) manager.PushUploaded(n) @@ -213,24 +249,19 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route download.Add(n) manager.PushDownloaded(n) }}), - manager: manager, - trackerInfo: &trackerInfo{ - UUID: uuid, - Start: time.Now(), - Metadata: metadata, - Chain: common.Reverse(chain), - Rule: "", - UploadTotal: upload, - DownloadTotal: download, + metadata: TrackerMetadata{ + ID: id, + Metadata: metadata, + CreatedAt: time.Now(), + Upload: upload, + Download: download, + Chain: common.Reverse(chain), + Rule: rule, + Outbound: outbound, + OutboundType: outboundType, }, + manager: manager, } - - if rule != nil { - ut.trackerInfo.Rule = rule.String() + " => " + rule.Outbound() - } else { - ut.trackerInfo.Rule = "final" - } - - manager.Join(ut) - return ut + manager.Join(trackerConn) + return trackerConn } diff --git a/experimental/libbox/command.go b/experimental/libbox/command.go index 7915419d64..f9aca13ff9 100644 --- a/experimental/libbox/command.go +++ b/experimental/libbox/command.go @@ -14,4 +14,6 @@ const ( CommandSetClashMode CommandGetSystemProxyStatus CommandSetSystemProxyEnabled + CommandConnections + CommandCloseConnection ) diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index f3c9ad2a19..199dce0d18 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -31,6 +31,7 @@ type CommandClientHandler interface { WriteGroups(message OutboundGroupIterator) InitializeClashMode(modeList StringIterator, currentMode string) UpdateClashMode(newMode string) + WriteConnections(message *Connections) } func NewStandaloneCommandClient() *CommandClient { @@ -116,6 +117,13 @@ func (c *CommandClient) Connect() error { 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 } diff --git a/experimental/libbox/command_close_connection.go b/experimental/libbox/command_close_connection.go new file mode 100644 index 0000000000..62f5dc8419 --- /dev/null +++ b/experimental/libbox/command_close_connection.go @@ -0,0 +1,53 @@ +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/gofrs/uuid/v5" +) + +func (c *CommandClient) CloseConnection(connId string) error { + conn, err := c.directConnect() + if err != nil { + return err + } + defer conn.Close() + writer := bufio.NewWriter(conn) + err = binary.WriteData(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 := binary.ReadData(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")) + } + clashServer := service.instance.Router().ClashServer() + if clashServer == nil { + return writeError(conn, E.New("Clash API disabled")) + } + targetConn := 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 new file mode 100644 index 0000000000..9aaa995acf --- /dev/null +++ b/experimental/libbox/command_connections.go @@ -0,0 +1,269 @@ +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/gofrs/uuid/v5" +) + +func (c *CommandClient) handleConnectionsConn(conn net.Conn) { + defer conn.Close() + reader := bufio.NewReader(conn) + var connections Connections + for { + rawConnections = nil + err := binary.ReadData(reader, binary.BigEndian, &connections.connections) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + 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 { + clashServer := service.instance.Router().ClashServer() + if clashServer == nil { + return E.New("Clash API disabled") + } + trafficManager = 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 = binary.WriteData(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 { + connections []Connection + filteredConnections []Connection + outConnections *[]Connection +} + +func (c *Connections) FilterState(state int32) { + c.filteredConnections = c.filteredConnections[:0] + switch state { + case ConnectionStateAll: + c.filteredConnections = append(c.filteredConnections, c.connections...) + case ConnectionStateActive: + for _, connection := range c.connections { + if connection.ClosedAt == 0 { + c.filteredConnections = append(c.filteredConnections, connection) + } + } + case ConnectionStateClosed: + for _, connection := range c.connections { + if connection.ClosedAt != 0 { + c.filteredConnections = append(c.filteredConnections, connection) + } + } + } +} + +func (c *Connections) SortByDate() { + slices.SortStableFunc(c.filteredConnections, 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.filteredConnections, 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.filteredConnections, 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.filteredConnections) +} + +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_group.go b/experimental/libbox/command_group.go index 21fd39d29a..a5572ea1f5 100644 --- a/experimental/libbox/command_group.go +++ b/experimental/libbox/command_group.go @@ -14,36 +14,6 @@ import ( "github.com/sagernet/sing/service" ) -type OutboundGroup struct { - Tag string - Type string - Selectable bool - Selected string - IsExpand bool - items []*OutboundGroupItem -} - -func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { - return newIterator(g.items) -} - -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 (c *CommandClient) handleGroupConn(conn net.Conn) { defer conn.Close() @@ -92,6 +62,36 @@ func (s *CommandServer) handleGroupConn(conn net.Conn) error { } } +type OutboundGroup struct { + Tag string + Type string + Selectable bool + Selected string + IsExpand bool + items []*OutboundGroupItem +} + +func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { + return newIterator(g.items) +} + +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) { var groupLength uint16 err := binary.Read(reader, binary.BigEndian, &groupLength) diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index da931ef552..8918756dae 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -33,6 +33,8 @@ type CommandServer struct { urlTestUpdate chan struct{} modeUpdate chan struct{} logReset chan struct{} + + closedConnections []Connection } type CommandServerHandler interface { @@ -176,6 +178,10 @@ func (s *CommandServer) handleConnection(conn net.Conn) error { return s.handleGetSystemProxyStatus(conn) case CommandSetSystemProxyEnabled: return s.handleSetSystemProxyEnabled(conn) + case CommandConnections: + return s.handleConnectionsConn(conn) + case CommandCloseConnection: + return s.handleCloseConnection(conn) default: return E.New("unknown command: ", command) } diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go index 7f1eca8c03..4ab09d4b28 100644 --- a/experimental/libbox/command_status.go +++ b/experimental/libbox/command_status.go @@ -36,7 +36,7 @@ func (s *CommandServer) readStatus() StatusMessage { trafficManager := clashServer.(*clashapi.Server).TrafficManager() message.Uplink, message.Downlink = trafficManager.Now() message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() - message.ConnectionsIn = int32(trafficManager.Connections()) + message.ConnectionsIn = int32(trafficManager.ConnectionsLen()) } } diff --git a/experimental/libbox/iterator.go b/experimental/libbox/iterator.go index db64a25978..530a7e43bd 100644 --- a/experimental/libbox/iterator.go +++ b/experimental/libbox/iterator.go @@ -17,6 +17,10 @@ func newIterator[T any](values []T) *iterator[T] { return &iterator[T]{values} } +func newPtrIterator[T any](values []T) *iterator[*T] { + return &iterator[*T]{common.Map(values, func(value T) *T { return &value })} +} + func (i *iterator[T]) Next() T { if len(i.values) == 0 { return common.DefaultValue[T]() diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 0a54d7abd8..c65090103c 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -149,33 +149,6 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions return tun.New(*options) } -func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { - var uid int32 - if w.useProcFS { - 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())) - if err != nil { - return nil, err - } - } - packageName, _ := w.iif.PackageNameByUid(uid) - return &process.Info{UserId: uid, PackageName: packageName}, nil -} - func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool { return w.iif.UsePlatformDefaultInterfaceMonitor() } @@ -229,6 +202,33 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { return (adapter.WIFIState)(*wifiState) } +func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { + var uid int32 + if w.useProcFS { + 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())) + if err != nil { + return nil, err + } + } + packageName, _ := w.iif.PackageNameByUid(uid) + return &process.Info{UserId: uid, PackageName: packageName}, nil +} + func (w *platformInterfaceWrapper) DisableColors() bool { return runtime.GOOS != "android" } diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index ea468f391c..31611354bf 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -4,10 +4,12 @@ import ( "os" "os/user" "strconv" + "time" "github.com/sagernet/sing-box/common/humanize" C "github.com/sagernet/sing-box/constant" _ "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/log" ) var ( @@ -59,6 +61,10 @@ func FormatMemoryBytes(length int64) string { return humanize.MemoryBytes(uint64(length)) } +func FormatDuration(duration int64) string { + return log.FormatDuration(time.Duration(duration) * time.Millisecond) +} + func ProxyDisplayType(proxyType string) string { return C.ProxyDisplayName(proxyType) } diff --git a/inbound/builder.go b/inbound/builder.go index 513b016f79..ddfd361dbd 100644 --- a/inbound/builder.go +++ b/inbound/builder.go @@ -11,43 +11,43 @@ import ( E "github.com/sagernet/sing/common/exceptions" ) -func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) { +func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) { if options.Type == "" { return nil, E.New("missing inbound type") } switch options.Type { case C.TypeTun: - return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface) + return NewTun(ctx, router, logger, tag, options.TunOptions, platformInterface) case C.TypeRedirect: - return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil + return NewRedirect(ctx, router, logger, tag, options.RedirectOptions), nil case C.TypeTProxy: - return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil + return NewTProxy(ctx, router, logger, tag, options.TProxyOptions), nil case C.TypeDirect: - return NewDirect(ctx, router, logger, options.Tag, options.DirectOptions), nil + return NewDirect(ctx, router, logger, tag, options.DirectOptions), nil case C.TypeSOCKS: - return NewSocks(ctx, router, logger, options.Tag, options.SocksOptions), nil + return NewSocks(ctx, router, logger, tag, options.SocksOptions), nil case C.TypeHTTP: - return NewHTTP(ctx, router, logger, options.Tag, options.HTTPOptions) + return NewHTTP(ctx, router, logger, tag, options.HTTPOptions) case C.TypeMixed: - return NewMixed(ctx, router, logger, options.Tag, options.MixedOptions), nil + return NewMixed(ctx, router, logger, tag, options.MixedOptions), nil case C.TypeShadowsocks: - return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions) + return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions) case C.TypeVMess: - return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions) + return NewVMess(ctx, router, logger, tag, options.VMessOptions) case C.TypeTrojan: - return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions) + return NewTrojan(ctx, router, logger, tag, options.TrojanOptions) case C.TypeNaive: - return NewNaive(ctx, router, logger, options.Tag, options.NaiveOptions) + return NewNaive(ctx, router, logger, tag, options.NaiveOptions) case C.TypeHysteria: - return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions) + return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions) case C.TypeShadowTLS: - return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions) + return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions) case C.TypeVLESS: - return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions) + return NewVLESS(ctx, router, logger, tag, options.VLESSOptions) case C.TypeTUIC: - return NewTUIC(ctx, router, logger, options.Tag, options.TUICOptions) + return NewTUIC(ctx, router, logger, tag, options.TUICOptions) case C.TypeHysteria2: - return NewHysteria2(ctx, router, logger, options.Tag, options.Hysteria2Options) + return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options) default: return nil, E.New("unknown inbound type: ", options.Type) } diff --git a/log/format.go b/log/format.go index 6fb91d3157..6f4347b12a 100644 --- a/log/format.go +++ b/log/format.go @@ -43,7 +43,7 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message id, hasId = IDFromContext(ctx) } if hasId { - activeDuration := formatDuration(time.Since(id.CreatedAt)) + activeDuration := FormatDuration(time.Since(id.CreatedAt)) if !f.DisableColors { var color aurora.Color color = aurora.Color(uint8(id.ID)) @@ -113,7 +113,7 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string id, hasId = IDFromContext(ctx) } if hasId { - activeDuration := formatDuration(time.Since(id.CreatedAt)) + activeDuration := FormatDuration(time.Since(id.CreatedAt)) if !f.DisableColors { var color aurora.Color color = aurora.Color(uint8(id.ID)) @@ -163,7 +163,7 @@ func xd(value int, x int) string { return message } -func formatDuration(duration time.Duration) string { +func FormatDuration(duration time.Duration) string { if duration < time.Second { return F.ToString(duration.Milliseconds(), "ms") } else if duration < time.Minute { From 7eec3fb57a4b2e743020a65e4c12cbb3ef941d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 18 Jun 2024 17:49:06 +0800 Subject: [PATCH 0045/1272] platform: Add log update interval --- experimental/libbox/command_client.go | 8 ++- experimental/libbox/command_log.go | 95 ++++++++++++++++----------- experimental/libbox/iterator.go | 15 +++-- 3 files changed, 71 insertions(+), 47 deletions(-) diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index 199dce0d18..bd99511582 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -25,8 +25,8 @@ type CommandClientOptions struct { type CommandClientHandler interface { Connected() Disconnected(message string) - ClearLog() - WriteLog(message string) + ClearLogs() + WriteLogs(messageList StringIterator) WriteStatus(message *StatusMessage) WriteGroups(message OutboundGroupIterator) InitializeClashMode(modeList StringIterator, currentMode string) @@ -84,6 +84,10 @@ func (c *CommandClient) Connect() error { } 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.handler.Connected() go c.handleLogConn(conn) case CommandStatus: diff --git a/experimental/libbox/command_log.go b/experimental/libbox/command_log.go index ce72010dd0..8a22aa2e74 100644 --- a/experimental/libbox/command_log.go +++ b/experimental/libbox/command_log.go @@ -1,10 +1,14 @@ package libbox import ( + "bufio" "context" - "encoding/binary" "io" "net" + "time" + + "github.com/sagernet/sing/common/binary" + E "github.com/sagernet/sing/common/exceptions" ) func (s *CommandServer) WriteMessage(message string) { @@ -17,43 +21,39 @@ func (s *CommandServer) WriteMessage(message string) { s.access.Unlock() } -func readLog(reader io.Reader) ([]byte, error) { - var messageLength uint16 - err := binary.Read(reader, binary.BigEndian, &messageLength) - if err != nil { - return nil, err - } - if messageLength == 0 { - return nil, nil - } - data := make([]byte, messageLength) - _, err = io.ReadFull(reader, data) - if err != nil { - return nil, err - } - return data, nil -} - -func writeLog(writer io.Writer, message []byte) error { +func writeLog(writer *bufio.Writer, messages []string) error { err := binary.Write(writer, binary.BigEndian, uint8(0)) if err != nil { return err } - err = binary.Write(writer, binary.BigEndian, uint16(len(message))) + err = binary.WriteData(writer, binary.BigEndian, messages) if err != nil { return err } - if len(message) > 0 { - _, err = writer.Write(message) - } - return err + return writer.Flush() } -func writeClearLog(writer io.Writer) error { - return binary.Write(writer, binary.BigEndian, uint8(1)) +func writeClearLog(writer *bufio.Writer) error { + err := binary.Write(writer, binary.BigEndian, uint8(1)) + if err != nil { + return err + } + return writer.Flush() } 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()) @@ -66,52 +66,67 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error { return err } defer s.observer.UnSubscribe(subscription) - for _, line := range savedLines { - err = writeLog(conn, []byte(line)) + writer := bufio.NewWriter(conn) + if len(savedLines) > 0 { + err = writeLog(writer, savedLines) if err != nil { return err } } ctx := connKeepAlive(conn) + var logLines []string for { select { case <-ctx.Done(): return ctx.Err() - case message := <-subscription: - err = writeLog(conn, []byte(message)) - if err != nil { - return err - } case <-s.logReset: - err = writeClearLog(conn) + err = writeClearLog(writer) 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 = writeLog(writer, logLines) + if err != nil { + return err + } } } } func (c *CommandClient) handleLogConn(conn net.Conn) { + reader := bufio.NewReader(conn) for { var messageType uint8 - err := binary.Read(conn, binary.BigEndian, &messageType) + err := binary.Read(reader, binary.BigEndian, &messageType) if err != nil { c.handler.Disconnected(err.Error()) return } - var message []byte + var messages []string switch messageType { case 0: - message, err = readLog(conn) + err = binary.ReadData(reader, binary.BigEndian, &messages) if err != nil { c.handler.Disconnected(err.Error()) return } - c.handler.WriteLog(string(message)) + c.handler.WriteLogs(newIterator(messages)) case 1: - c.handler.ClearLog() + c.handler.ClearLogs() } } } @@ -120,7 +135,7 @@ func connKeepAlive(reader io.Reader) context.Context { ctx, cancel := context.WithCancelCause(context.Background()) go func() { for { - _, err := readLog(reader) + _, err := reader.Read(make([]byte, 1)) if err != nil { cancel(err) return diff --git a/experimental/libbox/iterator.go b/experimental/libbox/iterator.go index 530a7e43bd..50d7385b36 100644 --- a/experimental/libbox/iterator.go +++ b/experimental/libbox/iterator.go @@ -3,8 +3,9 @@ package libbox import "github.com/sagernet/sing/common" type StringIterator interface { - Next() string + Len() int32 HasNext() bool + Next() string } var _ StringIterator = (*iterator[string])(nil) @@ -21,6 +22,14 @@ func newPtrIterator[T any](values []T) *iterator[*T] { return &iterator[*T]{common.Map(values, func(value T) *T { return &value })} } +func (i *iterator[T]) Len() int32 { + return int32(len(i.values)) +} + +func (i *iterator[T]) HasNext() bool { + return len(i.values) > 0 +} + func (i *iterator[T]) Next() T { if len(i.values) == 0 { return common.DefaultValue[T]() @@ -30,10 +39,6 @@ func (i *iterator[T]) Next() T { return nextValue } -func (i *iterator[T]) HasNext() bool { - return len(i.values) > 0 -} - type abstractIterator[T any] interface { Next() T HasNext() bool From bda93d516bc93248b568140b26c8b445dd833aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 25 Jun 2024 20:26:39 +0800 Subject: [PATCH 0046/1272] platform: Fix clash server reload on android --- experimental/clashapi/server.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 5ace3351e8..3428bbb771 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -7,7 +7,9 @@ import ( "net" "net/http" "os" + "runtime" "strings" + "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -143,7 +145,18 @@ func (s *Server) PreStart() error { func (s *Server) Start() error { if s.externalController { s.checkAndDownloadExternalUI() - listener, err := net.Listen("tcp", s.httpServer.Addr) + var ( + listener net.Listener + err error + ) + for i := 0; i < 3; i++ { + listener, err = net.Listen("tcp", s.httpServer.Addr) + if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) { + time.Sleep(100 * time.Millisecond) + continue + } + break + } if err != nil { return E.Cause(err, "external controller listen error") } From 923d3222b013af5174569a83843f324770f6ea4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 24 Jun 2024 09:49:15 +0800 Subject: [PATCH 0047/1272] WTF is this --- adapter/experimental.go | 17 +-- adapter/router.go | 2 +- box_outbound.go | 4 +- cmd/internal/build_libbox/main.go | 4 +- cmd/internal/build_shared/sdk.go | 10 +- cmd/sing-box/cmd_merge.go | 6 +- cmd/sing-box/cmd_run.go | 2 +- common/geosite/geosite_test.go | 34 +++++ common/geosite/reader.go | 88 +++++++---- common/geosite/writer.go | 21 ++- common/srs/binary.go | 113 +++++--------- common/srs/ip_set.go | 105 +++++-------- constant/path.go | 6 +- experimental/libbox/command_clash_mode.go | 18 +-- .../libbox/command_close_connection.go | 5 +- experimental/libbox/command_connections.go | 36 +++-- experimental/libbox/command_group.go | 140 +++--------------- experimental/libbox/command_log.go | 66 +++++---- experimental/libbox/command_power.go | 10 +- experimental/libbox/command_select.go | 10 +- experimental/libbox/command_server.go | 8 - experimental/libbox/command_shared.go | 6 +- experimental/libbox/command_urltest.go | 6 +- experimental/libbox/profile_import.go | 43 +++--- experimental/libbox/setup.go | 6 + go.mod | 2 +- go.sum | 4 +- inbound/mixed.go | 14 +- inbound/vless.go | 11 +- inbound/vmess.go | 11 +- option/outbound.go | 2 +- option/route.go | 2 +- outbound/proxy.go | 18 +-- outbound/tor.go | 12 +- route/router.go | 4 +- route/router_geo_resources.go | 8 +- route/rule_abstract.go | 19 ++- transport/trojan/mux.go | 27 +++- transport/trojan/service.go | 4 +- transport/v2raygrpc/conn.go | 4 +- transport/v2raygrpclite/conn.go | 8 +- 41 files changed, 415 insertions(+), 501 deletions(-) create mode 100644 common/geosite/geosite_test.go diff --git a/adapter/experimental.go b/adapter/experimental.go index 5e1cbd9d9d..0cab5ed5a8 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -4,14 +4,13 @@ import ( "bytes" "context" "encoding/binary" - "io" "net" "time" "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-dns" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) type ClashServer interface { @@ -56,16 +55,15 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) { if err != nil { return nil, err } - err = rw.WriteUVariant(&buffer, uint64(len(s.Content))) + err = varbin.Write(&buffer, binary.BigEndian, s.Content) if err != nil { return nil, err } - buffer.Write(s.Content) err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix()) if err != nil { return nil, err } - err = rw.WriteVString(&buffer, s.LastEtag) + err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag) if err != nil { return nil, err } @@ -79,12 +77,7 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error { if err != nil { return err } - contentLen, err := rw.ReadUVariant(reader) - if err != nil { - return err - } - s.Content = make([]byte, contentLen) - _, err = io.ReadFull(reader, s.Content) + err = varbin.Read(reader, binary.BigEndian, &s.Content) if err != nil { return err } @@ -94,7 +87,7 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error { return err } s.LastUpdated = time.Unix(lastUpdated, 0) - s.LastEtag, err = rw.ReadVString(reader) + err = varbin.Read(reader, binary.BigEndian, &s.LastEtag) if err != nil { return err } diff --git a/adapter/router.go b/adapter/router.go index 54dc3396dc..c481f0c8c9 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -45,7 +45,7 @@ type Router interface { DefaultInterface() string AutoDetectInterface() bool AutoDetectInterfaceFunc() control.Func - DefaultMark() int + DefaultMark() uint32 NetworkMonitor() tun.NetworkUpdateMonitor InterfaceMonitor() tun.DefaultInterfaceMonitor PackageManager() tun.PackageManager diff --git a/box_outbound.go b/box_outbound.go index 6e3f0617fc..f03f3b7d41 100644 --- a/box_outbound.go +++ b/box_outbound.go @@ -45,7 +45,9 @@ func (s *Box) startOutbounds() error { } started[outboundTag] = true canContinue = true - if starter, isStarter := outboundToStart.(common.Starter); isStarter { + if starter, isStarter := outboundToStart.(interface { + Start() error + }); isStarter { monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") err := starter.Start() monitor.Finish() diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index ae0fe34a6b..fc9308ff40 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -93,7 +93,7 @@ func buildAndroid() { const name = "libbox.aar" copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs") - if rw.FileExists(copyPath) { + if rw.IsDir(copyPath) { copyPath, _ = filepath.Abs(copyPath) err = rw.CopyFile(name, filepath.Join(copyPath, name)) if err != nil { @@ -134,7 +134,7 @@ func buildiOS() { } copyPath := filepath.Join("..", "sing-box-for-apple") - if rw.FileExists(copyPath) { + if rw.IsDir(copyPath) { targetDir := filepath.Join(copyPath, "Libbox.xcframework") targetDir, _ = filepath.Abs(targetDir) os.RemoveAll(targetDir) diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index ce7f0c86d0..b6c1ec9deb 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -30,7 +30,7 @@ func FindSDK() { } for _, path := range searchPath { path = os.ExpandEnv(path) - if rw.FileExists(filepath.Join(path, "licenses", "android-sdk-license")) { + if rw.IsFile(filepath.Join(path, "licenses", "android-sdk-license")) { androidSDKPath = path break } @@ -60,7 +60,7 @@ func FindSDK() { func findNDK() bool { const fixedVersion = "26.2.11394342" const versionFile = "source.properties" - if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.FileExists(filepath.Join(fixedPath, versionFile)) { + if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { androidNDKPath = fixedPath return true } @@ -86,7 +86,7 @@ func findNDK() bool { }) for _, versionName := range versionNames { currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName) - if rw.FileExists(filepath.Join(androidSDKPath, versionFile)) { + if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) { androidNDKPath = currentNDKPath log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion) return true @@ -100,11 +100,11 @@ var GoBinPath string func FindMobile() { goBin := filepath.Join(build.Default.GOPATH, "bin") if runtime.GOOS == "windows" { - if !rw.FileExists(filepath.Join(goBin, "gobind.exe")) { + if !rw.IsFile(filepath.Join(goBin, "gobind.exe")) { log.Fatal("missing gomobile installation") } } else { - if !rw.FileExists(filepath.Join(goBin, "gobind")) { + if !rw.IsFile(filepath.Join(goBin, "gobind")) { log.Fatal("missing gomobile installation") } } diff --git a/cmd/sing-box/cmd_merge.go b/cmd/sing-box/cmd_merge.go index 1d19ff17e1..10dd38a1db 100644 --- a/cmd/sing-box/cmd_merge.go +++ b/cmd/sing-box/cmd_merge.go @@ -54,7 +54,11 @@ func merge(outputPath string) error { return nil } } - err = rw.WriteFile(outputPath, buffer.Bytes()) + err = rw.MkdirParent(outputPath) + if err != nil { + return err + } + err = os.WriteFile(outputPath, buffer.Bytes(), 0o644) if err != nil { return err } diff --git a/cmd/sing-box/cmd_run.go b/cmd/sing-box/cmd_run.go index 3c4dd0d910..e717c5945d 100644 --- a/cmd/sing-box/cmd_run.go +++ b/cmd/sing-box/cmd_run.go @@ -109,7 +109,7 @@ func readConfigAndMerge() (option.Options, error) { } var mergedMessage json.RawMessage for _, options := range optionsList { - mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage) + mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false) if err != nil { return option.Options{}, E.Cause(err, "merge config at ", options.path) } diff --git a/common/geosite/geosite_test.go b/common/geosite/geosite_test.go new file mode 100644 index 0000000000..bdcb7a7afd --- /dev/null +++ b/common/geosite/geosite_test.go @@ -0,0 +1,34 @@ +package geosite_test + +import ( + "bytes" + "testing" + + "github.com/sagernet/sing-box/common/geosite" + + "github.com/stretchr/testify/require" +) + +func TestGeosite(t *testing.T) { + t.Parallel() + + var buffer bytes.Buffer + err := geosite.Write(&buffer, map[string][]geosite.Item{ + "test": { + { + Type: geosite.RuleTypeDomain, + Value: "example.org", + }, + }, + }) + require.NoError(t, err) + reader, codes, err := geosite.NewReader(bytes.NewReader(buffer.Bytes())) + require.NoError(t, err) + require.Equal(t, []string{"test"}, codes) + items, err := reader.Read("test") + require.NoError(t, err) + require.Equal(t, []geosite.Item{{ + Type: geosite.RuleTypeDomain, + Value: "example.org", + }}, items) +} diff --git a/common/geosite/reader.go b/common/geosite/reader.go index a1b39f28b4..3b3f7fec29 100644 --- a/common/geosite/reader.go +++ b/common/geosite/reader.go @@ -1,17 +1,24 @@ package geosite import ( + "bufio" + "encoding/binary" "io" "os" + "sync" + "sync/atomic" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) type Reader struct { - reader io.ReadSeeker - domainIndex map[string]int - domainLength map[string]int + access sync.Mutex + reader io.ReadSeeker + bufferedReader *bufio.Reader + metadataIndex int64 + domainIndex map[string]int + domainLength map[string]int } func Open(path string) (*Reader, []string, error) { @@ -19,12 +26,20 @@ func Open(path string) (*Reader, []string, error) { if err != nil { return nil, nil, err } + reader, codes, err := NewReader(content) + if err != nil { + content.Close() + return nil, nil, err + } + return reader, codes, nil +} + +func NewReader(readSeeker io.ReadSeeker) (*Reader, []string, error) { reader := &Reader{ - reader: content, + reader: readSeeker, } - err = reader.readMetadata() + err := reader.readMetadata() if err != nil { - content.Close() return nil, nil, err } codes := make([]string, 0, len(reader.domainIndex)) @@ -34,15 +49,23 @@ func Open(path string) (*Reader, []string, error) { return reader, codes, nil } +type geositeMetadata struct { + Code string + Index uint64 + Length uint64 +} + func (r *Reader) readMetadata() error { - version, err := rw.ReadByte(r.reader) + counter := &readCounter{Reader: r.reader} + reader := bufio.NewReader(counter) + version, err := reader.ReadByte() if err != nil { return err } if version != 0 { return E.New("unknown version") } - entryLength, err := rw.ReadUVariant(r.reader) + entryLength, err := binary.ReadUvarint(reader) if err != nil { return err } @@ -55,16 +78,16 @@ func (r *Reader) readMetadata() error { codeIndex uint64 codeLength uint64 ) - code, err = rw.ReadVString(r.reader) + code, err = varbin.ReadValue[string](reader, binary.BigEndian) if err != nil { return err } keys[i] = code - codeIndex, err = rw.ReadUVariant(r.reader) + codeIndex, err = binary.ReadUvarint(reader) if err != nil { return err } - codeLength, err = rw.ReadUVariant(r.reader) + codeLength, err = binary.ReadUvarint(reader) if err != nil { return err } @@ -73,6 +96,8 @@ func (r *Reader) readMetadata() error { } r.domainIndex = domainIndex r.domainLength = domainLength + r.metadataIndex = counter.count - int64(reader.Buffered()) + r.bufferedReader = reader return nil } @@ -81,31 +106,32 @@ func (r *Reader) Read(code string) ([]Item, error) { if !exists { return nil, E.New("code ", code, " not exists!") } - _, err := r.reader.Seek(int64(index), io.SeekCurrent) + _, err := r.reader.Seek(r.metadataIndex+int64(index), io.SeekStart) if err != nil { return nil, err } - counter := &rw.ReadCounter{Reader: r.reader} - domain := make([]Item, r.domainLength[code]) - for i := range domain { - var ( - item Item - err error - ) - item.Type, err = rw.ReadByte(counter) - if err != nil { - return nil, err - } - item.Value, err = rw.ReadVString(counter) - if err != nil { - return nil, err - } - domain[i] = item + 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 } - _, err = r.reader.Seek(int64(-index)-counter.Count(), io.SeekCurrent) - return domain, err + return itemList, nil } func (r *Reader) Upstream() any { return r.reader } + +type readCounter struct { + io.Reader + count int64 +} + +func (r *readCounter) Read(p []byte) (n int, err error) { + n, err = r.Reader.Read(p) + if n > 0 { + atomic.AddInt64(&r.count, int64(n)) + } + return +} diff --git a/common/geosite/writer.go b/common/geosite/writer.go index 4e7ec514b1..1615fa3480 100644 --- a/common/geosite/writer.go +++ b/common/geosite/writer.go @@ -2,13 +2,13 @@ package geosite import ( "bytes" - "io" + "encoding/binary" "sort" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) -func Write(writer io.Writer, domains map[string][]Item) error { +func Write(writer varbin.Writer, domains map[string][]Item) error { keys := make([]string, 0, len(domains)) for code := range domains { keys = append(keys, code) @@ -19,35 +19,34 @@ func Write(writer io.Writer, domains map[string][]Item) error { index := make(map[string]int) for _, code := range keys { index[code] = content.Len() - for _, domain := range domains[code] { - content.WriteByte(domain.Type) - err := rw.WriteVString(content, domain.Value) + for _, item := range domains[code] { + err := varbin.Write(content, binary.BigEndian, item) if err != nil { return err } } } - err := rw.WriteByte(writer, 0) + err := writer.WriteByte(0) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(len(keys))) + _, err = varbin.WriteUvarint(writer, uint64(len(keys))) if err != nil { return err } for _, code := range keys { - err = rw.WriteVString(writer, code) + err = varbin.Write(writer, binary.BigEndian, code) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(index[code])) + _, err = varbin.WriteUvarint(writer, uint64(index[code])) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(len(domains[code]))) + _, err = varbin.WriteUvarint(writer, uint64(len(domains[code]))) if err != nil { return err } diff --git a/common/srs/binary.go b/common/srs/binary.go index faf4cd17b2..c7c55e0838 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -1,6 +1,7 @@ package srs import ( + "bufio" "compress/zlib" "encoding/binary" "io" @@ -11,7 +12,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/domain" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" "go4.org/netipx" ) @@ -38,7 +39,7 @@ const ( ruleItemFinal uint8 = 0xFF ) -func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err error) { +func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) { var magicBytes [3]byte _, err = io.ReadFull(reader, magicBytes[:]) if err != nil { @@ -60,13 +61,14 @@ func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err err if err != nil { return } - length, err := rw.ReadUVariant(zReader) + bReader := bufio.NewReader(zReader) + length, err := binary.ReadUvarint(bReader) if err != nil { return } ruleSet.Rules = make([]option.HeadlessRule, length) for i := uint64(0); i < length; i++ { - ruleSet.Rules[i], err = readRule(zReader, recovery) + ruleSet.Rules[i], err = readRule(bReader, recover) if err != nil { err = E.Cause(err, "read rule[", i, "]") return @@ -88,20 +90,25 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet) error { if err != nil { return err } - err = rw.WriteUVariant(zWriter, uint64(len(ruleSet.Rules))) + bWriter := bufio.NewWriter(zWriter) + _, err = varbin.WriteUvarint(bWriter, uint64(len(ruleSet.Rules))) if err != nil { return err } for _, rule := range ruleSet.Rules { - err = writeRule(zWriter, rule) + err = writeRule(bWriter, rule) if err != nil { return err } } + err = bWriter.Flush() + if err != nil { + return err + } return zWriter.Close() } -func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err error) { +func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err error) { var ruleType uint8 err = binary.Read(reader, binary.BigEndian, &ruleType) if err != nil { @@ -110,17 +117,17 @@ func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err er switch ruleType { case 0: rule.Type = C.RuleTypeDefault - rule.DefaultOptions, err = readDefaultRule(reader, recovery) + rule.DefaultOptions, err = readDefaultRule(reader, recover) case 1: rule.Type = C.RuleTypeLogical - rule.LogicalOptions, err = readLogicalRule(reader, recovery) + rule.LogicalOptions, err = readLogicalRule(reader, recover) default: err = E.New("unknown rule type: ", ruleType) } return } -func writeRule(writer io.Writer, rule option.HeadlessRule) error { +func writeRule(writer varbin.Writer, rule option.HeadlessRule) error { switch rule.Type { case C.RuleTypeDefault: return writeDefaultRule(writer, rule.DefaultOptions) @@ -131,7 +138,7 @@ func writeRule(writer io.Writer, rule option.HeadlessRule) error { } } -func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadlessRule, err error) { +func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHeadlessRule, err error) { var lastItemType uint8 for { var itemType uint8 @@ -158,6 +165,9 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle return } rule.DomainMatcher = matcher + if recover { + rule.Domain, rule.DomainSuffix = matcher.Dump() + } case ruleItemDomainKeyword: rule.DomainKeyword, err = readRuleItemString(reader) case ruleItemDomainRegex: @@ -167,7 +177,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle if err != nil { return } - if recovery { + if recover { rule.SourceIPCIDR = common.Map(rule.SourceIPSet.Prefixes(), netip.Prefix.String) } case ruleItemIPCIDR: @@ -175,7 +185,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle if err != nil { return } - if recovery { + if recover { rule.IPCIDR = common.Map(rule.IPSet.Prefixes(), netip.Prefix.String) } case ruleItemSourcePort: @@ -209,7 +219,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle } } -func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error { +func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule) error { err := binary.Write(writer, binary.BigEndian, uint8(0)) if err != nil { return err @@ -327,73 +337,31 @@ func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error { return nil } -func readRuleItemString(reader io.Reader) ([]string, error) { - length, err := rw.ReadUVariant(reader) - if err != nil { - return nil, err - } - value := make([]string, length) - for i := uint64(0); i < length; i++ { - value[i], err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - } - return value, nil +func readRuleItemString(reader varbin.Reader) ([]string, error) { + return varbin.ReadValue[[]string](reader, binary.BigEndian) } -func writeRuleItemString(writer io.Writer, itemType uint8, value []string) error { - err := binary.Write(writer, binary.BigEndian, itemType) - if err != nil { - return err - } - err = rw.WriteUVariant(writer, uint64(len(value))) +func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error { + err := writer.WriteByte(itemType) if err != nil { return err } - for _, item := range value { - err = rw.WriteVString(writer, item) - if err != nil { - return err - } - } - return nil + return varbin.Write(writer, binary.BigEndian, value) } -func readRuleItemUint16(reader io.Reader) ([]uint16, error) { - length, err := rw.ReadUVariant(reader) - if err != nil { - return nil, err - } - value := make([]uint16, length) - for i := uint64(0); i < length; i++ { - err = binary.Read(reader, binary.BigEndian, &value[i]) - if err != nil { - return nil, err - } - } - return value, nil +func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) { + return varbin.ReadValue[[]uint16](reader, binary.BigEndian) } -func writeRuleItemUint16(writer io.Writer, itemType uint8, value []uint16) error { - err := binary.Write(writer, binary.BigEndian, itemType) +func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error { + err := writer.WriteByte(itemType) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(len(value))) - if err != nil { - return err - } - for _, item := range value { - err = binary.Write(writer, binary.BigEndian, item) - if err != nil { - return err - } - } - return nil + return varbin.Write(writer, binary.BigEndian, value) } -func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error { +func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error { var builder netipx.IPSetBuilder for i, prefixString := range value { prefix, err := netip.ParsePrefix(prefixString) @@ -419,9 +387,8 @@ func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error { return writeIPSet(writer, ipSet) } -func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) { - var mode uint8 - err = binary.Read(reader, binary.BigEndian, &mode) +func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) { + mode, err := reader.ReadByte() if err != nil { return } @@ -434,7 +401,7 @@ func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.Logica err = E.New("unknown logical mode: ", mode) return } - length, err := rw.ReadUVariant(reader) + length, err := binary.ReadUvarint(reader) if err != nil { return } @@ -453,7 +420,7 @@ func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.Logica return } -func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule) error { +func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule) error { err := binary.Write(writer, binary.BigEndian, uint8(1)) if err != nil { return err @@ -469,7 +436,7 @@ func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(len(logicalRule.Rules))) + _, err = varbin.WriteUvarint(writer, uint64(len(logicalRule.Rules))) if err != nil { return err } diff --git a/common/srs/ip_set.go b/common/srs/ip_set.go index b346da26f6..044dc823b3 100644 --- a/common/srs/ip_set.go +++ b/common/srs/ip_set.go @@ -2,11 +2,13 @@ package srs import ( "encoding/binary" - "io" "net/netip" + "os" "unsafe" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/varbin" "go4.org/netipx" ) @@ -20,94 +22,57 @@ type myIPRange struct { to netip.Addr } -func readIPSet(reader io.Reader) (*netipx.IPSet, error) { - var version uint8 - err := binary.Read(reader, binary.BigEndian, &version) +type myIPRangeData struct { + From []byte + To []byte +} + +func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) { + version, err := reader.ReadByte() if err != nil { return nil, err } + if version != 1 { + return nil, os.ErrInvalid + } + // WTF why using uint64 here var length uint64 err = binary.Read(reader, binary.BigEndian, &length) 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, length), + rr: make([]myIPRange, len(ranges)), } - for i := uint64(0); i < length; i++ { - var ( - fromLen uint64 - toLen uint64 - fromAddr netip.Addr - toAddr netip.Addr - ) - fromLen, err = rw.ReadUVariant(reader) - if err != nil { - return nil, err - } - fromBytes := make([]byte, fromLen) - _, err = io.ReadFull(reader, fromBytes) - if err != nil { - return nil, err - } - err = fromAddr.UnmarshalBinary(fromBytes) - if err != nil { - return nil, err - } - toLen, err = rw.ReadUVariant(reader) - if err != nil { - return nil, err - } - toBytes := make([]byte, toLen) - _, err = io.ReadFull(reader, toBytes) - if err != nil { - return nil, err - } - err = toAddr.UnmarshalBinary(toBytes) - if err != nil { - return nil, err - } - mySet.rr[i] = myIPRange{fromAddr, toAddr} + 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 } -func writeIPSet(writer io.Writer, set *netipx.IPSet) error { - err := binary.Write(writer, binary.BigEndian, uint8(1)) +func writeIPSet(writer varbin.Writer, set *netipx.IPSet) error { + err := writer.WriteByte(1) if err != nil { return err } - mySet := (*myIPSet)(unsafe.Pointer(set)) - err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr))) + 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))) if err != nil { return err } - for _, rr := range mySet.rr { - var ( - fromBinary []byte - toBinary []byte - ) - fromBinary, err = rr.from.MarshalBinary() - if err != nil { - return err - } - err = rw.WriteUVariant(writer, uint64(len(fromBinary))) - if err != nil { - return err - } - _, err = writer.Write(fromBinary) - if err != nil { - return err - } - toBinary, err = rr.to.MarshalBinary() - if err != nil { - return err - } - err = rw.WriteUVariant(writer, uint64(len(toBinary))) - if err != nil { - return err - } - _, err = writer.Write(toBinary) + for _, data := range dataList { + err = varbin.Write(writer, binary.BigEndian, data) if err != nil { return err } diff --git a/constant/path.go b/constant/path.go index 98acacdc37..ea2aad3e23 100644 --- a/constant/path.go +++ b/constant/path.go @@ -13,14 +13,14 @@ var resourcePaths []string func FindPath(name string) (string, bool) { name = os.ExpandEnv(name) - if rw.FileExists(name) { + if rw.IsFile(name) { return name, true } for _, dir := range resourcePaths { - if path := filepath.Join(dir, dirName, name); rw.FileExists(path) { + if path := filepath.Join(dir, dirName, name); rw.IsFile(path) { return path, true } - if path := filepath.Join(dir, name); rw.FileExists(path) { + if path := filepath.Join(dir, name); rw.IsFile(path) { return path, true } } diff --git a/experimental/libbox/command_clash_mode.go b/experimental/libbox/command_clash_mode.go index 3377ae3afd..1b6eb47085 100644 --- a/experimental/libbox/command_clash_mode.go +++ b/experimental/libbox/command_clash_mode.go @@ -9,7 +9,7 @@ import ( "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/rw" + "github.com/sagernet/sing/common/varbin" ) func (c *CommandClient) SetClashMode(newMode string) error { @@ -22,7 +22,7 @@ func (c *CommandClient) SetClashMode(newMode string) error { if err != nil { return err } - err = rw.WriteVString(conn, newMode) + err = varbin.Write(conn, binary.BigEndian, newMode) if err != nil { return err } @@ -30,7 +30,7 @@ func (c *CommandClient) SetClashMode(newMode string) error { } func (s *CommandServer) handleSetClashMode(conn net.Conn) error { - newMode, err := rw.ReadVString(conn) + newMode, err := varbin.ReadValue[string](conn, binary.BigEndian) if err != nil { return err } @@ -50,7 +50,7 @@ func (c *CommandClient) handleModeConn(conn net.Conn) { defer conn.Close() for { - newMode, err := rw.ReadVString(conn) + newMode, err := varbin.ReadValue[string](conn, binary.BigEndian) if err != nil { c.handler.Disconnected(err.Error()) return @@ -80,7 +80,7 @@ func (s *CommandServer) handleModeConn(conn net.Conn) error { for { select { case <-s.modeUpdate: - err = rw.WriteVString(conn, clashServer.Mode()) + err = varbin.Write(conn, binary.BigEndian, clashServer.Mode()) if err != nil { return err } @@ -101,12 +101,12 @@ func readClashModeList(reader io.Reader) (modeList []string, currentMode string, } modeList = make([]string, modeListLength) for i := 0; i < int(modeListLength); i++ { - modeList[i], err = rw.ReadVString(reader) + modeList[i], err = varbin.ReadValue[string](reader, binary.BigEndian) if err != nil { return } } - currentMode, err = rw.ReadVString(reader) + currentMode, err = varbin.ReadValue[string](reader, binary.BigEndian) return } @@ -118,12 +118,12 @@ func writeClashModeList(writer io.Writer, clashServer adapter.ClashServer) error } if len(modeList) > 0 { for _, mode := range modeList { - err = rw.WriteVString(writer, mode) + err = varbin.Write(writer, binary.BigEndian, mode) if err != nil { return err } } - err = rw.WriteVString(writer, clashServer.Mode()) + err = varbin.Write(writer, binary.BigEndian, clashServer.Mode()) if err != nil { return err } diff --git a/experimental/libbox/command_close_connection.go b/experimental/libbox/command_close_connection.go index 62f5dc8419..1edd591122 100644 --- a/experimental/libbox/command_close_connection.go +++ b/experimental/libbox/command_close_connection.go @@ -7,6 +7,7 @@ import ( "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" ) @@ -18,7 +19,7 @@ func (c *CommandClient) CloseConnection(connId string) error { } defer conn.Close() writer := bufio.NewWriter(conn) - err = binary.WriteData(writer, binary.BigEndian, connId) + err = varbin.Write(writer, binary.BigEndian, connId) if err != nil { return err } @@ -32,7 +33,7 @@ func (c *CommandClient) CloseConnection(connId string) error { func (s *CommandServer) handleCloseConnection(conn net.Conn) error { reader := bufio.NewReader(conn) var connId string - err := binary.ReadData(reader, binary.BigEndian, &connId) + err := varbin.Read(reader, binary.BigEndian, &connId) if err != nil { return E.Cause(err, "read connection id") } diff --git a/experimental/libbox/command_connections.go b/experimental/libbox/command_connections.go index 9aaa995acf..b51c735249 100644 --- a/experimental/libbox/command_connections.go +++ b/experimental/libbox/command_connections.go @@ -12,6 +12,7 @@ import ( "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" ) @@ -19,14 +20,18 @@ import ( func (c *CommandClient) handleConnectionsConn(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) - var connections Connections + var ( + rawConnections []Connection + connections Connections + ) for { rawConnections = nil - err := binary.ReadData(reader, binary.BigEndian, &connections.connections) + err := varbin.Read(reader, binary.BigEndian, &rawConnections) if err != nil { c.handler.Disconnected(err.Error()) return } + connections.input = rawConnections c.handler.WriteConnections(&connections) } } @@ -70,7 +75,7 @@ func (s *CommandServer) handleConnectionsConn(conn net.Conn) error { for _, connection := range trafficManager.ClosedConnections() { outConnections = append(outConnections, newConnection(connections, connection, true)) } - err = binary.WriteData(writer, binary.BigEndian, outConnections) + err = varbin.Write(writer, binary.BigEndian, outConnections) if err != nil { return err } @@ -93,33 +98,32 @@ const ( ) type Connections struct { - connections []Connection - filteredConnections []Connection - outConnections *[]Connection + input []Connection + filtered []Connection } func (c *Connections) FilterState(state int32) { - c.filteredConnections = c.filteredConnections[:0] + c.filtered = c.filtered[:0] switch state { case ConnectionStateAll: - c.filteredConnections = append(c.filteredConnections, c.connections...) + c.filtered = append(c.filtered, c.input...) case ConnectionStateActive: - for _, connection := range c.connections { + for _, connection := range c.input { if connection.ClosedAt == 0 { - c.filteredConnections = append(c.filteredConnections, connection) + c.filtered = append(c.filtered, connection) } } case ConnectionStateClosed: - for _, connection := range c.connections { + for _, connection := range c.input { if connection.ClosedAt != 0 { - c.filteredConnections = append(c.filteredConnections, connection) + c.filtered = append(c.filtered, connection) } } } } func (c *Connections) SortByDate() { - slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { if x.CreatedAt < y.CreatedAt { return 1 } else if x.CreatedAt > y.CreatedAt { @@ -131,7 +135,7 @@ func (c *Connections) SortByDate() { } func (c *Connections) SortByTraffic() { - slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { xTraffic := x.Uplink + x.Downlink yTraffic := y.Uplink + y.Downlink if xTraffic < yTraffic { @@ -145,7 +149,7 @@ func (c *Connections) SortByTraffic() { } func (c *Connections) SortByTrafficTotal() { - slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { xTraffic := x.UplinkTotal + x.DownlinkTotal yTraffic := y.UplinkTotal + y.DownlinkTotal if xTraffic < yTraffic { @@ -159,7 +163,7 @@ func (c *Connections) SortByTrafficTotal() { } func (c *Connections) Iterator() ConnectionIterator { - return newPtrIterator(c.filteredConnections) + return newPtrIterator(c.filtered) } type Connection struct { diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go index a5572ea1f5..3a8d2a0749 100644 --- a/experimental/libbox/command_group.go +++ b/experimental/libbox/command_group.go @@ -1,6 +1,7 @@ package libbox import ( + "bufio" "encoding/binary" "io" "net" @@ -10,7 +11,7 @@ import ( "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/outbound" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" "github.com/sagernet/sing/service" ) @@ -36,19 +37,24 @@ func (s *CommandServer) handleGroupConn(conn net.Conn) error { 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(conn, service) + err = writeGroups(writer, service) if err != nil { return err } } else { - err := binary.Write(conn, binary.BigEndian, uint16(0)) + 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() @@ -68,11 +74,11 @@ type OutboundGroup struct { Selectable bool Selected string IsExpand bool - items []*OutboundGroupItem + ItemList []*OutboundGroupItem } func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { - return newIterator(g.items) + return newIterator(g.ItemList) } type OutboundGroupIterator interface { @@ -93,73 +99,10 @@ type OutboundGroupItemIterator interface { } func readGroups(reader io.Reader) (OutboundGroupIterator, error) { - var groupLength uint16 - err := binary.Read(reader, binary.BigEndian, &groupLength) + groups, err := varbin.ReadValue[[]*OutboundGroup](reader, binary.BigEndian) if err != nil { return nil, err } - - groups := make([]*OutboundGroup, 0, groupLength) - for i := 0; i < int(groupLength); i++ { - var group OutboundGroup - group.Tag, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - group.Type, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - err = binary.Read(reader, binary.BigEndian, &group.Selectable) - if err != nil { - return nil, err - } - - group.Selected, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - err = binary.Read(reader, binary.BigEndian, &group.IsExpand) - if err != nil { - return nil, err - } - - var itemLength uint16 - err = binary.Read(reader, binary.BigEndian, &itemLength) - if err != nil { - return nil, err - } - - group.items = make([]*OutboundGroupItem, itemLength) - for j := 0; j < int(itemLength); j++ { - var item OutboundGroupItem - item.Tag, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - item.Type, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - err = binary.Read(reader, binary.BigEndian, &item.URLTestTime) - if err != nil { - return nil, err - } - - err = binary.Read(reader, binary.BigEndian, &item.URLTestDelay) - if err != nil { - return nil, err - } - - group.items[j] = &item - } - groups = append(groups, &group) - } return newIterator(groups), nil } @@ -199,63 +142,14 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { item.URLTestTime = history.Time.Unix() item.URLTestDelay = int32(history.Delay) } - group.items = append(group.items, &item) + group.ItemList = append(group.ItemList, &item) } - if len(group.items) < 2 { + if len(group.ItemList) < 2 { continue } groups = append(groups, group) } - - err := binary.Write(writer, binary.BigEndian, uint16(len(groups))) - if err != nil { - return err - } - for _, group := range groups { - err = rw.WriteVString(writer, group.Tag) - if err != nil { - return err - } - err = rw.WriteVString(writer, group.Type) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, group.Selectable) - if err != nil { - return err - } - err = rw.WriteVString(writer, group.Selected) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, group.IsExpand) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, uint16(len(group.items))) - if err != nil { - return err - } - for _, item := range group.items { - err = rw.WriteVString(writer, item.Tag) - if err != nil { - return err - } - err = rw.WriteVString(writer, item.Type) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, item.URLTestTime) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, item.URLTestDelay) - if err != nil { - return err - } - } - } - return nil + return varbin.Write(writer, binary.BigEndian, groups) } func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { @@ -268,7 +162,7 @@ func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { if err != nil { return err } - err = rw.WriteVString(conn, groupTag) + err = varbin.Write(conn, binary.BigEndian, groupTag) if err != nil { return err } @@ -280,7 +174,7 @@ func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { } func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error { - groupTag, err := rw.ReadVString(conn) + groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian) if err != nil { return err } diff --git a/experimental/libbox/command_log.go b/experimental/libbox/command_log.go index 8a22aa2e74..07f6e83919 100644 --- a/experimental/libbox/command_log.go +++ b/experimental/libbox/command_log.go @@ -9,8 +9,19 @@ import ( "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() @@ -21,26 +32,6 @@ func (s *CommandServer) WriteMessage(message string) { s.access.Unlock() } -func writeLog(writer *bufio.Writer, messages []string) error { - err := binary.Write(writer, binary.BigEndian, uint8(0)) - if err != nil { - return err - } - err = binary.WriteData(writer, binary.BigEndian, messages) - if err != nil { - return err - } - return writer.Flush() -} - -func writeClearLog(writer *bufio.Writer) error { - err := binary.Write(writer, binary.BigEndian, uint8(1)) - if err != nil { - return err - } - return writer.Flush() -} - func (s *CommandServer) handleLogConn(conn net.Conn) error { var ( interval int64 @@ -67,8 +58,24 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error { } 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 = writeLog(writer, savedLines) + err = writer.WriteByte(0) + if err != nil { + return err + } + err = varbin.Write(writer, binary.BigEndian, savedLines) if err != nil { return err } @@ -76,11 +83,15 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error { 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 = writeClearLog(writer) + err = writer.WriteByte(1) if err != nil { return err } @@ -99,7 +110,11 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error { break loopLogs } } - err = writeLog(writer, logLines) + err = writer.WriteByte(0) + if err != nil { + return err + } + err = varbin.Write(writer, binary.BigEndian, logLines) if err != nil { return err } @@ -110,8 +125,7 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error { func (c *CommandClient) handleLogConn(conn net.Conn) { reader := bufio.NewReader(conn) for { - var messageType uint8 - err := binary.Read(reader, binary.BigEndian, &messageType) + messageType, err := reader.ReadByte() if err != nil { c.handler.Disconnected(err.Error()) return @@ -119,7 +133,7 @@ func (c *CommandClient) handleLogConn(conn net.Conn) { var messages []string switch messageType { case 0: - err = binary.ReadData(reader, binary.BigEndian, &messages) + err = varbin.Read(reader, binary.BigEndian, &messages) if err != nil { c.handler.Disconnected(err.Error()) return diff --git a/experimental/libbox/command_power.go b/experimental/libbox/command_power.go index 619cb57b50..5ed7b01428 100644 --- a/experimental/libbox/command_power.go +++ b/experimental/libbox/command_power.go @@ -5,7 +5,7 @@ import ( "net" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) func (c *CommandClient) ServiceReload() error { @@ -24,7 +24,7 @@ func (c *CommandClient) ServiceReload() error { return err } if hasError { - errorMessage, err := rw.ReadVString(conn) + errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian) if err != nil { return err } @@ -40,7 +40,7 @@ func (s *CommandServer) handleServiceReload(conn net.Conn) error { return err } if rErr != nil { - return rw.WriteVString(conn, rErr.Error()) + return varbin.Write(conn, binary.BigEndian, rErr.Error()) } return nil } @@ -61,7 +61,7 @@ func (c *CommandClient) ServiceClose() error { return nil } if hasError { - errorMessage, err := rw.ReadVString(conn) + errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian) if err != nil { return nil } @@ -78,7 +78,7 @@ func (s *CommandServer) handleServiceClose(conn net.Conn) error { return err } if rErr != nil { - return rw.WriteVString(conn, rErr.Error()) + return varbin.Write(conn, binary.BigEndian, rErr.Error()) } return nil } diff --git a/experimental/libbox/command_select.go b/experimental/libbox/command_select.go index e7d5b08f86..e1e67e6068 100644 --- a/experimental/libbox/command_select.go +++ b/experimental/libbox/command_select.go @@ -6,7 +6,7 @@ import ( "github.com/sagernet/sing-box/outbound" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error { @@ -19,11 +19,11 @@ func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) erro if err != nil { return err } - err = rw.WriteVString(conn, groupTag) + err = varbin.Write(conn, binary.BigEndian, groupTag) if err != nil { return err } - err = rw.WriteVString(conn, outboundTag) + err = varbin.Write(conn, binary.BigEndian, outboundTag) if err != nil { return err } @@ -31,11 +31,11 @@ func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) erro } func (s *CommandServer) handleSelectOutbound(conn net.Conn) error { - groupTag, err := rw.ReadVString(conn) + groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian) if err != nil { return err } - outboundTag, err := rw.ReadVString(conn) + outboundTag, err := varbin.ReadValue[string](conn, binary.BigEndian) if err != nil { return err } diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 8918756dae..f913191d6a 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -66,14 +66,6 @@ func (s *CommandServer) SetService(newService *BoxService) { s.notifyURLTestUpdate() } -func (s *CommandServer) ResetLog() { - s.savedLines.Init() - select { - case s.logReset <- struct{}{}: - default: - } -} - func (s *CommandServer) notifyURLTestUpdate() { select { case s.urlTestUpdate <- struct{}{}: diff --git a/experimental/libbox/command_shared.go b/experimental/libbox/command_shared.go index ecad78dd8a..b98c2e5d3c 100644 --- a/experimental/libbox/command_shared.go +++ b/experimental/libbox/command_shared.go @@ -5,7 +5,7 @@ import ( "io" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) func readError(reader io.Reader) error { @@ -15,7 +15,7 @@ func readError(reader io.Reader) error { return err } if hasError { - errorMessage, err := rw.ReadVString(reader) + errorMessage, err := varbin.ReadValue[string](reader, binary.BigEndian) if err != nil { return err } @@ -30,7 +30,7 @@ func writeError(writer io.Writer, wErr error) error { return err } if wErr != nil { - err = rw.WriteVString(writer, wErr.Error()) + err = varbin.Write(writer, binary.BigEndian, wErr.Error()) if err != nil { return err } diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go index 19ddf3da3c..6feda3f8ef 100644 --- a/experimental/libbox/command_urltest.go +++ b/experimental/libbox/command_urltest.go @@ -11,7 +11,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/batch" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" "github.com/sagernet/sing/service" ) @@ -25,7 +25,7 @@ func (c *CommandClient) URLTest(groupTag string) error { if err != nil { return err } - err = rw.WriteVString(conn, groupTag) + err = varbin.Write(conn, binary.BigEndian, groupTag) if err != nil { return err } @@ -33,7 +33,7 @@ func (c *CommandClient) URLTest(groupTag string) error { } func (s *CommandServer) handleURLTest(conn net.Conn) error { - groupTag, err := rw.ReadVString(conn) + groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian) if err != nil { return err } diff --git a/experimental/libbox/profile_import.go b/experimental/libbox/profile_import.go index 75ddb06dcf..258c175a9f 100644 --- a/experimental/libbox/profile_import.go +++ b/experimental/libbox/profile_import.go @@ -1,13 +1,13 @@ package libbox import ( + "bufio" "bytes" "compress/gzip" "encoding/binary" - "io" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) func EncodeChunkedMessage(data []byte) []byte { @@ -35,13 +35,13 @@ type ErrorMessage struct { func (e *ErrorMessage) Encode() []byte { var buffer bytes.Buffer buffer.WriteByte(MessageTypeError) - rw.WriteVString(&buffer, e.Message) + varbin.Write(&buffer, binary.BigEndian, e.Message) return buffer.Bytes() } func DecodeErrorMessage(data []byte) (*ErrorMessage, error) { reader := bytes.NewReader(data) - messageType, err := rw.ReadByte(reader) + messageType, err := reader.ReadByte() if err != nil { return nil, err } @@ -49,7 +49,7 @@ func DecodeErrorMessage(data []byte) (*ErrorMessage, error) { return nil, E.New("invalid message") } var message ErrorMessage - message.Message, err = rw.ReadVString(reader) + message.Message, err = varbin.ReadValue[string](reader, binary.BigEndian) if err != nil { return nil, err } @@ -87,7 +87,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) - rw.WriteVString(&buffer, preview.Name) + varbin.Write(&buffer, binary.BigEndian, preview.Name) binary.Write(&buffer, binary.BigEndian, preview.Type) } return buffer.Bytes() @@ -117,7 +117,7 @@ func (d *ProfileDecoder) Decode(data []byte) error { if err != nil { return err } - profile.Name, err = rw.ReadVString(reader) + profile.Name, err = varbin.ReadValue[string](reader, binary.BigEndian) if err != nil { return err } @@ -147,7 +147,7 @@ func (r *ProfileContentRequest) Encode() []byte { func DecodeProfileContentRequest(data []byte) (*ProfileContentRequest, error) { reader := bytes.NewReader(data) - messageType, err := rw.ReadByte(reader) + messageType, err := reader.ReadByte() if err != nil { return nil, err } @@ -176,12 +176,13 @@ func (c *ProfileContent) Encode() []byte { buffer := new(bytes.Buffer) buffer.WriteByte(MessageTypeProfileContent) buffer.WriteByte(1) - writer := gzip.NewWriter(buffer) - rw.WriteVString(writer, c.Name) + gWriter := gzip.NewWriter(buffer) + writer := bufio.NewWriter(gWriter) + varbin.Write(writer, binary.BigEndian, c.Name) binary.Write(writer, binary.BigEndian, c.Type) - rw.WriteVString(writer, c.Config) + varbin.Write(writer, binary.BigEndian, c.Config) if c.Type != ProfileTypeLocal { - rw.WriteVString(writer, c.RemotePath) + varbin.Write(writer, binary.BigEndian, c.RemotePath) } if c.Type == ProfileTypeRemote { binary.Write(writer, binary.BigEndian, c.AutoUpdate) @@ -189,29 +190,31 @@ func (c *ProfileContent) Encode() []byte { binary.Write(writer, binary.BigEndian, c.LastUpdated) } writer.Flush() - writer.Close() + gWriter.Flush() + gWriter.Close() return buffer.Bytes() } func DecodeProfileContent(data []byte) (*ProfileContent, error) { - var reader io.Reader = bytes.NewReader(data) - messageType, err := rw.ReadByte(reader) + reader := bytes.NewReader(data) + messageType, err := reader.ReadByte() if err != nil { return nil, err } if messageType != MessageTypeProfileContent { return nil, E.New("invalid message") } - version, err := rw.ReadByte(reader) + version, err := reader.ReadByte() if err != nil { return nil, err } - reader, err = gzip.NewReader(reader) + gReader, err := gzip.NewReader(reader) if err != nil { return nil, E.Cause(err, "unsupported profile") } + bReader := varbin.StubReader(gReader) var content ProfileContent - content.Name, err = rw.ReadVString(reader) + content.Name, err = varbin.ReadValue[string](bReader, binary.BigEndian) if err != nil { return nil, err } @@ -219,12 +222,12 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) { if err != nil { return nil, err } - content.Config, err = rw.ReadVString(reader) + content.Config, err = varbin.ReadValue[string](bReader, binary.BigEndian) if err != nil { return nil, err } if content.Type != ProfileTypeLocal { - content.RemotePath, err = rw.ReadVString(reader) + content.RemotePath, err = varbin.ReadValue[string](bReader, binary.BigEndian) if err != nil { return nil, err } diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index 31611354bf..ac67db38f2 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -3,6 +3,7 @@ package libbox import ( "os" "os/user" + "runtime/debug" "strconv" "time" @@ -21,6 +22,11 @@ var ( sTVOS bool ) +func init() { + debug.SetPanicOnFault(true) + debug.SetTraceback("all") +} + func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) { sBasePath = basePath sWorkingPath = workingPath diff --git a/go.mod b/go.mod index d471b4433b..6346e38ab2 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f github.com/sagernet/quic-go v0.47.0-beta.2 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.4.3 + github.com/sagernet/sing v0.5.0-beta.2 github.com/sagernet/sing-dns v0.2.3 github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-quic v0.2.2 diff --git a/go.sum b/go.sum index e6147304a0..1301c07286 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYi 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.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8= -github.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= +github.com/sagernet/sing v0.5.0-beta.2 h1:V12EpwtsgYo5OLGjAiGoJobDJZeUsKv0b5y+yGAM6W0= +github.com/sagernet/sing v0.5.0-beta.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k= github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= diff --git a/inbound/mixed.go b/inbound/mixed.go index 982842efeb..3933f7af05 100644 --- a/inbound/mixed.go +++ b/inbound/mixed.go @@ -12,10 +12,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/protocol/http" "github.com/sagernet/sing/protocol/socks" "github.com/sagernet/sing/protocol/socks/socks4" @@ -51,16 +48,17 @@ func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogg } func (h *Mixed) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - headerType, err := rw.ReadByte(conn) + reader := std_bufio.NewReader(conn) + headerBytes, err := reader.Peek(1) if err != nil { return err } - switch headerType { + switch headerBytes[0] { case socks4.Version, socks5.Version: - return socks.HandleConnection0(ctx, conn, headerType, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) + return socks.HandleConnection0(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) + default: + return http.HandleConnection(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) } - reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType}))) - return http.HandleConnection(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) } func (h *Mixed) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { diff --git a/inbound/vless.go b/inbound/vless.go index 69ed042bc3..567475647a 100644 --- a/inbound/vless.go +++ b/inbound/vless.go @@ -83,12 +83,11 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg } func (h *VLESS) Start() error { - err := common.Start( - h.service, - h.tlsConfig, - ) - if err != nil { - return err + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return err + } } if h.transport == nil { return h.myInboundAdapter.Start() diff --git a/inbound/vmess.go b/inbound/vmess.go index 70676bbd8c..154512751c 100644 --- a/inbound/vmess.go +++ b/inbound/vmess.go @@ -93,13 +93,16 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg } func (h *VMess) Start() error { - err := common.Start( - h.service, - h.tlsConfig, - ) + err := h.service.Start() if err != nil { return err } + if h.tlsConfig != nil { + err = h.tlsConfig.Start() + if err != nil { + return err + } + } if h.transport == nil { return h.myInboundAdapter.Start() } diff --git a/option/outbound.go b/option/outbound.go index 59ee85ab69..6c943cd9af 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -113,7 +113,7 @@ type DialerOptions struct { Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"` Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"` ProtectPath string `json:"protect_path,omitempty"` - RoutingMark int `json:"routing_mark,omitempty"` + RoutingMark uint32 `json:"routing_mark,omitempty"` ReuseAddr bool `json:"reuse_addr,omitempty"` ConnectTimeout Duration `json:"connect_timeout,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"` diff --git a/option/route.go b/option/route.go index e313fcf242..dfd72986ff 100644 --- a/option/route.go +++ b/option/route.go @@ -10,7 +10,7 @@ type RouteOptions struct { AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` DefaultInterface string `json:"default_interface,omitempty"` - DefaultMark int `json:"default_mark,omitempty"` + DefaultMark uint32 `json:"default_mark,omitempty"` } type GeoIPOptions struct { diff --git a/outbound/proxy.go b/outbound/proxy.go index 6127f0f215..fbc4848180 100644 --- a/outbound/proxy.go +++ b/outbound/proxy.go @@ -1,7 +1,6 @@ package outbound import ( - std_bufio "bufio" "context" "crypto/rand" "encoding/hex" @@ -11,16 +10,10 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/protocol/http" "github.com/sagernet/sing/protocol/socks" - "github.com/sagernet/sing/protocol/socks/socks4" - "github.com/sagernet/sing/protocol/socks/socks5" ) type ProxyListener struct { @@ -102,16 +95,7 @@ func (l *ProxyListener) acceptLoop() { } func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error { - headerType, err := rw.ReadByte(conn) - if err != nil { - return err - } - switch headerType { - case socks4.Version, socks5.Version: - return socks.HandleConnection0(ctx, conn, headerType, l.authenticator, l, M.Metadata{}) - } - reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType}))) - return http.HandleConnection(ctx, conn, reader, l.authenticator, l, M.Metadata{}) + return socks.HandleConnection(ctx, conn, l.authenticator, l, M.Metadata{}) } func (l *ProxyListener) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error { diff --git a/outbound/tor.go b/outbound/tor.go index 76c7955da7..8ae73a66fa 100644 --- a/outbound/tor.go +++ b/outbound/tor.go @@ -44,10 +44,10 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger startConf.ExtraArgs = options.ExtraArgs if options.DataDirectory != "" { dataDirAbs, _ := filepath.Abs(startConf.DataDir) - if geoIPPath := filepath.Join(dataDirAbs, "geoip"); rw.FileExists(geoIPPath) && !common.Contains(options.ExtraArgs, "--GeoIPFile") { + if geoIPPath := filepath.Join(dataDirAbs, "geoip"); rw.IsFile(geoIPPath) && !common.Contains(options.ExtraArgs, "--GeoIPFile") { options.ExtraArgs = append(options.ExtraArgs, "--GeoIPFile", geoIPPath) } - if geoIP6Path := filepath.Join(dataDirAbs, "geoip6"); rw.FileExists(geoIP6Path) && !common.Contains(options.ExtraArgs, "--GeoIPv6File") { + if geoIP6Path := filepath.Join(dataDirAbs, "geoip6"); rw.IsFile(geoIP6Path) && !common.Contains(options.ExtraArgs, "--GeoIPv6File") { options.ExtraArgs = append(options.ExtraArgs, "--GeoIPv6File", geoIP6Path) } } @@ -58,8 +58,12 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger } if startConf.DataDir != "" { torrcFile := filepath.Join(startConf.DataDir, "torrc") - if !rw.FileExists(torrcFile) { - err := rw.WriteFile(torrcFile, []byte("")) + err := rw.MkdirParent(torrcFile) + if err != nil { + return nil, err + } + if !rw.IsFile(torrcFile) { + err := os.WriteFile(torrcFile, []byte(""), 0o600) if err != nil { return nil, err } diff --git a/route/router.go b/route/router.go index a8698264cb..3a89568518 100644 --- a/route/router.go +++ b/route/router.go @@ -82,7 +82,7 @@ type Router struct { interfaceFinder *control.DefaultInterfaceFinder autoDetectInterface bool defaultInterface string - defaultMark int + defaultMark uint32 networkMonitor tun.NetworkUpdateMonitor interfaceMonitor tun.DefaultInterfaceMonitor packageManager tun.PackageManager @@ -1171,7 +1171,7 @@ func (r *Router) DefaultInterface() string { return r.defaultInterface } -func (r *Router) DefaultMark() int { +func (r *Router) DefaultMark() uint32 { return r.defaultMark } diff --git a/route/router_geo_resources.go b/route/router_geo_resources.go index e0a572c92f..14364d210d 100644 --- a/route/router_geo_resources.go +++ b/route/router_geo_resources.go @@ -50,7 +50,7 @@ func (r *Router) prepareGeoIPDatabase() error { geoPath = foundPath } } - if !rw.FileExists(geoPath) { + if !rw.IsFile(geoPath) { geoPath = filemanager.BasePath(r.ctx, geoPath) } if stat, err := os.Stat(geoPath); err == nil { @@ -61,7 +61,7 @@ func (r *Router) prepareGeoIPDatabase() error { os.Remove(geoPath) } } - if !rw.FileExists(geoPath) { + if !rw.IsFile(geoPath) { r.logger.Warn("geoip database not exists: ", geoPath) var err error for attempts := 0; attempts < 3; attempts++ { @@ -96,7 +96,7 @@ func (r *Router) prepareGeositeDatabase() error { geoPath = foundPath } } - if !rw.FileExists(geoPath) { + if !rw.IsFile(geoPath) { geoPath = filemanager.BasePath(r.ctx, geoPath) } if stat, err := os.Stat(geoPath); err == nil { @@ -107,7 +107,7 @@ func (r *Router) prepareGeositeDatabase() error { os.Remove(geoPath) } } - if !rw.FileExists(geoPath) { + if !rw.IsFile(geoPath) { r.logger.Warn("geosite database not exists: ", geoPath) var err error for attempts := 0; attempts < 3; attempts++ { diff --git a/route/rule_abstract.go b/route/rule_abstract.go index c13bdd8d96..9ef2e93277 100644 --- a/route/rule_abstract.go +++ b/route/rule_abstract.go @@ -29,9 +29,13 @@ func (r *abstractDefaultRule) Type() string { func (r *abstractDefaultRule) Start() error { for _, item := range r.allItems { - err := common.Start(item) - if err != nil { - return err + if starter, isStarter := item.(interface { + Start() error + }); isStarter { + err := starter.Start() + if err != nil { + return err + } } } return nil @@ -183,8 +187,13 @@ func (r *abstractLogicalRule) UpdateGeosite() error { } func (r *abstractLogicalRule) Start() error { - for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (common.Starter, bool) { - rule, loaded := it.(common.Starter) + for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (interface { + Start() error + }, bool, + ) { + rule, loaded := it.(interface { + Start() error + }) return rule, loaded }) { err := rule.Start() diff --git a/transport/trojan/mux.go b/transport/trojan/mux.go index 13ac1e83a9..b1cc9985c9 100644 --- a/transport/trojan/mux.go +++ b/transport/trojan/mux.go @@ -1,12 +1,14 @@ package trojan import ( + std_bufio "bufio" "context" "net" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/task" "github.com/sagernet/smux" ) @@ -33,27 +35,36 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata return group.Run(ctx) } -func newMuxConnection(ctx context.Context, stream net.Conn, metadata M.Metadata, handler Handler) { - err := newMuxConnection0(ctx, stream, metadata, handler) +func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) { + err := newMuxConnection0(ctx, conn, metadata, handler) if err != nil { handler.NewError(ctx, E.Cause(err, "process trojan-go multiplex connection")) } } -func newMuxConnection0(ctx context.Context, stream net.Conn, metadata M.Metadata, handler Handler) error { - command, err := rw.ReadByte(stream) +func newMuxConnection0(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) error { + reader := std_bufio.NewReader(conn) + command, err := reader.ReadByte() if err != nil { return E.Cause(err, "read command") } - metadata.Destination, err = M.SocksaddrSerializer.ReadAddrPort(stream) + metadata.Destination, err = M.SocksaddrSerializer.ReadAddrPort(reader) if err != nil { return E.Cause(err, "read destination") } + if reader.Buffered() > 0 { + buffer := buf.NewSize(reader.Buffered()) + _, err = buffer.ReadFullFrom(reader, buffer.Len()) + if err != nil { + return err + } + conn = bufio.NewCachedConn(conn, buffer) + } switch command { case CommandTCP: - return handler.NewConnection(ctx, stream, metadata) + return handler.NewConnection(ctx, conn, metadata) case CommandUDP: - return handler.NewPacketConnection(ctx, &PacketConn{Conn: stream}, metadata) + return handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata) default: return E.New("unknown command ", command) } diff --git a/transport/trojan/service.go b/transport/trojan/service.go index 9078276c73..97f674ab93 100644 --- a/transport/trojan/service.go +++ b/transport/trojan/service.go @@ -2,6 +2,7 @@ package trojan import ( "context" + "encoding/binary" "net" "github.com/sagernet/sing/common/auth" @@ -76,7 +77,8 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata return E.Cause(err, "skip crlf") } - command, err := rw.ReadByte(conn) + var command byte + err = binary.Read(conn, binary.BigEndian, &command) if err != nil { return E.Cause(err, "read command") } diff --git a/transport/v2raygrpc/conn.go b/transport/v2raygrpc/conn.go index 0fecbf33d3..bc78f91e3c 100644 --- a/transport/v2raygrpc/conn.go +++ b/transport/v2raygrpc/conn.go @@ -8,7 +8,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/baderror" M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/rw" + N "github.com/sagernet/sing/common/network" ) var _ net.Conn = (*GRPCConn)(nil) @@ -90,7 +90,7 @@ func (c *GRPCConn) Upstream() any { return c.GunService } -var _ rw.WriteCloser = (*clientConnWrapper)(nil) +var _ N.WriteCloser = (*clientConnWrapper)(nil) type clientConnWrapper struct { GunService_TunClient diff --git a/transport/v2raygrpclite/conn.go b/transport/v2raygrpclite/conn.go index f5a71939d3..5ab02569fd 100644 --- a/transport/v2raygrpclite/conn.go +++ b/transport/v2raygrpclite/conn.go @@ -13,7 +13,7 @@ import ( "github.com/sagernet/sing/common/baderror" "github.com/sagernet/sing/common/buf" M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) // kanged from: https://github.com/Qv2ray/gun-lite @@ -96,7 +96,7 @@ func (c *GunConn) read(b []byte) (n int, err error) { } func (c *GunConn) Write(b []byte) (n int, err error) { - varLen := rw.UVariantLen(uint64(len(b))) + varLen := varbin.UvarintLen(uint64(len(b))) buffer := buf.NewSize(6 + varLen + len(b)) header := buffer.Extend(6 + varLen) header[0] = 0x00 @@ -117,13 +117,13 @@ func (c *GunConn) Write(b []byte) (n int, err error) { func (c *GunConn) WriteBuffer(buffer *buf.Buffer) error { defer buffer.Release() dataLen := buffer.Len() - varLen := rw.UVariantLen(uint64(dataLen)) + varLen := varbin.UvarintLen(uint64(dataLen)) header := buffer.ExtendHeader(6 + varLen) header[0] = 0x00 binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen)) header[5] = 0x0A binary.PutUvarint(header[6:], uint64(dataLen)) - err := rw.WriteBytes(c.writer, buffer.Bytes()) + err := common.Error(c.writer.Write(buffer.Bytes())) if err != nil { return baderror.WrapH2(err) } From 2f776168ded508cc423e2afca5e145ade3e2f978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 19 Aug 2024 07:34:11 +0800 Subject: [PATCH 0048/1272] Implement read deadline for QUIC based UDP inbounds --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6346e38ab2..7f3610d48d 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/sagernet/sing v0.5.0-beta.2 github.com/sagernet/sing-dns v0.2.3 github.com/sagernet/sing-mux v0.2.0 - github.com/sagernet/sing-quic v0.2.2 + github.com/sagernet/sing-quic v0.3.0-beta.3 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 diff --git a/go.sum b/go.sum index 1301c07286..40318265fe 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,8 @@ github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= -github.com/sagernet/sing-quic v0.2.2 h1:Ryp02zMhHh/ZDrG7MdLsmhuBU8+BEpOdJonFQiqIopo= -github.com/sagernet/sing-quic v0.2.2/go.mod h1:YLV1dUDv8Eyp/8e55O/EvfsrwxOgEDVgDCIoPqmDREE= +github.com/sagernet/sing-quic v0.3.0-beta.3 h1:8S98VXZxtSiOqVCFbCNbMEvKDPhOF/VNBYMjVC3xMhw= +github.com/sagernet/sing-quic v0.3.0-beta.3/go.mod h1:rFPUlYnSj1Bx9gFSghjCqrCzfGvpjhkisOiTKpjq5vQ= 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= From 9b8d6c1b73330287cdd83603ba9256000df0d140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 17 Jul 2024 17:57:35 +0800 Subject: [PATCH 0049/1272] Bump rule-set version --- cmd/sing-box/cmd_geoip_export.go | 2 +- cmd/sing-box/cmd_geosite_export.go | 2 +- cmd/sing-box/cmd_rule_set_compile.go | 6 +- cmd/sing-box/cmd_rule_set_upgrade.go | 91 ++++++++++++++++++++ common/srs/binary.go | 38 ++++---- constant/rule.go | 6 +- docs/configuration/rule-set/source-format.md | 21 ++++- option/rule_set.go | 6 +- route/rule_item_domain.go | 2 +- 9 files changed, 145 insertions(+), 29 deletions(-) create mode 100644 cmd/sing-box/cmd_rule_set_upgrade.go diff --git a/cmd/sing-box/cmd_geoip_export.go b/cmd/sing-box/cmd_geoip_export.go index 5787d2e5ad..b80e5cd3d0 100644 --- a/cmd/sing-box/cmd_geoip_export.go +++ b/cmd/sing-box/cmd_geoip_export.go @@ -87,7 +87,7 @@ func geoipExport(countryCode string) error { headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String()) } var plainRuleSet option.PlainRuleSetCompat - plainRuleSet.Version = C.RuleSetVersion1 + plainRuleSet.Version = C.RuleSetVersion2 plainRuleSet.Options.Rules = []option.HeadlessRule{ { Type: C.RuleTypeDefault, diff --git a/cmd/sing-box/cmd_geosite_export.go b/cmd/sing-box/cmd_geosite_export.go index 2a6c27a0ed..90a7955b99 100644 --- a/cmd/sing-box/cmd_geosite_export.go +++ b/cmd/sing-box/cmd_geosite_export.go @@ -70,7 +70,7 @@ func geositeExport(category string) error { headlessRule.DomainKeyword = defaultRule.DomainKeyword headlessRule.DomainRegex = defaultRule.DomainRegex var plainRuleSet option.PlainRuleSetCompat - plainRuleSet.Version = C.RuleSetVersion1 + plainRuleSet.Version = C.RuleSetVersion2 plainRuleSet.Options.Rules = []option.HeadlessRule{ { Type: C.RuleTypeDefault, diff --git a/cmd/sing-box/cmd_rule_set_compile.go b/cmd/sing-box/cmd_rule_set_compile.go index 6e065101a8..7e3753c928 100644 --- a/cmd/sing-box/cmd_rule_set_compile.go +++ b/cmd/sing-box/cmd_rule_set_compile.go @@ -6,6 +6,7 @@ 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/common/json" @@ -55,9 +56,6 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - if err != nil { - return err - } ruleSet := plainRuleSet.Upgrade() var outputPath string if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput { @@ -73,7 +71,7 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - err = srs.Write(outputFile, ruleSet) + err = srs.Write(outputFile, ruleSet, plainRuleSet.Version == C.RuleSetVersion2) if err != nil { outputFile.Close() os.Remove(outputPath) diff --git a/cmd/sing-box/cmd_rule_set_upgrade.go b/cmd/sing-box/cmd_rule_set_upgrade.go new file mode 100644 index 0000000000..0ec039fd71 --- /dev/null +++ b/cmd/sing-box/cmd_rule_set_upgrade.go @@ -0,0 +1,91 @@ +package main + +import ( + "bytes" + "io" + "os" + "path/filepath" + + 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/json" + + "github.com/spf13/cobra" +) + +var commandRuleSetUpgradeFlagWrite bool + +var commandRuleSetUpgrade = &cobra.Command{ + Use: "upgrade ", + Short: "Upgrade rule-set json", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + err := upgradeRuleSet(args[0]) + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandRuleSetUpgrade.Flags().BoolVarP(&commandRuleSetUpgradeFlagWrite, "write", "w", false, "write result to (source) file instead of stdout") + commandRuleSet.AddCommand(commandRuleSetUpgrade) +} + +func upgradeRuleSet(sourcePath string) error { + var ( + reader io.Reader + err error + ) + if sourcePath == "stdin" { + reader = os.Stdin + } else { + reader, err = os.Open(sourcePath) + if err != nil { + return err + } + } + content, err := io.ReadAll(reader) + if err != nil { + return err + } + plainRuleSetCompat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content) + if err != nil { + return err + } + switch plainRuleSetCompat.Version { + case C.RuleSetVersion1: + default: + log.Info("already up-to-date") + return nil + } + plainRuleSet := plainRuleSetCompat.Upgrade() + buffer := new(bytes.Buffer) + encoder := json.NewEncoder(buffer) + encoder.SetIndent("", " ") + err = encoder.Encode(plainRuleSet) + if err != nil { + return E.Cause(err, "encode config") + } + outputPath, _ := filepath.Abs(sourcePath) + if !commandRuleSetUpgradeFlagWrite || sourcePath == "stdin" { + os.Stdout.WriteString(buffer.String() + "\n") + return nil + } + if bytes.Equal(content, buffer.Bytes()) { + return nil + } + output, err := os.Create(sourcePath) + if err != nil { + return E.Cause(err, "open output") + } + _, err = output.Write(buffer.Bytes()) + output.Close() + if err != nil { + return E.Cause(err, "write output") + } + os.Stderr.WriteString(outputPath + "\n") + return nil +} diff --git a/common/srs/binary.go b/common/srs/binary.go index c7c55e0838..0bd5a9099b 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -54,14 +54,14 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro if err != nil { return ruleSet, err } - if version != 1 { + if version > C.RuleSetVersion2 { return ruleSet, E.New("unsupported version: ", version) } - zReader, err := zlib.NewReader(reader) + compressReader, err := zlib.NewReader(reader) if err != nil { return } - bReader := bufio.NewReader(zReader) + bReader := bufio.NewReader(compressReader) length, err := binary.ReadUvarint(bReader) if err != nil { return @@ -77,26 +77,32 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro return } -func Write(writer io.Writer, ruleSet option.PlainRuleSet) error { +func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) error { _, err := writer.Write(MagicBytes[:]) if err != nil { return err } - err = binary.Write(writer, binary.BigEndian, uint8(1)) + var version uint8 + if generateUnstable { + version = C.RuleSetVersion2 + } else { + version = C.RuleSetVersion1 + } + err = binary.Write(writer, binary.BigEndian, version) if err != nil { return err } - zWriter, err := zlib.NewWriterLevel(writer, zlib.BestCompression) + compressWriter, err := zlib.NewWriterLevel(writer, zlib.BestCompression) if err != nil { return err } - bWriter := bufio.NewWriter(zWriter) + bWriter := bufio.NewWriter(compressWriter) _, err = varbin.WriteUvarint(bWriter, uint64(len(ruleSet.Rules))) if err != nil { return err } for _, rule := range ruleSet.Rules { - err = writeRule(bWriter, rule) + err = writeRule(bWriter, rule, generateUnstable) if err != nil { return err } @@ -105,7 +111,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet) error { if err != nil { return err } - return zWriter.Close() + return compressWriter.Close() } func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err error) { @@ -127,12 +133,12 @@ func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err return } -func writeRule(writer varbin.Writer, rule option.HeadlessRule) error { +func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateUnstable bool) error { switch rule.Type { case C.RuleTypeDefault: - return writeDefaultRule(writer, rule.DefaultOptions) + return writeDefaultRule(writer, rule.DefaultOptions, generateUnstable) case C.RuleTypeLogical: - return writeLogicalRule(writer, rule.LogicalOptions) + return writeLogicalRule(writer, rule.LogicalOptions, generateUnstable) default: panic("unknown rule type: " + rule.Type) } @@ -219,7 +225,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea } } -func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule) error { +func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateUnstable bool) error { err := binary.Write(writer, binary.BigEndian, uint8(0)) if err != nil { return err @@ -243,7 +249,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule) err if err != nil { return err } - err = domain.NewMatcher(rule.Domain, rule.DomainSuffix).Write(writer) + err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, !generateUnstable).Write(writer) if err != nil { return err } @@ -420,7 +426,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo return } -func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule) error { +func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateUnstable bool) error { err := binary.Write(writer, binary.BigEndian, uint8(1)) if err != nil { return err @@ -441,7 +447,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu return err } for _, rule := range logicalRule.Rules { - err = writeRule(writer, rule) + err = writeRule(writer, rule, generateUnstable) if err != nil { return err } diff --git a/constant/rule.go b/constant/rule.go index 5a8eaf127f..fb0f39e22e 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -13,7 +13,11 @@ const ( const ( RuleSetTypeLocal = "local" RuleSetTypeRemote = "remote" - RuleSetVersion1 = 1 RuleSetFormatSource = "source" RuleSetFormatBinary = "binary" ) + +const ( + RuleSetVersion1 = 1 + iota + RuleSetVersion2 +) diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index ee5e48e04c..8c46cbf02b 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -1,12 +1,20 @@ +--- +icon: material/new-box +--- + # Source Format +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: version `2` + !!! question "Since sing-box 1.8.0" ### Structure ```json { - "version": 1, + "version": 2, "rules": [] } ``` @@ -21,7 +29,16 @@ Use `sing-box rule-set compile [--output .srs] .json` to c ==Required== -Version of Rule Set, must be `1`. +Version of rule-set, one of `1` or `2`. + +* 1: Initial rule-set version, since sing-box 1.8.0. +* 2: Optimized memory usages of `domain_suffix` rules. + +The new rule-set version `2` does not make any changes to the format, only affecting `binary` rule-sets compiled by command `rule-set compile` + +Since 1.10.0, the optimization is always applied to `source` rule-sets even if version is set to `1`. + +It is recommended to upgrade to `2` after sing-box 1.10.0 becomes a stable version. #### rules diff --git a/option/rule_set.go b/option/rule_set.go index 8e367e69a5..5498400f2f 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -185,7 +185,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { var v any switch r.Version { - case C.RuleSetVersion1: + case C.RuleSetVersion1, C.RuleSetVersion2: v = r.Options default: return nil, E.New("unknown rule set version: ", r.Version) @@ -200,7 +200,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { } var v any switch r.Version { - case C.RuleSetVersion1: + case C.RuleSetVersion1, C.RuleSetVersion2: v = &r.Options case 0: return E.New("missing rule set version") @@ -217,7 +217,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { func (r PlainRuleSetCompat) Upgrade() PlainRuleSet { var result PlainRuleSet switch r.Version { - case C.RuleSetVersion1: + case C.RuleSetVersion1, C.RuleSetVersion2: result = r.Options default: panic("unknown rule set version: " + F.ToString(r.Version)) diff --git a/route/rule_item_domain.go b/route/rule_item_domain.go index 36839a5556..c77890df24 100644 --- a/route/rule_item_domain.go +++ b/route/rule_item_domain.go @@ -38,7 +38,7 @@ func NewDomainItem(domains []string, domainSuffixes []string) *DomainItem { } } return &DomainItem{ - domain.NewMatcher(domains, domainSuffixes), + domain.NewMatcher(domains, domainSuffixes, false), description, } } From f4f5a3c925d4fb05e9a1dd33b7740e82d96ca545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 22 Jul 2024 12:10:22 +0800 Subject: [PATCH 0050/1272] Improve usages of `json.Unmarshal` --- option/json.go | 6 +----- option/types.go | 4 ++-- option/udp_over_tcp.go | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/option/json.go b/option/json.go index 07580e9f7b..775141d5aa 100644 --- a/option/json.go +++ b/option/json.go @@ -1,8 +1,6 @@ package option import ( - "bytes" - "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -69,7 +67,5 @@ func UnmarshallExcluded(inputContent []byte, parentObject any, object any) error if err != nil { return err } - decoder := json.NewDecoder(bytes.NewReader(inputContent)) - decoder.DisallowUnknownFields() - return decoder.Decode(object) + return json.UnmarshalDisallowUnknownFields(inputContent, object) } diff --git a/option/types.go b/option/types.go index 83fee8f05a..b17f222248 100644 --- a/option/types.go +++ b/option/types.go @@ -128,12 +128,12 @@ func (l Listable[T]) MarshalJSON() ([]byte, error) { } func (l *Listable[T]) UnmarshalJSON(content []byte) error { - err := json.Unmarshal(content, (*[]T)(l)) + err := json.UnmarshalDisallowUnknownFields(content, (*[]T)(l)) if err == nil { return nil } var singleItem T - newError := json.Unmarshal(content, &singleItem) + newError := json.UnmarshalDisallowUnknownFields(content, &singleItem) if newError != nil { return E.Errors(err, newError) } diff --git a/option/udp_over_tcp.go b/option/udp_over_tcp.go index e8a7a9726e..b496017a41 100644 --- a/option/udp_over_tcp.go +++ b/option/udp_over_tcp.go @@ -26,5 +26,5 @@ func (o *UDPOverTCPOptions) UnmarshalJSON(bytes []byte) error { if err == nil { return nil } - return json.Unmarshal(bytes, (*_UDPOverTCPOptions)(o)) + return json.UnmarshalDisallowUnknownFields(bytes, (*_UDPOverTCPOptions)(o)) } From dcb0141646563a55a01871909de260bf260df949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 3 Jul 2024 19:42:06 +0800 Subject: [PATCH 0051/1272] Add IP address support for `rule-set match` match --- cmd/sing-box/cmd_rule_set_match.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index 08784caf11..fb2560afb1 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -14,6 +14,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + M "github.com/sagernet/sing/common/metadata" "github.com/spf13/cobra" ) @@ -21,8 +22,8 @@ import ( var flagRuleSetMatchFormat string var commandRuleSetMatch = &cobra.Command{ - Use: "match ", - Short: "Check if a domain matches the rule set", + Use: "match ", + Short: "Check if an IP address or a domain matches the rule set", Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { err := ruleSetMatch(args[0], args[1]) @@ -71,15 +72,20 @@ func ruleSetMatch(sourcePath string, domain string) error { default: return E.New("unknown rule set format: ", flagRuleSetMatchFormat) } + ipAddress := M.ParseAddr(domain) + var metadata adapter.InboundContext + if ipAddress.IsValid() { + metadata.Destination = M.SocksaddrFrom(ipAddress, 0) + } else { + metadata.Domain = domain + } for i, ruleOptions := range plainRuleSet.Rules { var currentRule adapter.HeadlessRule currentRule, err = route.NewHeadlessRule(nil, ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } - if currentRule.Match(&adapter.InboundContext{ - Domain: domain, - }) { + if currentRule.Match(&metadata) { println(F.ToString("match rules.[", i, "]: ", currentRule)) } } From fec38f85cd2a89fb2c8a270b4930fd5e97c643a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 3 Jul 2024 19:42:21 +0800 Subject: [PATCH 0052/1272] Add `rule-set decompile` command --- cmd/sing-box/cmd_rule_set_decompile.go | 83 ++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 cmd/sing-box/cmd_rule_set_decompile.go diff --git a/cmd/sing-box/cmd_rule_set_decompile.go b/cmd/sing-box/cmd_rule_set_decompile.go new file mode 100644 index 0000000000..02af03dd67 --- /dev/null +++ b/cmd/sing-box/cmd_rule_set_decompile.go @@ -0,0 +1,83 @@ +package main + +import ( + "io" + "os" + "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/common/json" + + "github.com/spf13/cobra" +) + +var flagRuleSetDecompileOutput string + +const flagRuleSetDecompileDefaultOutput = ".json" + +var commandRuleSetDecompile = &cobra.Command{ + Use: "decompile [binary-path]", + Short: "Decompile rule-set binary to json", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + err := decompileRuleSet(args[0]) + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandRuleSet.AddCommand(commandRuleSetDecompile) + commandRuleSetDecompile.Flags().StringVarP(&flagRuleSetDecompileOutput, "output", "o", flagRuleSetDecompileDefaultOutput, "Output file") +} + +func decompileRuleSet(sourcePath string) error { + var ( + reader io.Reader + err error + ) + if sourcePath == "stdin" { + reader = os.Stdin + } else { + reader, err = os.Open(sourcePath) + if err != nil { + return err + } + } + plainRuleSet, err := srs.Read(reader, true) + if err != nil { + return err + } + ruleSet := option.PlainRuleSetCompat{ + Version: C.RuleSetVersion1, + Options: plainRuleSet, + } + var outputPath string + if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput { + if strings.HasSuffix(sourcePath, ".srs") { + outputPath = sourcePath[:len(sourcePath)-4] + ".json" + } else { + outputPath = sourcePath + ".json" + } + } else { + outputPath = flagRuleSetDecompileOutput + } + outputFile, err := os.Create(outputPath) + if err != nil { + return err + } + encoder := json.NewEncoder(outputFile) + encoder.SetIndent("", " ") + err = encoder.Encode(ruleSet) + if err != nil { + outputFile.Close() + os.Remove(outputPath) + return err + } + outputFile.Close() + return nil +} From db3a0c636d4652c4336d5652aeed1b2b409e5d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 7 Jun 2024 15:55:21 +0800 Subject: [PATCH 0053/1272] Add auto-redirect & Improve auto-route --- Dockerfile | 2 +- adapter/router.go | 15 ++ box.go | 28 ++- cmd/sing-box/cmd_tools_fetch.go | 32 ++- cmd/sing-box/cmd_tools_fetch_http3.go | 36 +++ cmd/sing-box/cmd_tools_fetch_http3_stub.go | 18 ++ common/dialer/default.go | 18 +- docs/configuration/inbound/tun.md | 144 +++++++++++- docs/configuration/inbound/tun.zh.md | 147 ++++++++++++- docs/deprecated.md | 8 + docs/deprecated.zh.md | 8 + docs/migration.md | 68 ++++++ docs/migration.zh.md | 68 ++++++ go.mod | 19 +- go.sum | 40 ++-- inbound/tun.go | 243 ++++++++++++++++++--- option/tun.go | 63 ++++-- route/router.go | 52 ++++- route/rule_item_rule_set.go | 1 + route/rule_set.go | 21 ++ route/rule_set_local.go | 59 ++++- route/rule_set_remote.go | 74 ++++++- 22 files changed, 1045 insertions(+), 119 deletions(-) create mode 100644 cmd/sing-box/cmd_tools_fetch_http3.go create mode 100644 cmd/sing-box/cmd_tools_fetch_http3_stub.go diff --git a/Dockerfile b/Dockerfile index 22f2dfb289..e82b81cee9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ FROM --platform=$TARGETPLATFORM alpine AS dist LABEL maintainer="nekohasekai " RUN set -ex \ && apk upgrade \ - && apk add bash tzdata ca-certificates \ + && apk add bash tzdata ca-certificates nftables \ && rm -rf /var/cache/apk/* COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box ENTRYPOINT ["sing-box"] diff --git a/adapter/router.go b/adapter/router.go index c481f0c8c9..619c1110cb 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -10,15 +10,18 @@ import ( "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" mdns "github.com/miekg/dns" + "go4.org/netipx" ) type Router interface { Service PreStarter PostStarter + Cleanup() error Outbounds() []Outbound Outbound(tag string) (Outbound, bool) @@ -46,6 +49,8 @@ type Router interface { AutoDetectInterface() bool AutoDetectInterfaceFunc() control.Func DefaultMark() uint32 + RegisterAutoRedirectOutputMark(mark uint32) error + AutoRedirectOutputMark() uint32 NetworkMonitor() tun.NetworkUpdateMonitor InterfaceMonitor() tun.DefaultInterfaceMonitor PackageManager() tun.PackageManager @@ -92,12 +97,22 @@ type DNSRule interface { } type RuleSet interface { + Name() string StartContext(ctx context.Context, startContext RuleSetStartContext) error + PostStart() error Metadata() RuleSetMetadata + ExtractIPSet() []*netipx.IPSet + IncRef() + DecRef() + Cleanup() + RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback] + UnregisterCallback(element *list.Element[RuleSetUpdateCallback]) Close() error HeadlessRule } +type RuleSetUpdateCallback func(it RuleSet) + type RuleSetMetadata struct { ContainsProcessRule bool ContainsWIFIRule bool diff --git a/box.go b/box.go index 3c514cfee2..716b1b093c 100644 --- a/box.go +++ b/box.go @@ -303,7 +303,11 @@ func (s *Box) start() error { return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") } } - return s.postStart() + err = s.postStart() + if err != nil { + return err + } + return s.router.Cleanup() } func (s *Box) postStart() error { @@ -313,16 +317,28 @@ func (s *Box) postStart() error { return E.Cause(err, "start ", serviceName) } } - for _, outbound := range s.outbounds { - if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound { + // TODO: reorganize ALL start order + for _, out := range s.outbounds { + if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound { err := lateOutbound.PostStart() if err != nil { - return E.Cause(err, "post-start outbound/", outbound.Tag()) + return E.Cause(err, "post-start outbound/", out.Tag()) } } } - - return s.router.PostStart() + err := s.router.PostStart() + if err != nil { + return err + } + for _, in := range s.inbounds { + if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound { + err = lateInbound.PostStart() + if err != nil { + return E.Cause(err, "post-start inbound/", in.Tag()) + } + } + } + return nil } func (s *Box) Close() error { diff --git a/cmd/sing-box/cmd_tools_fetch.go b/cmd/sing-box/cmd_tools_fetch.go index 256c3f42b6..3f62424a49 100644 --- a/cmd/sing-box/cmd_tools_fetch.go +++ b/cmd/sing-box/cmd_tools_fetch.go @@ -9,8 +9,10 @@ import ( "net/url" "os" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/spf13/cobra" @@ -32,7 +34,10 @@ func init() { commandTools.AddCommand(commandFetch) } -var httpClient *http.Client +var ( + httpClient *http.Client + http3Client *http.Client +) func fetch(args []string) error { instance, err := createPreStartedClient() @@ -53,8 +58,16 @@ func fetch(args []string) error { }, } defer httpClient.CloseIdleConnections() + if C.WithQUIC { + err = initializeHTTP3Client(instance) + if err != nil { + return err + } + defer http3Client.CloseIdleConnections() + } for _, urlString := range args { - parsedURL, err := url.Parse(urlString) + var parsedURL *url.URL + parsedURL, err = url.Parse(urlString) if err != nil { return err } @@ -63,16 +76,27 @@ func fetch(args []string) error { parsedURL.Scheme = "http" fallthrough case "http", "https": - err = fetchHTTP(parsedURL) + err = fetchHTTP(httpClient, parsedURL) + if err != nil { + return err + } + case "http3": + if !C.WithQUIC { + return C.ErrQUICNotIncluded + } + parsedURL.Scheme = "https" + err = fetchHTTP(http3Client, parsedURL) if err != nil { return err } + default: + return E.New("unsupported scheme: ", parsedURL.Scheme) } } return nil } -func fetchHTTP(parsedURL *url.URL) error { +func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error { request, err := http.NewRequest("GET", parsedURL.String(), nil) if err != nil { return err diff --git a/cmd/sing-box/cmd_tools_fetch_http3.go b/cmd/sing-box/cmd_tools_fetch_http3.go new file mode 100644 index 0000000000..5dc3d9157f --- /dev/null +++ b/cmd/sing-box/cmd_tools_fetch_http3.go @@ -0,0 +1,36 @@ +//go:build with_quic + +package main + +import ( + "context" + "crypto/tls" + "net/http" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + box "github.com/sagernet/sing-box" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func initializeHTTP3Client(instance *box.Box) error { + dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound) + if err != nil { + return err + } + http3Client = &http.Client{ + Transport: &http3.RoundTripper{ + Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + destination := M.ParseSocksaddr(addr) + udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination) + if dErr != nil { + return nil, dErr + } + return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg) + }, + }, + } + return nil +} diff --git a/cmd/sing-box/cmd_tools_fetch_http3_stub.go b/cmd/sing-box/cmd_tools_fetch_http3_stub.go new file mode 100644 index 0000000000..ae13f54c42 --- /dev/null +++ b/cmd/sing-box/cmd_tools_fetch_http3_stub.go @@ -0,0 +1,18 @@ +//go:build !with_quic + +package main + +import ( + "net/url" + "os" + + box "github.com/sagernet/sing-box" +) + +func initializeHTTP3Client(instance *box.Box) error { + return os.ErrInvalid +} + +func fetchHTTP3(parsedURL *url.URL) error { + return os.ErrInvalid +} diff --git a/common/dialer/default.go b/common/dialer/default.go index 4fbad07deb..488e8000eb 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -50,12 +50,26 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) } - if options.RoutingMark != 0 { + var autoRedirectOutputMark uint32 + if router != nil { + autoRedirectOutputMark = router.AutoRedirectOutputMark() + } + if autoRedirectOutputMark > 0 { + dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark)) + listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark)) + } + if options.RoutingMark > 0 { dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) - } else if router != nil && router.DefaultMark() != 0 { + if autoRedirectOutputMark > 0 { + return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`") + } + } else if router != nil && router.DefaultMark() > 0 { dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark())) listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark())) + if autoRedirectOutputMark > 0 { + return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`") + } } if options.ReuseAddr { listener.Control = control.Append(listener.Control, control.ReuseAddr()) diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 1d5d8d0f65..1e2bf40058 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -2,6 +2,21 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: [address](#address) + :material-delete-clock: [inet4_address](#inet4_address) + :material-delete-clock: [inet6_address](#inet6_address) + :material-plus: [route_address](#route_address) + :material-delete-clock: [inet4_route_address](#inet4_route_address) + :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-plus: [auto_redirect](#auto_redirect) + :material-plus: [route_address_set](#route_address_set) + :material-plus: [route_exclude_address_set](#route_address_set) + !!! quote "Changes in sing-box 1.9.0" :material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain) @@ -23,26 +38,57 @@ icon: material/new-box "type": "tun", "tag": "tun-in", "interface_name": "tun0", - "inet4_address": "172.19.0.1/30", - "inet6_address": "fdfe:dcba:9876::1/126", + "address": [ + "172.18.0.1/30", + "fdfe:dcba:9876::1/126" + ], + // deprecated + "inet4_address": [ + "172.19.0.1/30" + ], + // deprecated + "inet6_address": [ + "fdfe:dcba:9876::1/126" + ], "mtu": 9000, "gso": false, "auto_route": true, "strict_route": true, + "auto_redirect": false, + "route_address": [ + "0.0.0.0/1", + "128.0.0.0/1", + "::/1", + "8000::/1" + ], + // deprecated "inet4_route_address": [ "0.0.0.0/1", "128.0.0.0/1" ], + // deprecated "inet6_route_address": [ "::/1", "8000::/1" ], + "route_exclude_address": [ + "192.168.0.0/16", + "fc00::/7" + ], + // deprecated "inet4_route_exclude_address": [ "192.168.0.0/16" ], + // deprecated "inet6_route_exclude_address": [ "fc00::/7" ], + "route_address_set": [ + "geoip-cloudflare" + ], + "route_exclude_address_set": [ + "geoip-cn" + ], "endpoint_independent_nat": false, "udp_timeout": "5m", "stack": "system", @@ -102,14 +148,26 @@ icon: material/new-box Virtual device name, automatically selected if empty. +#### address + +!!! question "Since sing-box 1.10.0" + +IPv4 and IPv6 prefix for the tun interface. + #### inet4_address -==Required== +!!! failure "Deprecated in sing-box 1.10.0" + + `inet4_address` is merged to `address` and will be removed in sing-box 1.11.0. IPv4 prefix for the tun interface. #### inet6_address +!!! failure "Deprecated in sing-box 1.10.0" + + `inet6_address` is merged to `address` and will be removed in sing-box 1.11.0. + IPv6 prefix for the tun interface. #### mtu @@ -145,9 +203,10 @@ Enforce strict routing rules when `auto_route` is enabled: *In Linux*: * Let unsupported network unreachable +* Make ICMP traffic route to tun instead of upstream interfaces * Route all connections to tun -It prevents address leaks and makes DNS hijacking work on Android. +It prevents IP address leaks and makes DNS hijacking work on Android. *In Windows*: @@ -156,22 +215,95 @@ It prevents address leaks and makes DNS hijacking work on Android. It may prevent some applications (such as VirtualBox) from working properly in certain situations. +#### auto_redirect + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux with `auto_route` enabled. + +Automatically configure iptables/nftables to redirect connections. + +*In Android*: + +Only local connections are forwarded. To share your VPN connection over hotspot or repeater, +use [VPNHotspot](https://github.com/Mygod/VPNHotspot). + +*In Linux*: + +`auto_route` with `auto_redirect` now works as expected on routers **without intervention**. + +#### route_address + +!!! question "Since sing-box 1.10.0" + +Use custom routes instead of default when `auto_route` is enabled. + #### inet4_route_address +!!! failure "Deprecated in sing-box 1.10.0" + + `inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead. + Use custom routes instead of default when `auto_route` is enabled. #### inet6_route_address +!!! failure "Deprecated in sing-box 1.10.0" + + `inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead. + Use custom routes instead of default when `auto_route` is enabled. +#### route_exclude_address + +!!! question "Since sing-box 1.10.0" + +Exclude custom routes when `auto_route` is enabled. + #### inet4_route_exclude_address +!!! failure "Deprecated in sing-box 1.10.0" + + `inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead. + Exclude custom routes when `auto_route` is enabled. #### inet6_route_exclude_address +!!! failure "Deprecated in sing-box 1.10.0" + + `inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead. + Exclude custom routes when `auto_route` is enabled. +#### route_address_set + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled. + +Add the destination IP CIDR rules in the specified rule-sets to the firewall. +Unmatched traffic will bypass the sing-box routes. + +Conflict with `route.default_mark` and `[dialOptions].routing_mark`. + +#### route_exclude_address_set + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled. + +Add the destination IP CIDR rules in the specified rule-sets to the firewall. +Matched traffic will bypass the sing-box routes. + +Conflict with `route.default_mark` and `[dialOptions].routing_mark`. + #### endpoint_independent_nat !!! info "" @@ -214,6 +346,10 @@ Conflict with `exclude_interface`. #### exclude_interface +!!! warning "" + + When `strict_route` enabled, return traffic to excluded interfaces will not be automatically excluded, so add them as well (example: `br-lan` and `pppoe-wan`). + Exclude interfaces in route. Conflict with `include_interface`. diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 73d31d6497..5b1d35afb5 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -2,6 +2,21 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: [address](#address) + :material-delete-clock: [inet4_address](#inet4_address) + :material-delete-clock: [inet6_address](#inet6_address) + :material-plus: [route_address](#route_address) + :material-delete-clock: [inet4_route_address](#inet4_route_address) + :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-plus: [auto_redirect](#auto_redirect) + :material-plus: [route_address_set](#route_address_set) + :material-plus: [route_exclude_address_set](#route_address_set) + !!! quote "sing-box 1.9.0 中的更改" :material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain) @@ -23,26 +38,57 @@ icon: material/new-box "type": "tun", "tag": "tun-in", "interface_name": "tun0", - "inet4_address": "172.19.0.1/30", - "inet6_address": "fdfe:dcba:9876::1/126", + "address": [ + "172.18.0.1/30", + "fdfe:dcba:9876::1/126" + ], + // 已弃用 + "inet4_address": [ + "172.19.0.1/30" + ], + // 已弃用 + "inet6_address": [ + "fdfe:dcba:9876::1/126" + ], "mtu": 9000, "gso": false, "auto_route": true, "strict_route": true, + "auto_redirect": false, + "route_address": [ + "0.0.0.0/1", + "128.0.0.0/1", + "::/1", + "8000::/1" + ], + // 已弃用 "inet4_route_address": [ "0.0.0.0/1", "128.0.0.0/1" ], + // 已弃用 "inet6_route_address": [ "::/1", "8000::/1" ], + "route_exclude_address": [ + "192.168.0.0/16", + "fc00::/7" + ], + // 已弃用 "inet4_route_exclude_address": [ "192.168.0.0/16" ], + // 已弃用 "inet6_route_exclude_address": [ "fc00::/7" ], + "route_address_set": [ + "geoip-cloudflare" + ], + "route_exclude_address_set": [ + "geoip-cn" + ], "endpoint_independent_nat": false, "udp_timeout": "5m", "stack": "system", @@ -102,14 +148,30 @@ icon: material/new-box 虚拟设备名称,默认自动选择。 +#### address + +!!! question "自 sing-box 1.10.0 起" + +==必填== + +tun 接口的 IPv4 和 IPv6 前缀。 + #### inet4_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除. + ==必填== tun 接口的 IPv4 前缀。 #### inet6_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除. + tun 接口的 IPv6 前缀。 #### mtu @@ -145,9 +207,10 @@ tun 接口的 IPv6 前缀。 *在 Linux 中*: * 让不支持的网络无法到达 +* 使 ICMP 流量路由到 tun 而不是上游接口 * 将所有连接路由到 tun -它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作。 +它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。 *在 Windows 中*: @@ -157,22 +220,94 @@ tun 接口的 IPv6 前缀。 它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。 +#### auto_redirect + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux。 + +自动配置 iptables 以重定向 TCP 连接。 + +*在 Android 中*: + +仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。 + +*在 Linux 中*: + +带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。 + +#### route_address + +!!! question "自 sing-box 1.10.0 起" + +设置到 Tun 的自定义路由。 + #### inet4_route_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除. + 启用 `auto_route` 时使用自定义路由而不是默认路由。 #### inet6_route_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除. + 启用 `auto_route` 时使用自定义路由而不是默认路由。 +#### route_exclude_address + +!!! question "自 sing-box 1.10.0 起" + +设置到 Tun 的排除自定义路由。 + #### inet4_route_exclude_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除. + 启用 `auto_route` 时排除自定义路由。 #### inet6_route_exclude_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除. + 启用 `auto_route` 时排除自定义路由。 +#### route_address_set + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + +将指定规则集中的目标 IP CIDR 规则添加到防火墙。 +不匹配的流量将绕过 sing-box 路由。 + +与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 + +#### route_exclude_address_set + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + +将指定规则集中的目标 IP CIDR 规则添加到防火墙。 +匹配的流量将绕过 sing-box 路由。 + +与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 + #### endpoint_independent_nat 启用独立于端点的 NAT。 @@ -211,6 +346,10 @@ TCP/IP 栈。 #### exclude_interface +!!! warning "" + + 当 `strict_route` 启用,到被排除接口的回程流量将不会被自动排除,因此也要添加它们(例:`br-lan` 与 `pppoe-wan`)。 + 排除路由的接口。 与 `include_interface` 冲突。 @@ -284,7 +423,7 @@ TCP/IP 栈。 !!! note "" - 在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**. + 在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**. 绕过代理的主机名列表。 diff --git a/docs/deprecated.md b/docs/deprecated.md index 439bf7e8f5..249bc492a2 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -6,6 +6,14 @@ icon: material/delete-alert ## 1.10.0 +#### TUN address fields are merged + +`inet4_address` and `inet6_address` are merged into `address`, +`inet4_route_address` and `inet6_route_address` are merged into `route_address`, +`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. + +Old fields are deprecated and will be removed in sing-box 1.11.0. + #### Drop support for go1.18 and go1.19 Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile. diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 76e7e7684c..6815e9fc8b 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -6,6 +6,14 @@ icon: material/delete-alert ## 1.10.0 +#### TUN 地址字段已合并 + +`inet4_address` 和 `inet6_address` 已合并为 `address`, +`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`, +`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。 + +旧字段已废弃,且将在 sing-box 1.11.0 中移除。 + #### 移除对 go1.18 和 go1.19 的支持 由于维护困难,sing-box 1.10.0 要求至少 Go 1.20 才能编译。 diff --git a/docs/migration.md b/docs/migration.md index 6ea0de4927..017ed2302e 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -2,6 +2,74 @@ icon: material/arrange-bring-forward --- +## 1.10.0 + +### TUN address fields are merged + +`inet4_address` and `inet6_address` are merged into `address`, +`inet4_route_address` and `inet6_route_address` are merged into `route_address`, +`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. + +Old fields are deprecated and will be removed in sing-box 1.11.0. + +!!! info "References" + + [TUN](/configuration/inbound/tun/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "inbounds": [ + { + "type": "tun", + "inet4_address": "172.19.0.1/30", + "inet6_address": "fdfe:dcba:9876::1/126", + "inet4_route_address": [ + "0.0.0.0/1", + "128.0.0.0/1" + ], + "inet6_route_address": [ + "::/1", + "8000::/1" + ], + "inet4_route_exclude_address": [ + "192.168.0.0/16" + ], + "inet6_route_exclude_address": [ + "fc00::/7" + ] + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "inbounds": [ + { + "type": "tun", + "address": [ + "172.19.0.1/30", + "fdfe:dcba:9876::1/126" + ], + "route_address": [ + "0.0.0.0/1", + "128.0.0.0/1", + "::/1", + "8000::/1" + ], + "route_exclude_address": [ + "192.168.0.0/16", + "fc00::/7" + ] + } + ] + } + ``` + ## 1.9.5 ### Bundle Identifier updates in Apple platform clients diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 2cbbba6f4b..a8a0774045 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -2,6 +2,74 @@ icon: material/arrange-bring-forward --- +## 1.10.0 + +### TUN 地址字段已合并 + +`inet4_address` 和 `inet6_address` 已合并为 `address`, +`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`, +`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。 + +旧字段已废弃,且将在 sing-box 1.11.0 中移除。 + +!!! info "参考" + + [TUN](/zh/configuration/inbound/tun/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "inbounds": [ + { + "type": "tun", + "inet4_address": "172.19.0.1/30", + "inet6_address": "fdfe:dcba:9876::1/126", + "inet4_route_address": [ + "0.0.0.0/1", + "128.0.0.0/1" + ], + "inet6_route_address": [ + "::/1", + "8000::/1" + ], + "inet4_route_exclude_address": [ + "192.168.0.0/16" + ], + "inet6_route_exclude_address": [ + "fc00::/7" + ] + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "inbounds": [ + { + "type": "tun", + "address": [ + "172.19.0.1/30", + "fdfe:dcba:9876::1/126" + ], + "route_address": [ + "0.0.0.0/1", + "128.0.0.0/1", + "::/1", + "8000::/1" + ], + "route_exclude_address": [ + "192.168.0.0/16", + "fc00::/7" + ] + } + ] + } + ``` + ## 1.9.5 ### Apple 平台客户端的 Bundle Identifier 更新 diff --git a/go.mod b/go.mod index 7f3610d48d..8f219f8df7 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.3.3 + github.com/sagernet/sing-tun v0.4.0-beta.16 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.5.4 @@ -44,8 +44,8 @@ require ( github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.23.0 - golang.org/x/net v0.25.0 + golang.org/x/crypto v0.24.0 + golang.org/x/net v0.26.0 golang.org/x/sys v0.25.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 @@ -65,6 +65,7 @@ require ( github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -72,24 +73,28 @@ require ( github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/libdns/libdns v0.2.2 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.4.1 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect - github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect + github.com/sagernet/fswatch v0.1.1 // 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.5 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect + github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 40318265fe..b3d96a4594 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,7 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/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/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -69,6 +70,10 @@ github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/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.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U= github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= @@ -97,12 +102,16 @@ 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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= +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.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY= github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +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.47.0-beta.2 h1:1tCGWFOSaXIeuQaHrwOMJIYvlupjTcaVInGQw5ArULU= github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYigzQ5lQu3Nh4wNh8Q= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= @@ -122,8 +131,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.3.3 h1:LZnQNmfGcNG2KPTPkLgc+Lo7k606QJVkPp2DnjriwUk= -github.com/sagernet/sing-tun v0.3.3/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= +github.com/sagernet/sing-tun v0.4.0-beta.16 h1:05VdL5BZiKLQsDNrpdXMseSO1NwPfl9Y4o76PqAd9sY= +github.com/sagernet/sing-tun v0.4.0-beta.16/go.mod h1:81JwnnYw8X9W9XvmZetSTTiPgIE3SbAbnc+EHKwPJ5U= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= @@ -146,8 +155,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= @@ -163,20 +172,19 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -187,7 +195,7 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= 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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= @@ -195,8 +203,8 @@ golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.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.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= diff --git a/inbound/tun.go b/inbound/tun.go index 7bd700d348..cb6a02c301 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -3,6 +3,9 @@ package inbound import ( "context" "net" + "net/netip" + "os" + "runtime" "strconv" "strings" "time" @@ -19,27 +22,91 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ranges" + "github.com/sagernet/sing/common/x/list" + + "go4.org/netipx" ) var _ adapter.Inbound = (*Tun)(nil) type Tun struct { - tag string - ctx context.Context - router adapter.Router - logger log.ContextLogger - inboundOptions option.InboundOptions - tunOptions tun.Options - endpointIndependentNat bool - udpTimeout int64 - stack string - tunIf tun.Tun - tunStack tun.Stack - platformInterface platform.Interface - platformOptions option.TunPlatformOptions + tag string + ctx context.Context + router adapter.Router + logger log.ContextLogger + inboundOptions option.InboundOptions + tunOptions tun.Options + endpointIndependentNat bool + udpTimeout int64 + stack string + tunIf tun.Tun + tunStack tun.Stack + platformInterface platform.Interface + platformOptions option.TunPlatformOptions + autoRedirect tun.AutoRedirect + routeRuleSet []adapter.RuleSet + routeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback] + routeExcludeRuleSet []adapter.RuleSet + routeExcludeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback] + routeAddressSet []*netipx.IPSet + routeExcludeAddressSet []*netipx.IPSet } func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) { + address := options.Address + //nolint:staticcheck + //goland:noinspection GoDeprecation + if len(options.Inet4Address) > 0 { + address = append(address, options.Inet4Address...) + } + //nolint:staticcheck + //goland:noinspection GoDeprecation + if len(options.Inet6Address) > 0 { + address = append(address, options.Inet6Address...) + } + inet4Address := common.Filter(address, func(it netip.Prefix) bool { + return it.Addr().Is4() + }) + inet6Address := common.Filter(address, func(it netip.Prefix) bool { + return it.Addr().Is6() + }) + + routeAddress := options.RouteAddress + //nolint:staticcheck + //goland:noinspection GoDeprecation + if len(options.Inet4RouteAddress) > 0 { + routeAddress = append(routeAddress, options.Inet4RouteAddress...) + } + //nolint:staticcheck + //goland:noinspection GoDeprecation + if len(options.Inet6RouteAddress) > 0 { + routeAddress = append(routeAddress, options.Inet6RouteAddress...) + } + inet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool { + return it.Addr().Is4() + }) + inet6RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool { + return it.Addr().Is6() + }) + + routeExcludeAddress := options.RouteExcludeAddress + //nolint:staticcheck + //goland:noinspection GoDeprecation + if len(options.Inet4RouteExcludeAddress) > 0 { + routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...) + } + //nolint:staticcheck + //goland:noinspection GoDeprecation + if len(options.Inet6RouteExcludeAddress) > 0 { + routeExcludeAddress = append(routeExcludeAddress, options.Inet6RouteExcludeAddress...) + } + inet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool { + return it.Addr().Is4() + }) + inet6RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool { + return it.Addr().Is6() + }) + tunMTU := options.MTU if tunMTU == 0 { tunMTU = 9000 @@ -50,9 +117,9 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger } else { udpTimeout = C.UDPTimeout } + var err error includeUID := uidToRange(options.IncludeUID) if len(options.IncludeUIDRange) > 0 { - var err error includeUID, err = parseRange(includeUID, options.IncludeUIDRange) if err != nil { return nil, E.Cause(err, "parse include_uid_range") @@ -60,13 +127,30 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger } excludeUID := uidToRange(options.ExcludeUID) if len(options.ExcludeUIDRange) > 0 { - var err error excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange) if err != nil { return nil, E.Cause(err, "parse exclude_uid_range") } } - return &Tun{ + + tableIndex := options.IPRoute2TableIndex + if tableIndex == 0 { + tableIndex = tun.DefaultIPRoute2TableIndex + } + ruleIndex := options.IPRoute2RuleIndex + if ruleIndex == 0 { + ruleIndex = tun.DefaultIPRoute2RuleIndex + } + inputMark := options.AutoRedirectInputMark + if inputMark == 0 { + inputMark = tun.DefaultAutoRedirectInputMark + } + outputMark := options.AutoRedirectOutputMark + if outputMark == 0 { + outputMark = tun.DefaultAutoRedirectOutputMark + } + + inbound := &Tun{ tag: tag, ctx: ctx, router: router, @@ -76,30 +160,83 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger Name: options.InterfaceName, MTU: tunMTU, GSO: options.GSO, - Inet4Address: options.Inet4Address, - Inet6Address: options.Inet6Address, + Inet4Address: inet4Address, + Inet6Address: inet6Address, AutoRoute: options.AutoRoute, + IPRoute2TableIndex: tableIndex, + IPRoute2RuleIndex: ruleIndex, + AutoRedirectInputMark: inputMark, + AutoRedirectOutputMark: outputMark, StrictRoute: options.StrictRoute, IncludeInterface: options.IncludeInterface, ExcludeInterface: options.ExcludeInterface, - Inet4RouteAddress: options.Inet4RouteAddress, - Inet6RouteAddress: options.Inet6RouteAddress, - Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress, - Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress, + Inet4RouteAddress: inet4RouteAddress, + Inet6RouteAddress: inet6RouteAddress, + Inet4RouteExcludeAddress: inet4RouteExcludeAddress, + Inet6RouteExcludeAddress: inet6RouteExcludeAddress, IncludeUID: includeUID, ExcludeUID: excludeUID, IncludeAndroidUser: options.IncludeAndroidUser, IncludePackage: options.IncludePackage, ExcludePackage: options.ExcludePackage, InterfaceMonitor: router.InterfaceMonitor(), - TableIndex: 2022, }, endpointIndependentNat: options.EndpointIndependentNat, udpTimeout: int64(udpTimeout.Seconds()), stack: options.Stack, platformInterface: platformInterface, platformOptions: common.PtrValueOrDefault(options.Platform), - }, nil + } + if options.AutoRedirect { + if !options.AutoRoute { + return nil, E.New("`auto_route` is required by `auto_redirect`") + } + disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES")) + inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{ + TunOptions: &inbound.tunOptions, + Context: ctx, + Handler: inbound, + Logger: logger, + NetworkMonitor: router.NetworkMonitor(), + InterfaceFinder: router.InterfaceFinder(), + TableName: "sing-box", + DisableNFTables: dErr == nil && disableNFTables, + RouteAddressSet: &inbound.routeAddressSet, + RouteExcludeAddressSet: &inbound.routeExcludeAddressSet, + }) + if err != nil { + return nil, E.Cause(err, "initialize auto-redirect") + } + if runtime.GOOS != "android" { + var markMode bool + for _, routeAddressSet := range options.RouteAddressSet { + ruleSet, loaded := router.RuleSet(routeAddressSet) + if !loaded { + return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet) + } + ruleSet.IncRef() + inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet) + markMode = true + } + for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet { + ruleSet, loaded := router.RuleSet(routeExcludeAddressSet) + if !loaded { + return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet) + } + ruleSet.IncRef() + inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet) + markMode = true + } + if markMode { + inbound.tunOptions.AutoRedirectMarkMode = true + err = router.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) + if err != nil { + return nil, err + } + } + } + } + return inbound, nil } func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] { @@ -121,11 +258,11 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges. } var start, end uint64 var err error - start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32) + start, err = strconv.ParseUint(uidRange[:subIndex], 0, 32) if err != nil { return nil, E.Cause(err, "parse range start") } - end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32) + end, err = strconv.ParseUint(uidRange[subIndex+1:], 0, 32) if err != nil { return nil, E.Cause(err, "parse range end") } @@ -200,10 +337,58 @@ func (t *Tun) Start() error { return nil } +func (t *Tun) PostStart() error { + monitor := taskmonitor.New(t.logger, C.StartTimeout) + if t.autoRedirect != nil { + t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) + for _, routeRuleSet := range t.routeRuleSet { + ipSets := routeRuleSet.ExtractIPSet() + if len(ipSets) == 0 { + t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name()) + } + t.routeAddressSet = append(t.routeAddressSet, ipSets...) + } + t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) + for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { + ipSets := routeExcludeRuleSet.ExtractIPSet() + if len(ipSets) == 0 { + t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name()) + } + t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...) + } + monitor.Start("initialize auto-redirect") + err := t.autoRedirect.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "auto-redirect") + } + for _, routeRuleSet := range t.routeRuleSet { + t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + routeRuleSet.DecRef() + } + for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { + t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + routeExcludeRuleSet.DecRef() + } + t.routeAddressSet = nil + t.routeExcludeAddressSet = nil + } + return nil +} + +func (t *Tun) updateRouteAddressSet(it adapter.RuleSet) { + t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) + t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) + t.autoRedirect.UpdateRouteAddressSet() + t.routeAddressSet = nil + t.routeExcludeAddressSet = nil +} + func (t *Tun) Close() error { return common.Close( t.tunStack, t.tunIf, + t.autoRedirect, ) } @@ -215,7 +400,11 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata metadata.Source = upstreamMetadata.Source metadata.Destination = upstreamMetadata.Destination metadata.InboundOptions = t.inboundOptions - t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + if upstreamMetadata.Protocol != "" { + t.logger.InfoContext(ctx, "inbound ", upstreamMetadata.Protocol, " connection from ", metadata.Source) + } else { + t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + } t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) err := t.router.RouteConnection(ctx, conn, metadata) if err != nil { diff --git a/option/tun.go b/option/tun.go index ac66a8061c..cbc73e7d4b 100644 --- a/option/tun.go +++ b/option/tun.go @@ -3,29 +3,46 @@ package option import "net/netip" type TunInboundOptions struct { - InterfaceName string `json:"interface_name,omitempty"` - MTU uint32 `json:"mtu,omitempty"` - GSO bool `json:"gso,omitempty"` - Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"` - Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"` - AutoRoute bool `json:"auto_route,omitempty"` - StrictRoute bool `json:"strict_route,omitempty"` - Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` - Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` + InterfaceName string `json:"interface_name,omitempty"` + MTU uint32 `json:"mtu,omitempty"` + GSO bool `json:"gso,omitempty"` + Address 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 uint32 `json:"auto_redirect_input_mark,omitempty"` + AutoRedirectOutputMark uint32 `json:"auto_redirect_output_mark,omitempty"` + StrictRoute bool `json:"strict_route,omitempty"` + RouteAddress Listable[netip.Prefix] `json:"route_address,omitempty"` + RouteAddressSet Listable[string] `json:"route_address_set,omitempty"` + RouteExcludeAddress Listable[netip.Prefix] `json:"route_exclude_address,omitempty"` + RouteExcludeAddressSet Listable[string] `json:"route_exclude_address_set,omitempty"` + IncludeInterface Listable[string] `json:"include_interface,omitempty"` + ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"` + IncludeUID Listable[uint32] `json:"include_uid,omitempty"` + IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"` + ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"` + ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"` + IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"` + IncludePackage Listable[string] `json:"include_package,omitempty"` + ExcludePackage Listable[string] `json:"exclude_package,omitempty"` + EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Stack string `json:"stack,omitempty"` + Platform *TunPlatformOptions `json:"platform,omitempty"` + InboundOptions + + // Deprecated: merged to Address + Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"` + // Deprecated: merged to Address + Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"` + // Deprecated: merged to RouteAddress + Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` + // Deprecated: merged to RouteAddress + Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` + // Deprecated: merged to RouteExcludeAddress Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` + // Deprecated: merged to RouteExcludeAddress Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` - IncludeInterface Listable[string] `json:"include_interface,omitempty"` - ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"` - IncludeUID Listable[uint32] `json:"include_uid,omitempty"` - IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"` - ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"` - ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"` - IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"` - IncludePackage Listable[string] `json:"include_package,omitempty"` - ExcludePackage Listable[string] `json:"exclude_package,omitempty"` - EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - Stack string `json:"stack,omitempty"` - Platform *TunPlatformOptions `json:"platform,omitempty"` - InboundOptions } diff --git a/route/router.go b/route/router.go index 3a89568518..3e0e90db88 100644 --- a/route/router.go +++ b/route/router.go @@ -83,6 +83,7 @@ type Router struct { autoDetectInterface bool defaultInterface string defaultMark uint32 + autoRedirectOutputMark uint32 networkMonitor tun.NetworkUpdateMonitor interfaceMonitor tun.DefaultInterfaceMonitor packageManager tun.PackageManager @@ -533,7 +534,10 @@ func (r *Router) Start() error { if C.IsAndroid && r.platformInterface == nil { monitor.Start("initialize package manager") - packageManager, err := tun.NewPackageManager(r) + packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ + Callback: r, + Logger: r.logger, + }) monitor.Finish() if err != nil { return E.Cause(err, "create package manager") @@ -736,10 +740,26 @@ func (r *Router) PostStart() error { return E.Cause(err, "initialize rule[", i, "]") } } + for _, ruleSet := range r.ruleSets { + monitor.Start("post start rule_set[", ruleSet.Name(), "]") + err := ruleSet.PostStart() + monitor.Finish() + if err != nil { + return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]") + } + } r.started = true return nil } +func (r *Router) Cleanup() error { + for _, ruleSet := range r.ruleSetMap { + ruleSet.Cleanup() + } + runtime.GC() + return nil +} + func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { outbound, loaded := r.outboundByTag[tag] return outbound, loaded @@ -993,15 +1013,15 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m } if metadata.InboundOptions.SniffEnabled { sniffMetadata, _ := sniff.PeekPacket( - ctx, - buffer.Bytes(), - sniff.DomainNameQuery, - sniff.QUICClientHello, - sniff.STUNMessage, - sniff.UTP, - sniff.UDPTracker, - sniff.DTLSRecord, - ) + ctx, + buffer.Bytes(), + sniff.DomainNameQuery, + sniff.QUICClientHello, + sniff.STUNMessage, + sniff.UTP, + sniff.UDPTracker, + sniff.DTLSRecord, + ) if sniffMetadata != nil { metadata.Protocol = sniffMetadata.Protocol metadata.Domain = sniffMetadata.Domain @@ -1167,6 +1187,18 @@ func (r *Router) AutoDetectInterfaceFunc() control.Func { } } +func (r *Router) RegisterAutoRedirectOutputMark(mark uint32) error { + if r.autoRedirectOutputMark > 0 { + return E.New("only one auto-redirect can be configured") + } + r.autoRedirectOutputMark = mark + return nil +} + +func (r *Router) AutoRedirectOutputMark() uint32 { + return r.autoRedirectOutputMark +} + func (r *Router) DefaultInterface() string { return r.defaultInterface } diff --git a/route/rule_item_rule_set.go b/route/rule_item_rule_set.go index 482a9c7b45..4ecf8c18f0 100644 --- a/route/rule_item_rule_set.go +++ b/route/rule_item_rule_set.go @@ -32,6 +32,7 @@ func (r *RuleSetItem) Start() error { if !loaded { return E.New("rule-set not found: ", tag) } + ruleSet.IncRef() r.setList = append(r.setList, ruleSet) } return nil diff --git a/route/rule_set.go b/route/rule_set.go index fda3d1056c..7c02aa7a82 100644 --- a/route/rule_set.go +++ b/route/rule_set.go @@ -9,10 +9,13 @@ import ( "github.com/sagernet/sing-box/adapter" 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" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + + "go4.org/netipx" ) func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) { @@ -26,6 +29,24 @@ func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.Contex } } +func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet { + switch rule := rawRule.(type) { + case *DefaultHeadlessRule: + return common.FlatMap(rule.destinationIPCIDRItems, func(rawItem RuleItem) []*netipx.IPSet { + switch item := rawItem.(type) { + case *IPCIDRItem: + return []*netipx.IPSet{item.ipSet} + default: + return nil + } + }) + case *LogicalHeadlessRule: + return common.FlatMap(rule.rules, extractIPSetFromRule) + default: + panic("unexpected rule type") + } +} + var _ adapter.RuleSetStartContext = (*RuleSetStartContext)(nil) type RuleSetStartContext struct { diff --git a/route/rule_set_local.go b/route/rule_set_local.go index 07839c65b3..87a393c27d 100644 --- a/route/rule_set_local.go +++ b/route/rule_set_local.go @@ -9,17 +9,24 @@ import ( "github.com/sagernet/sing-box/common/srs" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/atomic" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service/filemanager" + + "go4.org/netipx" ) var _ adapter.RuleSet = (*LocalRuleSet)(nil) type LocalRuleSet struct { + tag string rules []adapter.HeadlessRule metadata adapter.RuleSetMetadata + refs atomic.Int32 } func NewLocalRuleSet(ctx context.Context, router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) { @@ -59,16 +66,11 @@ func NewLocalRuleSet(ctx context.Context, router adapter.Router, options option. metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) - return &LocalRuleSet{rules, metadata}, nil + return &LocalRuleSet{tag: options.Tag, rules: rules, metadata: metadata}, nil } -func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool { - for _, rule := range s.rules { - if rule.Match(metadata) { - return true - } - } - return false +func (s *LocalRuleSet) Name() string { + return s.tag } func (s *LocalRuleSet) String() string { @@ -79,10 +81,51 @@ func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.Ru return nil } +func (s *LocalRuleSet) PostStart() error { + return nil +} + func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata { return s.metadata } +func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet { + return common.FlatMap(s.rules, extractIPSetFromRule) +} + +func (s *LocalRuleSet) IncRef() { + s.refs.Add(1) +} + +func (s *LocalRuleSet) DecRef() { + if s.refs.Add(-1) < 0 { + panic("rule-set: negative refs") + } +} + +func (s *LocalRuleSet) Cleanup() { + if s.refs.Load() == 0 { + s.rules = nil + } +} + +func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { + return nil +} + +func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { +} + func (s *LocalRuleSet) Close() error { + s.rules = nil return nil } + +func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool { + for _, rule := range s.rules { + if rule.Match(metadata) { + return true + } + } + return false +} diff --git a/route/rule_set_remote.go b/route/rule_set_remote.go index 8389c2f46b..bf0cfe204c 100644 --- a/route/rule_set_remote.go +++ b/route/rule_set_remote.go @@ -8,20 +8,26 @@ import ( "net/http" "runtime" "strings" + "sync" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/srs" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/atomic" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" "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/x/list" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" + + "go4.org/netipx" ) var _ adapter.RuleSet = (*RemoteRuleSet)(nil) @@ -40,6 +46,9 @@ type RemoteRuleSet struct { lastEtag string updateTicker *time.Ticker pauseManager pause.Manager + callbackAccess sync.Mutex + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 } func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { @@ -61,13 +70,8 @@ func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger. } } -func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool { - for _, rule := range s.rules { - if rule.Match(metadata) { - return true - } - } - return false +func (s *RemoteRuleSet) Name() string { + return s.options.Tag } func (s *RemoteRuleSet) String() string { @@ -108,6 +112,10 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.R } } s.updateTicker = time.NewTicker(s.updateInterval) + return nil +} + +func (s *RemoteRuleSet) PostStart() error { go s.loopUpdate() return nil } @@ -116,6 +124,38 @@ func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata { return s.metadata } +func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet { + return common.FlatMap(s.rules, extractIPSetFromRule) +} + +func (s *RemoteRuleSet) IncRef() { + s.refs.Add(1) +} + +func (s *RemoteRuleSet) DecRef() { + if s.refs.Add(-1) < 0 { + panic("rule-set: negative refs") + } +} + +func (s *RemoteRuleSet) Cleanup() { + if s.refs.Load() == 0 { + s.rules = nil + } +} + +func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { + s.callbackAccess.Lock() + defer s.callbackAccess.Unlock() + return s.callbacks.PushBack(callback) +} + +func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { + s.callbackAccess.Lock() + defer s.callbackAccess.Unlock() + s.callbacks.Remove(element) +} + func (s *RemoteRuleSet) loadBytes(content []byte) error { var ( plainRuleSet option.PlainRuleSet @@ -148,6 +188,12 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) s.rules = rules + s.callbackAccess.Lock() + callbacks := s.callbacks.Array() + s.callbackAccess.Unlock() + for _, callback := range callbacks { + callback(s) + } return nil } @@ -156,6 +202,8 @@ func (s *RemoteRuleSet) loopUpdate() { err := s.fetchOnce(s.ctx, nil) if err != nil { s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err) + } else if s.refs.Load() == 0 { + s.rules = nil } } for { @@ -168,6 +216,8 @@ func (s *RemoteRuleSet) loopUpdate() { err := s.fetchOnce(s.ctx, nil) if err != nil { s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err) + } else if s.refs.Load() == 0 { + s.rules = nil } } } @@ -253,7 +303,17 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext adapter.Rule } func (s *RemoteRuleSet) Close() error { + s.rules = nil s.updateTicker.Stop() s.cancel() return nil } + +func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool { + for _, rule := range s.rules { + if rule.Match(metadata) { + return true + } + } + return false +} From 50f07b42f6840b4a16e659c9a12241e0285430be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 7 Jun 2024 15:59:56 +0800 Subject: [PATCH 0054/1272] Improve base DNS transports & Minor fixes --- go.mod | 14 +++++++------- go.sum | 30 +++++++++++++++--------------- route/router_dns.go | 7 ------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 8f219f8df7..ffcd0d796d 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d github.com/mholt/acmez v1.2.0 - github.com/miekg/dns v1.1.59 + github.com/miekg/dns v1.1.61 github.com/ooni/go-libtor v1.1.8 github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a @@ -28,7 +28,7 @@ require ( github.com/sagernet/quic-go v0.47.0-beta.2 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.5.0-beta.2 - github.com/sagernet/sing-dns v0.2.3 + github.com/sagernet/sing-dns v0.3.0-beta.14 github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-quic v0.3.0-beta.3 github.com/sagernet/sing-shadowsocks v0.2.7 @@ -44,8 +44,8 @@ require ( github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.24.0 - golang.org/x/net v0.26.0 + golang.org/x/crypto v0.25.0 + golang.org/x/net v0.27.0 golang.org/x/sys v0.25.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 @@ -89,12 +89,12 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.18.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.19.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/tools v0.23.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index b3d96a4594..6deefbe260 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNv github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= -github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= +github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= +github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= @@ -119,8 +119,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.5.0-beta.2 h1:V12EpwtsgYo5OLGjAiGoJobDJZeUsKv0b5y+yGAM6W0= github.com/sagernet/sing v0.5.0-beta.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k= -github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= +github.com/sagernet/sing-dns v0.3.0-beta.14 h1:/s+fJzYKsvLaNDt/2rjpsrDcN8wmCO2JbX6OFrl8Nww= +github.com/sagernet/sing-dns v0.3.0-beta.14/go.mod h1:rscgSr5ixOPk8XM9ZMLuMXCyldEQ1nLvdl0nfv+lp00= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-quic v0.3.0-beta.3 h1:8S98VXZxtSiOqVCFbCNbMEvKDPhOF/VNBYMjVC3xMhw= @@ -172,16 +172,16 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -195,7 +195,7 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= 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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= @@ -203,8 +203,8 @@ golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= diff --git a/route/router_dns.go b/route/router_dns.go index e0055009b0..51994072ab 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -8,7 +8,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/cache" E "github.com/sagernet/sing/common/exceptions" @@ -125,12 +124,10 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er for { var ( dnsCtx context.Context - cancel context.CancelFunc addressLimit bool ) dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) - dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout) if rule != nil && rule.WithAddressLimit() { addressLimit = true response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool { @@ -141,7 +138,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er addressLimit = false response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy) } - cancel() var rejected bool if err != nil { if errors.Is(err, dns.ErrResponseRejectedCached) { @@ -206,7 +202,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS for { var ( dnsCtx context.Context - cancel context.CancelFunc addressLimit bool ) metadata.ResetRuleCache() @@ -215,7 +210,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS if strategy == dns.DomainStrategyAsIS { strategy = transportStrategy } - dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout) if rule != nil && rule.WithAddressLimit() { addressLimit = true responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool { @@ -226,7 +220,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS addressLimit = false responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy) } - cancel() if err != nil { if errors.Is(err, dns.ErrResponseRejectedCached) { r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)") From ff7d8c9ba8da1d86609d6b6c80bb374486289881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 22 Jun 2024 14:11:49 +0800 Subject: [PATCH 0055/1272] Add custom options for TUN `auto-route` and `auto-redirect` --- docs/configuration/inbound/tun.md | 82 +++++++++++++++++++++------- docs/configuration/inbound/tun.zh.md | 70 +++++++++++++++++++----- inbound/tun.go | 4 +- option/tun.go | 36 +++++++++++- 4 files changed, 153 insertions(+), 39 deletions(-) diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 1e2bf40058..812b20d163 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -13,7 +13,11 @@ icon: material/new-box :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-plus: [iproute2_table_index](#iproute2_table_index) + :material-plus: [iproute2_rule_index](#iproute2_table_index) :material-plus: [auto_redirect](#auto_redirect) + :material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark) + :material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark) :material-plus: [route_address_set](#route_address_set) :material-plus: [route_exclude_address_set](#route_address_set) @@ -53,8 +57,12 @@ icon: material/new-box "mtu": 9000, "gso": false, "auto_route": true, - "strict_route": true, + "iproute2_table_index": 2022, + "iproute2_rule_index": 9000, "auto_redirect": false, + "auto_redirect_input_mark": "0x2023", + "auto_redirect_output_mark": "0x2024", + "strict_route": true, "route_address": [ "0.0.0.0/1", "128.0.0.0/1", @@ -129,8 +137,8 @@ icon: material/new-box "match_domain": [] } }, - - ... // Listen Fields + ... + // Listen Fields } ``` @@ -180,7 +188,7 @@ The maximum transmission unit. !!! quote "" - Only supported on Linux. + Only supported on Linux with `auto_route` enabled. Enable generic segmentation offload. @@ -196,24 +204,21 @@ Set the default route to the Tun. By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`. -#### strict_route +#### iproute2_table_index -Enforce strict routing rules when `auto_route` is enabled: +!!! question "Since sing-box 1.10.0" -*In Linux*: +Linux iproute2 table index generated by `auto_route`. -* Let unsupported network unreachable -* Make ICMP traffic route to tun instead of upstream interfaces -* Route all connections to tun +`2022` is used by default. -It prevents IP address leaks and makes DNS hijacking work on Android. +#### iproute2_rule_index -*In Windows*: +!!! question "Since sing-box 1.10.0" -* Add firewall rules to prevent DNS leak caused by - Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) +Linux iproute2 rule start index generated by `auto_route`. -It may prevent some applications (such as VirtualBox) from working properly in certain situations. +`9000` is used by default. #### auto_redirect @@ -234,6 +239,41 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot). `auto_route` with `auto_redirect` now works as expected on routers **without intervention**. +#### auto_redirect_input_mark + +!!! question "Since sing-box 1.10.0" + +Connection input mark used by `route_address_set` and `route_exclude_address_set`. + +`0x2023` is used by default. + +#### auto_redirect_output_mark + +!!! question "Since sing-box 1.10.0" + +Connection output mark used by `route_address_set` and `route_exclude_address_set`. + +`0x2024` is used by default. + +#### strict_route + +Enforce strict routing rules when `auto_route` is enabled: + +*In Linux*: + +* Let unsupported network unreachable +* Make ICMP traffic route to tun instead of upstream interfaces +* Route all connections to tun + +It prevents IP address leaks and makes DNS hijacking work on Android. + +*In Windows*: + +* Add firewall rules to prevent DNS leak caused by + Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) + +It may prevent some applications (such as VirtualBox) from working properly in certain situations. + #### route_address !!! question "Since sing-box 1.10.0" @@ -244,7 +284,8 @@ Use custom routes instead of default when `auto_route` is enabled. !!! failure "Deprecated in sing-box 1.10.0" - `inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead. +`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) +instead. Use custom routes instead of default when `auto_route` is enabled. @@ -252,7 +293,8 @@ Use custom routes instead of default when `auto_route` is enabled. !!! failure "Deprecated in sing-box 1.10.0" - `inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead. +`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) +instead. Use custom routes instead of default when `auto_route` is enabled. @@ -266,7 +308,8 @@ Exclude custom routes when `auto_route` is enabled. !!! failure "Deprecated in sing-box 1.10.0" - `inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead. +`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please +use [route_exclude_address](#route_exclude_address) instead. Exclude custom routes when `auto_route` is enabled. @@ -274,7 +317,8 @@ Exclude custom routes when `auto_route` is enabled. !!! failure "Deprecated in sing-box 1.10.0" - `inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead. +`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please +use [route_exclude_address](#route_exclude_address) instead. Exclude custom routes when `auto_route` is enabled. diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 5b1d35afb5..bff493d4f9 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -12,8 +12,12 @@ 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) + :material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark) + :material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark) :material-plus: [route_address_set](#route_address_set) :material-plus: [route_exclude_address_set](#route_address_set) @@ -53,8 +57,12 @@ icon: material/new-box "mtu": 9000, "gso": false, "auto_route": true, - "strict_route": true, + "iproute2_table_index": 2022, + "iproute2_rule_index": 9000, "auto_redirect": false, + "auto_redirect_input_mark": "0x2023", + "auto_redirect_output_mark": "0x2024", + "strict_route": true, "route_address": [ "0.0.0.0/1", "128.0.0.0/1", @@ -200,25 +208,21 @@ tun 接口的 IPv6 前缀。 VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。 -#### strict_route +#### iproute2_table_index -启用 `auto_route` 时执行严格的路由规则。 +!!! question "自 sing-box 1.10.0 起" -*在 Linux 中*: +`auto_route` 生成的 iproute2 路由表索引。 -* 让不支持的网络无法到达 -* 使 ICMP 流量路由到 tun 而不是上游接口 -* 将所有连接路由到 tun +默认使用 `2022`。 -它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。 +#### iproute2_rule_index -*在 Windows 中*: +!!! question "自 sing-box 1.10.0 起" -* 添加防火墙规则以阻止 Windows - 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) - 造成的 DNS 泄露 +`auto_route` 生成的 iproute2 规则起始索引。 -它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。 +默认使用 `9000`。 #### auto_redirect @@ -226,7 +230,7 @@ tun 接口的 IPv6 前缀。 !!! quote "" - 仅支持 Linux。 + 仅支持 Linux,且需要 `auto_route` 已启用。 自动配置 iptables 以重定向 TCP 连接。 @@ -238,6 +242,42 @@ tun 接口的 IPv6 前缀。 带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。 +#### auto_redirect_input_mark + +!!! question "自 sing-box 1.10.0 起" + +`route_address_set` 和 `route_exclude_address_set` 使用的连接输入标记。 + +默认使用 `0x2023`。 + +#### auto_redirect_output_mark + +!!! question "自 sing-box 1.10.0 起" + +`route_address_set` 和 `route_exclude_address_set` 使用的连接输出标记。 + +默认使用 `0x2024`。 + +#### strict_route + +启用 `auto_route` 时执行严格的路由规则。 + +*在 Linux 中*: + +* 让不支持的网络无法到达 +* 使 ICMP 流量路由到 tun 而不是上游接口 +* 将所有连接路由到 tun + +它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。 + +*在 Windows 中*: + +* 添加防火墙规则以阻止 Windows + 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) + 造成的 DNS 泄露 + +它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。 + #### route_address !!! question "自 sing-box 1.10.0 起" diff --git a/inbound/tun.go b/inbound/tun.go index cb6a02c301..6cb65de249 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -141,11 +141,11 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger if ruleIndex == 0 { ruleIndex = tun.DefaultIPRoute2RuleIndex } - inputMark := options.AutoRedirectInputMark + inputMark := uint32(options.AutoRedirectInputMark) if inputMark == 0 { inputMark = tun.DefaultAutoRedirectInputMark } - outputMark := options.AutoRedirectOutputMark + outputMark := uint32(options.AutoRedirectOutputMark) if outputMark == 0 { outputMark = tun.DefaultAutoRedirectOutputMark } diff --git a/option/tun.go b/option/tun.go index cbc73e7d4b..dbb1bfeae0 100644 --- a/option/tun.go +++ b/option/tun.go @@ -1,6 +1,13 @@ package option -import "net/netip" +import ( + "net/netip" + "strconv" + + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json" +) type TunInboundOptions struct { InterfaceName string `json:"interface_name,omitempty"` @@ -11,8 +18,8 @@ type TunInboundOptions struct { IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"` IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"` AutoRedirect bool `json:"auto_redirect,omitempty"` - AutoRedirectInputMark uint32 `json:"auto_redirect_input_mark,omitempty"` - AutoRedirectOutputMark uint32 `json:"auto_redirect_output_mark,omitempty"` + AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"` + AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"` StrictRoute bool `json:"strict_route,omitempty"` RouteAddress Listable[netip.Prefix] `json:"route_address,omitempty"` RouteAddressSet Listable[string] `json:"route_address_set,omitempty"` @@ -46,3 +53,26 @@ type TunInboundOptions struct { // Deprecated: merged to RouteExcludeAddress Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` } + +type FwMark uint32 + +func (f FwMark) MarshalJSON() ([]byte, error) { + return json.Marshal(F.ToString("0x", strconv.FormatUint(uint64(f), 16))) +} + +func (f *FwMark) UnmarshalJSON(bytes []byte) error { + var stringValue string + err := json.Unmarshal(bytes, &stringValue) + if err != nil { + if rawErr := json.Unmarshal(bytes, (*uint32)(f)); rawErr == nil { + return nil + } + return E.Cause(err, "invalid number or string mark") + } + intValue, err := strconv.ParseUint(stringValue, 0, 32) + if err != nil { + return err + } + *f = FwMark(intValue) + return nil +} From 93cf134995df2381b17deb649eb7d4aacfac8fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 24 Jun 2024 09:41:00 +0800 Subject: [PATCH 0056/1272] Add accept empty DNS rule option --- adapter/inbound.go | 5 ++++- docs/configuration/dns/rule.md | 29 ++++++++++++++++++++++++-- docs/configuration/dns/rule.zh.md | 31 +++++++++++++++++++++++++--- docs/configuration/inbound/tun.zh.md | 12 +++++------ docs/configuration/route/rule.md | 23 ++++++++++++++++++++- docs/configuration/route/rule.zh.md | 23 ++++++++++++++++++++- option/rule.go | 25 +++++++++++++++++++--- option/rule_dns.go | 26 ++++++++++++++++++++--- route/router_dns.go | 17 +++++++++------ route/rule_default.go | 2 +- route/rule_dns.go | 2 +- route/rule_item_cidr.go | 19 +++++++++-------- route/rule_item_rule_set.go | 13 +++++++----- 13 files changed, 185 insertions(+), 42 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index 063671c1a1..56eb562308 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -51,7 +51,9 @@ type InboundContext struct { // rule cache - IPCIDRMatchSource bool + IPCIDRMatchSource bool + IPCIDRAcceptEmpty bool + SourceAddressMatch bool SourcePortMatch bool DestinationAddressMatch bool @@ -62,6 +64,7 @@ type InboundContext struct { func (c *InboundContext) ResetRuleCache() { c.IPCIDRMatchSource = false + c.IPCIDRAcceptEmpty = false c.SourceAddressMatch = false c.SourcePortMatch = false c.DestinationAddressMatch = false diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 4c4abacb65..0faae0e680 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -2,6 +2,12 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.10.0" + + :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + !!! quote "Changes in sing-box 1.9.0" :material-plus: [geoip](#geoip) @@ -117,7 +123,10 @@ icon: material/new-box "geoip-cn", "geosite-cn" ], + // deprecated "rule_set_ipcidr_match_source": false, + "rule_set_ip_cidr_match_source": false, + "rule_set_ip_cidr_accept_empty": false, "invert": false, "outbound": [ "direct" @@ -309,7 +318,17 @@ Match [Rule Set](/configuration/route/#rule_set). !!! question "Since sing-box 1.9.0" -Make `ipcidr` in rule sets match the source IP. +!!! failure "Deprecated in sing-box 1.10.0" + + `rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0. + +Make `ip_cidr` rule items in rule sets match the source IP. + +#### rule_set_ip_cidr_match_source + +!!! question "Since sing-box 1.10.0" + +Make `ip_cidr` rule items in rule sets match the source IP. #### invert @@ -347,7 +366,7 @@ Will overrides `dns.client_subnet` and `servers.[].client_subnet`. ### Address Filter Fields -Only takes effect for IP address requests. When the query results do not match the address filtering rule items, the current rule will be skipped. +Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped. !!! info "" @@ -375,6 +394,12 @@ Match IP CIDR with query response. Match private IP with query response. +#### rule_set_ip_cidr_accept_empty + +!!! question "Since sing-box 1.10.0" + +Make `ip_cidr` rules in rule sets accept empty query response. + ### Logical Fields #### type diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 796d29e8c4..eecddb3816 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -2,6 +2,12 @@ icon: material/new-box --- +!!! quote "sing-box 1.10.0 中的更改" + + :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + !!! quote "sing-box 1.9.0 中的更改" :material-plus: [geoip](#geoip) @@ -117,7 +123,10 @@ icon: material/new-box "geoip-cn", "geosite-cn" ], + // 已弃用 "rule_set_ipcidr_match_source": false, + "rule_set_ip_cidr_match_source": false, + "rule_set_ip_cidr_accept_empty": false, "invert": false, "outbound": [ "direct" @@ -307,7 +316,17 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 !!! question "自 sing-box 1.9.0 起" -使规则集中的 `ipcidr` 规则匹配源 IP。 +!!! failure "已在 sing-box 1.10.0 废弃" + + `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。 + +使规则集中的 `ip_cidr` 规则匹配源 IP。 + +#### rule_set_ip_cidr_match_source + +!!! question "自 sing-box 1.10.0 起" + +使规则集中的 `ip_cidr` 规则匹配源 IP。 #### invert @@ -345,7 +364,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 ### 地址筛选字段 -仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。 +仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。 !!! info "" @@ -365,7 +384,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 !!! question "自 sing-box 1.9.0 起" -与查询相应匹配 IP CIDR。 +与查询响应匹配 IP CIDR。 #### ip_is_private @@ -373,6 +392,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 与查询响应匹配非公开 IP。 +#### rule_set_ip_cidr_accept_empty + +!!! question "自 sing-box 1.10.0 起" + +使规则集中的 `ip_cidr` 规则接受空查询响应。 + ### 逻辑字段 #### type diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index bff493d4f9..88e02fe767 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -168,7 +168,7 @@ tun 接口的 IPv4 和 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除. + `inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。 ==必填== @@ -178,7 +178,7 @@ tun 接口的 IPv4 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除. + `inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。 tun 接口的 IPv6 前缀。 @@ -288,7 +288,7 @@ tun 接口的 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除. + `inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。 启用 `auto_route` 时使用自定义路由而不是默认路由。 @@ -296,7 +296,7 @@ tun 接口的 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除. + `inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。 启用 `auto_route` 时使用自定义路由而不是默认路由。 @@ -310,7 +310,7 @@ tun 接口的 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除. + `inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。 启用 `auto_route` 时排除自定义路由。 @@ -318,7 +318,7 @@ tun 接口的 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除. + `inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。 启用 `auto_route` 时排除自定义路由。 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 62d33c6c53..1d06a875a1 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.10.0" + + :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + !!! quote "Changes in sing-box 1.8.0" :material-plus: [rule_set](#rule_set) @@ -105,7 +114,9 @@ "geoip-cn", "geosite-cn" ], + // deprecated "rule_set_ipcidr_match_source": false, + "rule_set_ip_cidr_match_source": false, "invert": false, "outbound": "direct" }, @@ -303,7 +314,17 @@ Match [Rule Set](/configuration/route/#rule_set). !!! question "Since sing-box 1.8.0" -Make `ipcidr` in rule sets match the source IP. +!!! failure "Deprecated in sing-box 1.10.0" + + `rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0. + +Make `ip_cidr` in rule sets match the source IP. + +#### rule_set_ip_cidr_match_source + +!!! question "Since sing-box 1.10.0" + +Make `ip_cidr` in rule sets match the source IP. #### invert diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index cba35bc581..52d334f22c 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.10.0 中的更改" + + :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) @@ -103,7 +112,9 @@ "geoip-cn", "geosite-cn" ], + // 已弃用 "rule_set_ipcidr_match_source": false, + "rule_set_ip_cidr_match_source": false, "invert": false, "outbound": "direct" }, @@ -301,7 +312,17 @@ !!! question "自 sing-box 1.8.0 起" -使规则集中的 `ipcidr` 规则匹配源 IP。 +!!! failure "已在 sing-box 1.10.0 废弃" + + `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。 + +使规则集中的 `ip_cidr` 规则匹配源 IP。 + +#### rule_set_ip_cidr_match_source + +!!! question "自 sing-box 1.10.0 起" + +使规则集中的 `ip_cidr` 规则匹配源 IP。 #### invert diff --git a/option/rule.go b/option/rule.go index 0ea133c75f..74dd13c615 100644 --- a/option/rule.go +++ b/option/rule.go @@ -64,7 +64,7 @@ func (r Rule) IsValid() bool { } } -type DefaultRule struct { +type _DefaultRule struct { Inbound Listable[string] `json:"inbound,omitempty"` IPVersion int `json:"ip_version,omitempty"` Network Listable[string] `json:"network,omitempty"` @@ -94,12 +94,31 @@ type DefaultRule struct { WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` RuleSet Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` Invert bool `json:"invert,omitempty"` Outbound string `json:"outbound,omitempty"` + + // Deprecated: renamed to rule_set_ip_cidr_match_source + Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` +} + +type DefaultRule _DefaultRule + +func (r *DefaultRule) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_DefaultRule)(r)) + if err != nil { + return err + } + //nolint:staticcheck + //goland:noinspection GoDeprecation + if r.Deprecated_RulesetIPCIDRMatchSource { + r.Deprecated_RulesetIPCIDRMatchSource = false + r.RuleSetIPCIDRMatchSource = true + } + return nil } -func (r DefaultRule) IsValid() bool { +func (r *DefaultRule) IsValid() bool { var defaultValue DefaultRule defaultValue.Invert = r.Invert defaultValue.Outbound = r.Outbound diff --git a/option/rule_dns.go b/option/rule_dns.go index c5994e1cec..2afe245ea4 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -64,7 +64,7 @@ func (r DNSRule) IsValid() bool { } } -type DefaultDNSRule struct { +type _DefaultDNSRule struct { Inbound Listable[string] `json:"inbound,omitempty"` IPVersion int `json:"ip_version,omitempty"` QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` @@ -96,15 +96,35 @@ type DefaultDNSRule struct { WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` RuleSet Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,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"` Server string `json:"server,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + + // Deprecated: renamed to rule_set_ip_cidr_match_source + Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` +} + +type DefaultDNSRule _DefaultDNSRule + +func (r *DefaultDNSRule) UnmarshalJSON(bytes []byte) error { + err := json.UnmarshalDisallowUnknownFields(bytes, (*_DefaultDNSRule)(r)) + if err != nil { + return err + } + //nolint:staticcheck + //goland:noinspection GoDeprecation + if r.Deprecated_RulesetIPCIDRMatchSource { + r.Deprecated_RulesetIPCIDRMatchSource = false + r.RuleSetIPCIDRMatchSource = true + } + return nil } -func (r DefaultDNSRule) IsValid() bool { +func (r *DefaultDNSRule) IsValid() bool { var defaultValue DefaultDNSRule defaultValue.Invert = r.Invert defaultValue.Server = r.Server diff --git a/route/router_dns.go b/route/router_dns.go index 51994072ab..ead8c28943 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -104,7 +104,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er response, cached = r.dnsClient.ExchangeCache(ctx, message) if !cached { var metadata *adapter.InboundContext - ctx, metadata = adapter.AppendContext(ctx) + ctx, metadata = adapter.ExtendContext(ctx) + metadata.Destination = M.Socksaddr{} if len(message.Question) > 0 { metadata.QueryType = message.Question[0].Qtype switch metadata.QueryType { @@ -126,12 +127,16 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er dnsCtx context.Context addressLimit bool ) - dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) + dnsCtx = adapter.OverrideContext(dnsCtx) if rule != nil && rule.WithAddressLimit() { addressLimit = true response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool { - metadata.DestinationAddresses, _ = dns.MessageToAddresses(response) + addresses, addrErr := dns.MessageToAddresses(response) + if addrErr != nil { + return false + } + metadata.DestinationAddresses = addresses return rule.MatchAddressLimit(metadata) }) } else { @@ -190,7 +195,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS return responseAddrs, nil } r.dnsLogger.DebugContext(ctx, "lookup domain ", domain) - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Destination = M.Socksaddr{} metadata.Domain = domain var ( transport dns.Transport @@ -204,9 +210,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS dnsCtx context.Context addressLimit bool ) - metadata.ResetRuleCache() - metadata.DestinationAddresses = nil dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) + dnsCtx = adapter.OverrideContext(dnsCtx) if strategy == dns.DomainStrategyAsIS { strategy = transportStrategy } diff --git a/route/rule_default.go b/route/rule_default.go index d1d13f7d72..53e53bdf86 100644 --- a/route/rule_default.go +++ b/route/rule_default.go @@ -205,7 +205,7 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.allItems = append(rule.allItems, item) } if len(options.RuleSet) > 0 { - item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource) + item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource, false) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule_dns.go b/route/rule_dns.go index 955526fc6f..1b79d30b25 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -219,7 +219,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.allItems = append(rule.allItems, item) } if len(options.RuleSet) > 0 { - item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource) + item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource, options.RuleSetIPCIDRAcceptEmpty) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule_item_cidr.go b/route/rule_item_cidr.go index 85b9c8d7d3..be0bb1369c 100644 --- a/route/rule_item_cidr.go +++ b/route/rule_item_cidr.go @@ -75,18 +75,19 @@ func NewRawIPCIDRItem(isSource bool, ipSet *netipx.IPSet) *IPCIDRItem { func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool { if r.isSource || metadata.IPCIDRMatchSource { return r.ipSet.Contains(metadata.Source.Addr) - } else { - if metadata.Destination.IsIP() { - return r.ipSet.Contains(metadata.Destination.Addr) - } else { - for _, address := range metadata.DestinationAddresses { - if r.ipSet.Contains(address) { - return true - } + } + if metadata.Destination.IsIP() { + return r.ipSet.Contains(metadata.Destination.Addr) + } + if len(metadata.DestinationAddresses) > 0 { + for _, address := range metadata.DestinationAddresses { + if r.ipSet.Contains(address) { + return true } } + return false } - return false + return metadata.IPCIDRAcceptEmpty } func (r *IPCIDRItem) String() string { diff --git a/route/rule_item_rule_set.go b/route/rule_item_rule_set.go index 4ecf8c18f0..b80fca995c 100644 --- a/route/rule_item_rule_set.go +++ b/route/rule_item_rule_set.go @@ -15,14 +15,16 @@ type RuleSetItem struct { router adapter.Router tagList []string setList []adapter.RuleSet - ipcidrMatchSource bool + ipCidrMatchSource bool + ipCidrAcceptEmpty bool } -func NewRuleSetItem(router adapter.Router, tagList []string, ipCIDRMatchSource bool) *RuleSetItem { +func NewRuleSetItem(router adapter.Router, tagList []string, ipCIDRMatchSource bool, ipCidrAcceptEmpty bool) *RuleSetItem { return &RuleSetItem{ router: router, tagList: tagList, - ipcidrMatchSource: ipCIDRMatchSource, + ipCidrMatchSource: ipCIDRMatchSource, + ipCidrAcceptEmpty: ipCidrAcceptEmpty, } } @@ -39,7 +41,8 @@ func (r *RuleSetItem) Start() error { } func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool { - metadata.IPCIDRMatchSource = r.ipcidrMatchSource + metadata.IPCIDRMatchSource = r.ipCidrMatchSource + metadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty for _, ruleSet := range r.setList { if ruleSet.Match(metadata) { return true @@ -49,7 +52,7 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool { } func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool { - if r.ipcidrMatchSource { + if r.ipCidrMatchSource { return false } return common.Any(r.setList, func(ruleSet adapter.RuleSet) bool { From 6bebe2483b67d0bdfd139c6c6001bc977b44c96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 26 Jun 2024 00:45:10 +0800 Subject: [PATCH 0057/1272] Unique rule-set names --- cmd/sing-box/cmd_rule_set.go | 2 +- cmd/sing-box/cmd_rule_set_match.go | 4 ++-- common/srs/binary.go | 2 +- docs/changelog.md | 26 +++++++++++++------------- docs/configuration/dns/rule.md | 12 ++++++------ docs/configuration/route/index.md | 2 +- docs/configuration/route/rule.md | 8 ++++---- docs/configuration/rule-set/index.md | 14 +++++++------- docs/deprecated.md | 4 ++-- docs/migration.md | 16 ++++++++-------- docs/migration.zh.md | 4 ++-- option/rule_set.go | 14 +++++++------- route/rule_set.go | 2 +- route/rule_set_local.go | 2 +- route/rule_set_remote.go | 2 +- 15 files changed, 57 insertions(+), 57 deletions(-) diff --git a/cmd/sing-box/cmd_rule_set.go b/cmd/sing-box/cmd_rule_set.go index f4112a087b..242ea8b666 100644 --- a/cmd/sing-box/cmd_rule_set.go +++ b/cmd/sing-box/cmd_rule_set.go @@ -6,7 +6,7 @@ import ( var commandRuleSet = &cobra.Command{ Use: "rule-set", - Short: "Manage rule sets", + Short: "Manage rule-sets", } func init() { diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index fb2560afb1..8bf2ec7e12 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -23,7 +23,7 @@ var flagRuleSetMatchFormat string var commandRuleSetMatch = &cobra.Command{ Use: "match ", - Short: "Check if an IP address or a domain matches the rule set", + Short: "Check if an IP address or a domain matches the rule-set", Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { err := ruleSetMatch(args[0], args[1]) @@ -70,7 +70,7 @@ func ruleSetMatch(sourcePath string, domain string) error { return err } default: - return E.New("unknown rule set format: ", flagRuleSetMatchFormat) + return E.New("unknown rule-set format: ", flagRuleSetMatchFormat) } ipAddress := M.ParseAddr(domain) var metadata adapter.InboundContext diff --git a/common/srs/binary.go b/common/srs/binary.go index 0bd5a9099b..69075f7881 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -46,7 +46,7 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro return } if magicBytes != MagicBytes { - err = E.New("invalid sing-box rule set file") + err = E.New("invalid sing-box rule-set file") return } var version uint8 diff --git a/docs/changelog.md b/docs/changelog.md index f5eb714cde..f835a68bd2 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -367,7 +367,7 @@ See [Address Filter Fields](/configuration/dns/rule#address-filter-fields). Important changes since 1.7: * Migrate cache file from Clash API to independent options **1** -* Introducing [Rule Set](/configuration/rule-set/) **2** +* Introducing [rule-set](/configuration/rule-set/) **2** * Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3** * Allow nested logical rules **4** * Independent `source_ip_is_private` and `ip_is_private` rules **5** @@ -387,7 +387,7 @@ See [Cache File](/configuration/experimental/cache-file/) and **2**: -Rule set is independent collections of rules that can be compiled into binaries to improve performance. +rule-set is independent collections of rules that can be compiled into binaries to improve performance. Compared to legacy GeoIP and Geosite resources, it can include more types of rules, load faster, use less memory, and update automatically. @@ -395,16 +395,16 @@ use less memory, and update automatically. See [Route#rule_set](/configuration/route/#rule_set), [Route Rule](/configuration/route/rule/), [DNS Rule](/configuration/dns/rule/), -[Rule Set](/configuration/rule-set/), +[rule-set](/configuration/rule-set/), [Source Format](/configuration/rule-set/source-format/) and [Headless Rule](/configuration/rule-set/headless-rule/). -For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and -[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets). +For GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and +[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets). **3**: -New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets. +New commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets. **4**: @@ -601,7 +601,7 @@ This change is intended to break incorrect usage and essentially requires no act **1**: -Now the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule sets, +Now the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule-sets, rather than completely following the AND logic. #### 1.8.0-alpha.5 @@ -617,7 +617,7 @@ Since GeoIP was deprecated, we made this rule independent, see [Migration](/migr #### 1.8.0-alpha.1 * Migrate cache file from Clash API to independent options **1** -* Introducing [Rule Set](/configuration/rule-set/) **2** +* Introducing [rule-set](/configuration/rule-set/) **2** * Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3** * Allow nested logical rules **4** @@ -628,7 +628,7 @@ See [Cache File](/configuration/experimental/cache-file/) and **2**: -Rule set is independent collections of rules that can be compiled into binaries to improve performance. +rule-set is independent collections of rules that can be compiled into binaries to improve performance. Compared to legacy GeoIP and Geosite resources, it can include more types of rules, load faster, use less memory, and update automatically. @@ -636,16 +636,16 @@ use less memory, and update automatically. See [Route#rule_set](/configuration/route/#rule_set), [Route Rule](/configuration/route/rule/), [DNS Rule](/configuration/dns/rule/), -[Rule Set](/configuration/rule-set/), +[rule-set](/configuration/rule-set/), [Source Format](/configuration/rule-set/source-format/) and [Headless Rule](/configuration/rule-set/headless-rule/). -For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and -[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets). +For GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and +[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets). **3**: -New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets. +New commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets. **4**: diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 0faae0e680..6b3d65194f 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -166,7 +166,7 @@ icon: material/new-box (`source_port` || `source_port_range`) && `other fields` - Additionally, included rule sets can be considered merged rather than as a single rule sub-item. + Additionally, included rule-sets can be considered merged rather than as a single rule sub-item. #### inbound @@ -312,7 +312,7 @@ Match WiFi BSSID. !!! question "Since sing-box 1.8.0" -Match [Rule Set](/configuration/route/#rule_set). +Match [rule-set](/configuration/route/#rule_set). #### rule_set_ipcidr_match_source @@ -322,13 +322,13 @@ Match [Rule Set](/configuration/route/#rule_set). `rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0. -Make `ip_cidr` rule items in rule sets match the source IP. +Make `ip_cidr` rule items in rule-sets match the source IP. #### rule_set_ip_cidr_match_source !!! question "Since sing-box 1.10.0" -Make `ip_cidr` rule items in rule sets match the source IP. +Make `ip_cidr` rule items in rule-sets match the source IP. #### invert @@ -370,7 +370,7 @@ Only takes effect for address requests (A/AAAA/HTTPS). When the query results do !!! info "" - `ip_cidr` items in included rule sets also takes effect as an address filtering field. + `ip_cidr` items in included rule-sets also takes effect as an address filtering field. !!! note "" @@ -398,7 +398,7 @@ Match private IP with query response. !!! question "Since sing-box 1.10.0" -Make `ip_cidr` rules in rule sets accept empty query response. +Make `ip_cidr` rules in rule-sets accept empty query response. ### Logical Fields diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 7b2a7e7ef2..507cb140b6 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -39,7 +39,7 @@ List of [Route Rule](./rule/) !!! question "Since sing-box 1.8.0" -List of [Rule Set](/configuration/rule-set/) +List of [rule-set](/configuration/rule-set/) #### final diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 1d06a875a1..23d2bf1b0c 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -148,7 +148,7 @@ icon: material/alert-decagram (`source_port` || `source_port_range`) && `other fields` - Additionally, included rule sets can be considered merged rather than as a single rule sub-item. + Additionally, included rule-sets can be considered merged rather than as a single rule sub-item. #### inbound @@ -308,7 +308,7 @@ Match WiFi BSSID. !!! question "Since sing-box 1.8.0" -Match [Rule Set](/configuration/route/#rule_set). +Match [rule-set](/configuration/route/#rule_set). #### rule_set_ipcidr_match_source @@ -318,13 +318,13 @@ Match [Rule Set](/configuration/route/#rule_set). `rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0. -Make `ip_cidr` in rule sets match the source IP. +Make `ip_cidr` in rule-sets match the source IP. #### rule_set_ip_cidr_match_source !!! question "Since sing-box 1.10.0" -Make `ip_cidr` in rule sets match the source IP. +Make `ip_cidr` in rule-sets match the source IP. #### invert diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index ba2f741e4f..b92d80f3d7 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -1,4 +1,4 @@ -# Rule Set +# rule-set !!! question "Since sing-box 1.8.0" @@ -50,19 +50,19 @@ ==Required== -Type of Rule Set, `local` or `remote`. +Type of rule-set, `local` or `remote`. #### tag ==Required== -Tag of Rule Set. +Tag of rule-set. #### format ==Required== -Format of Rule Set, `source` or `binary`. +Format of rule-set, `source` or `binary`. ### Local Fields @@ -70,7 +70,7 @@ Format of Rule Set, `source` or `binary`. ==Required== -File path of Rule Set. +File path of rule-set. ### Remote Fields @@ -78,7 +78,7 @@ File path of Rule Set. ==Required== -Download URL of Rule Set. +Download URL of rule-set. #### download_detour @@ -88,6 +88,6 @@ Default outbound will be used if empty. #### update_interval -Update interval of Rule Set. +Update interval of rule-set. `1d` will be used if empty. diff --git a/docs/deprecated.md b/docs/deprecated.md index 249bc492a2..eb0c1925c8 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -33,7 +33,7 @@ The maxmind GeoIP National Database, as an IP classification database, is not entirely suitable for traffic bypassing, and all existing implementations suffer from high memory usage and difficult management. -sing-box 1.8.0 introduces [Rule Set](/configuration/rule-set/), which can completely replace GeoIP, +sing-box 1.8.0 introduces [rule-set](/configuration/rule-set/), which can completely replace GeoIP, check [Migration](/migration/#migrate-geoip-to-rule-sets). #### Geosite @@ -43,7 +43,7 @@ Geosite is deprecated and may be removed in the future. Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution, suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management. -sing-box 1.8.0 introduces [Rule Set](/configuration/rule-set/), which can completely replace Geosite, +sing-box 1.8.0 introduces [rule-set](/configuration/rule-set/), which can completely replace Geosite, check [Migration](/migration/#migrate-geosite-to-rule-sets). ## 1.6.0 diff --git a/docs/migration.md b/docs/migration.md index 017ed2302e..71b6169216 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -145,7 +145,7 @@ which will disrupt the existing `process_path` use cases in Windows. } ``` -### :material-checkbox-intermediate: Migrate GeoIP to rule sets +### :material-checkbox-intermediate: Migrate GeoIP to rule-sets !!! info "References" @@ -153,11 +153,11 @@ which will disrupt the existing `process_path` use cases in Windows. [Route](/configuration/route/) / [Route Rule](/configuration/route/rule/) / [DNS Rule](/configuration/dns/rule/) / - [Rule Set](/configuration/rule-set/) + [rule-set](/configuration/rule-set/) !!! tip - `sing-box geoip` commands can help you convert custom GeoIP into rule sets. + `sing-box geoip` commands can help you convert custom GeoIP into rule-sets. === ":material-card-remove: Deprecated" @@ -224,13 +224,13 @@ which will disrupt the existing `process_path` use cases in Windows. }, "experimental": { "cache_file": { - "enabled": true // required to save Rule Set cache + "enabled": true // required to save rule-set cache } } } ``` -### :material-checkbox-intermediate: Migrate Geosite to rule sets +### :material-checkbox-intermediate: Migrate Geosite to rule-sets !!! info "References" @@ -238,11 +238,11 @@ which will disrupt the existing `process_path` use cases in Windows. [Route](/configuration/route/) / [Route Rule](/configuration/route/rule/) / [DNS Rule](/configuration/dns/rule/) / - [Rule Set](/configuration/rule-set/) + [rule-set](/configuration/rule-set/) !!! tip - `sing-box geosite` commands can help you convert custom Geosite into rule sets. + `sing-box geosite` commands can help you convert custom Geosite into rule-sets. === ":material-card-remove: Deprecated" @@ -285,7 +285,7 @@ which will disrupt the existing `process_path` use cases in Windows. }, "experimental": { "cache_file": { - "enabled": true // required to save Rule Set cache + "enabled": true // required to save rule-set cache } } } diff --git a/docs/migration.zh.md b/docs/migration.zh.md index a8a0774045..62fbe9edeb 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -222,7 +222,7 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold }, "experimental": { "cache_file": { - "enabled": true // required to save Rule Set cache + "enabled": true // required to save rule-set cache } } } @@ -283,7 +283,7 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold }, "experimental": { "cache_file": { - "enabled": true // required to save Rule Set cache + "enabled": true // required to save rule-set cache } } } diff --git a/option/rule_set.go b/option/rule_set.go index 5498400f2f..ec32d0a13a 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -31,7 +31,7 @@ func (r RuleSet) MarshalJSON() ([]byte, error) { case C.RuleSetTypeRemote: v = r.RemoteOptions default: - return nil, E.New("unknown rule set type: " + r.Type) + return nil, E.New("unknown rule-set type: " + r.Type) } return MarshallObjects((_RuleSet)(r), v) } @@ -49,7 +49,7 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { return E.New("missing format") case C.RuleSetFormatSource, C.RuleSetFormatBinary: default: - return E.New("unknown rule set format: " + r.Format) + return E.New("unknown rule-set format: " + r.Format) } var v any switch r.Type { @@ -60,7 +60,7 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { case "": return E.New("missing type") default: - return E.New("unknown rule set type: " + r.Type) + return E.New("unknown rule-set type: " + r.Type) } err = UnmarshallExcluded(bytes, (*_RuleSet)(r), v) if err != nil { @@ -188,7 +188,7 @@ func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { case C.RuleSetVersion1, C.RuleSetVersion2: v = r.Options default: - return nil, E.New("unknown rule set version: ", r.Version) + return nil, E.New("unknown rule-set version: ", r.Version) } return MarshallObjects((_PlainRuleSetCompat)(r), v) } @@ -203,9 +203,9 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { case C.RuleSetVersion1, C.RuleSetVersion2: v = &r.Options case 0: - return E.New("missing rule set version") + return E.New("missing rule-set version") default: - return E.New("unknown rule set version: ", r.Version) + return E.New("unknown rule-set version: ", r.Version) } err = UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v) if err != nil { @@ -220,7 +220,7 @@ func (r PlainRuleSetCompat) Upgrade() PlainRuleSet { case C.RuleSetVersion1, C.RuleSetVersion2: result = r.Options default: - panic("unknown rule set version: " + F.ToString(r.Version)) + panic("unknown rule-set version: " + F.ToString(r.Version)) } return result } diff --git a/route/rule_set.go b/route/rule_set.go index 7c02aa7a82..9907a1c673 100644 --- a/route/rule_set.go +++ b/route/rule_set.go @@ -25,7 +25,7 @@ func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.Contex case C.RuleSetTypeRemote: return NewRemoteRuleSet(ctx, router, logger, options), nil default: - return nil, E.New("unknown rule set type: ", options.Type) + return nil, E.New("unknown rule-set type: ", options.Type) } } diff --git a/route/rule_set_local.go b/route/rule_set_local.go index 87a393c27d..96d30dd5e8 100644 --- a/route/rule_set_local.go +++ b/route/rule_set_local.go @@ -52,7 +52,7 @@ func NewLocalRuleSet(ctx context.Context, router adapter.Router, options option. return nil, err } default: - return nil, E.New("unknown rule set format: ", options.Format) + return nil, E.New("unknown rule-set format: ", options.Format) } rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) var err error diff --git a/route/rule_set_remote.go b/route/rule_set_remote.go index bf0cfe204c..1473a494a9 100644 --- a/route/rule_set_remote.go +++ b/route/rule_set_remote.go @@ -175,7 +175,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { return err } default: - return E.New("unknown rule set format: ", s.options.Format) + return E.New("unknown rule-set format: ", s.options.Format) } rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) for i, ruleOptions := range plainRuleSet.Rules { From 3a7acaa92ab967ca5fade11d7703d925a73b0ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 26 Jun 2024 00:43:51 +0800 Subject: [PATCH 0058/1272] Add inline rule-set & Add reload for local rule-set --- cmd/sing-box/cmd_rule_set_compile.go | 5 +- cmd/sing-box/cmd_rule_set_match.go | 5 +- cmd/sing-box/cmd_rule_set_upgrade.go | 5 +- common/tls/ech_server.go | 185 +++++++++------------------ common/tls/std_server.go | 61 +++------ constant/rule.go | 1 + docs/configuration/rule-set/index.md | 104 +++++++++------ docs/configuration/shared/tls.md | 18 ++- docs/configuration/shared/tls.zh.md | 16 ++- go.mod | 4 +- option/rule_set.go | 37 +++--- route/rule_set.go | 4 +- route/rule_set_local.go | 127 +++++++++++++----- route/rule_set_remote.go | 5 +- 14 files changed, 304 insertions(+), 273 deletions(-) diff --git a/cmd/sing-box/cmd_rule_set_compile.go b/cmd/sing-box/cmd_rule_set_compile.go index 7e3753c928..4fae4d99e5 100644 --- a/cmd/sing-box/cmd_rule_set_compile.go +++ b/cmd/sing-box/cmd_rule_set_compile.go @@ -56,7 +56,10 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - ruleSet := plainRuleSet.Upgrade() + ruleSet, err := plainRuleSet.Upgrade() + if err != nil { + return err + } var outputPath string if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput { if strings.HasSuffix(sourcePath, ".json") { diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index 8bf2ec7e12..937458f2c6 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -63,7 +63,10 @@ func ruleSetMatch(sourcePath string, domain string) error { if err != nil { return err } - plainRuleSet = compat.Upgrade() + plainRuleSet, err = compat.Upgrade() + if err != nil { + return err + } case C.RuleSetFormatBinary: plainRuleSet, err = srs.Read(bytes.NewReader(content), false) if err != nil { diff --git a/cmd/sing-box/cmd_rule_set_upgrade.go b/cmd/sing-box/cmd_rule_set_upgrade.go index 0ec039fd71..e885d849e4 100644 --- a/cmd/sing-box/cmd_rule_set_upgrade.go +++ b/cmd/sing-box/cmd_rule_set_upgrade.go @@ -61,7 +61,10 @@ func upgradeRuleSet(sourcePath string) error { log.Info("already up-to-date") return nil } - plainRuleSet := plainRuleSetCompat.Upgrade() + plainRuleSet, err := plainRuleSetCompat.Upgrade() + if err != nil { + return err + } buffer := new(bytes.Buffer) encoder := json.NewEncoder(buffer) encoder.SetIndent("", " ") diff --git a/common/tls/ech_server.go b/common/tls/ech_server.go index 43ddd820ec..ac3e6279c3 100644 --- a/common/tls/ech_server.go +++ b/common/tls/ech_server.go @@ -11,12 +11,11 @@ import ( "strings" cftls "github.com/sagernet/cloudflare-tls" + "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" - - "github.com/fsnotify/fsnotify" ) type echServerConfig struct { @@ -26,9 +25,8 @@ type echServerConfig struct { key []byte certificatePath string keyPath string - watcher *fsnotify.Watcher echKeyPath string - echWatcher *fsnotify.Watcher + watcher *fswatch.Watcher } func (c *echServerConfig) ServerName() string { @@ -66,146 +64,84 @@ func (c *echServerConfig) Clone() Config { } func (c *echServerConfig) Start() error { - if c.certificatePath != "" && c.keyPath != "" { - err := c.startWatcher() - if err != nil { - c.logger.Warn("create fsnotify watcher: ", err) - } - } - if c.echKeyPath != "" { - err := c.startECHWatcher() - if err != nil { - c.logger.Warn("create fsnotify watcher: ", err) - } + err := c.startWatcher() + if err != nil { + c.logger.Warn("create credentials watcher: ", err) } return nil } func (c *echServerConfig) startWatcher() error { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } + var watchPath []string if c.certificatePath != "" { - err = watcher.Add(c.certificatePath) - if err != nil { - return err - } + watchPath = append(watchPath, c.certificatePath) } if c.keyPath != "" { - err = watcher.Add(c.keyPath) - if err != nil { - return err - } + watchPath = append(watchPath, c.keyPath) + } + if c.echKeyPath != "" { + watchPath = append(watchPath, c.echKeyPath) + } + if len(watchPath) == 0 { + return nil + } + watcher, err := fswatch.NewWatcher(fswatch.Options{ + Path: watchPath, + Callback: func(path string) { + err := c.credentialsUpdated(path) + if err != nil { + c.logger.Error(E.Cause(err, "reload credentials from ", path)) + } + }, + }) + if err != nil { + return err } c.watcher = watcher - go c.loopUpdate() return nil } -func (c *echServerConfig) loopUpdate() { - for { - select { - case event, ok := <-c.watcher.Events: - if !ok { - return - } - if event.Op&fsnotify.Write != fsnotify.Write { - continue - } - err := c.reloadKeyPair() +func (c *echServerConfig) credentialsUpdated(path string) error { + if path == c.certificatePath || path == c.keyPath { + if path == c.certificatePath { + certificate, err := os.ReadFile(c.certificatePath) if err != nil { - c.logger.Error(E.Cause(err, "reload TLS key pair")) + return err } - case err, ok := <-c.watcher.Errors: - if !ok { - return + c.certificate = certificate + } else { + key, err := os.ReadFile(c.keyPath) + if err != nil { + return err } - c.logger.Error(E.Cause(err, "fsnotify error")) + c.key = key } - } -} - -func (c *echServerConfig) reloadKeyPair() error { - if c.certificatePath != "" { - certificate, err := os.ReadFile(c.certificatePath) + keyPair, err := cftls.X509KeyPair(c.certificate, c.key) if err != nil { - return E.Cause(err, "reload certificate from ", c.certificatePath) + return E.Cause(err, "parse key pair") } - c.certificate = certificate - } - if c.keyPath != "" { - key, err := os.ReadFile(c.keyPath) + c.config.Certificates = []cftls.Certificate{keyPair} + c.logger.Info("reloaded TLS certificate") + } else { + echKeyContent, err := os.ReadFile(c.echKeyPath) if err != nil { - return E.Cause(err, "reload key from ", c.keyPath) + return err } - c.key = key - } - keyPair, err := cftls.X509KeyPair(c.certificate, c.key) - if err != nil { - return E.Cause(err, "reload key pair") - } - c.config.Certificates = []cftls.Certificate{keyPair} - c.logger.Info("reloaded TLS certificate") - return nil -} - -func (c *echServerConfig) startECHWatcher() error { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } - err = watcher.Add(c.echKeyPath) - if err != nil { - return err - } - c.echWatcher = watcher - go c.loopECHUpdate() - return nil -} - -func (c *echServerConfig) loopECHUpdate() { - for { - select { - case event, ok := <-c.echWatcher.Events: - if !ok { - return - } - if event.Op&fsnotify.Write != fsnotify.Write { - continue - } - err := c.reloadECHKey() - if err != nil { - c.logger.Error(E.Cause(err, "reload ECH key")) - } - case err, ok := <-c.echWatcher.Errors: - if !ok { - return - } - c.logger.Error(E.Cause(err, "fsnotify error")) + block, rest := pem.Decode(echKeyContent) + if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { + return E.New("invalid ECH keys pem") } + echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes) + if err != nil { + return E.Cause(err, "parse ECH keys") + } + echKeySet, err := cftls.EXP_NewECHKeySet(echKeys) + if err != nil { + return E.Cause(err, "create ECH key set") + } + c.config.ServerECHProvider = echKeySet + c.logger.Info("reloaded ECH keys") } -} - -func (c *echServerConfig) reloadECHKey() error { - echKeyContent, err := os.ReadFile(c.echKeyPath) - if err != nil { - return err - } - block, rest := pem.Decode(echKeyContent) - if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { - return E.New("invalid ECH keys pem") - } - echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes) - if err != nil { - return E.Cause(err, "parse ECH keys") - } - echKeySet, err := cftls.EXP_NewECHKeySet(echKeys) - if err != nil { - return E.Cause(err, "create ECH key set") - } - c.config.ServerECHProvider = echKeySet - c.logger.Info("reloaded ECH keys") return nil } @@ -213,12 +149,7 @@ func (c *echServerConfig) Close() error { var err error if c.watcher != nil { err = E.Append(err, c.watcher.Close(), func(err error) error { - return E.Cause(err, "close certificate watcher") - }) - } - if c.echWatcher != nil { - err = E.Append(err, c.echWatcher.Close(), func(err error) error { - return E.Cause(err, "close ECH key watcher") + return E.Cause(err, "close credentials watcher") }) } return err diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 7184bdb36b..7001bd3a04 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -7,14 +7,13 @@ import ( "os" "strings" + "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" "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/ntp" - - "github.com/fsnotify/fsnotify" ) var errInsecureUnused = E.New("tls: insecure unused") @@ -27,7 +26,7 @@ type STDServerConfig struct { key []byte certificatePath string keyPath string - watcher *fsnotify.Watcher + watcher *fswatch.Watcher } func (c *STDServerConfig) ServerName() string { @@ -88,59 +87,37 @@ func (c *STDServerConfig) Start() error { } func (c *STDServerConfig) startWatcher() error { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } + var watchPath []string if c.certificatePath != "" { - err = watcher.Add(c.certificatePath) - if err != nil { - return err - } + watchPath = append(watchPath, c.certificatePath) } if c.keyPath != "" { - err = watcher.Add(c.keyPath) - if err != nil { - return err - } + watchPath = append(watchPath, c.keyPath) } - c.watcher = watcher - go c.loopUpdate() - return nil -} - -func (c *STDServerConfig) loopUpdate() { - for { - select { - case event, ok := <-c.watcher.Events: - if !ok { - return - } - if event.Op&fsnotify.Write != fsnotify.Write { - continue - } - err := c.reloadKeyPair() + watcher, err := fswatch.NewWatcher(fswatch.Options{ + Path: watchPath, + Callback: func(path string) { + err := c.certificateUpdated(path) if err != nil { - c.logger.Error(E.Cause(err, "reload TLS key pair")) - } - case err, ok := <-c.watcher.Errors: - if !ok { - return + c.logger.Error(err) } - c.logger.Error(E.Cause(err, "fsnotify error")) - } + }, + }) + if err != nil { + return err } + c.watcher = watcher + return nil } -func (c *STDServerConfig) reloadKeyPair() error { - if c.certificatePath != "" { +func (c *STDServerConfig) certificateUpdated(path string) error { + if path == c.certificatePath { certificate, err := os.ReadFile(c.certificatePath) if err != nil { return E.Cause(err, "reload certificate from ", c.certificatePath) } c.certificate = certificate - } - if c.keyPath != "" { + } else if path == c.keyPath { key, err := os.ReadFile(c.keyPath) if err != nil { return E.Cause(err, "reload key from ", c.keyPath) diff --git a/constant/rule.go b/constant/rule.go index fb0f39e22e..718e79a5e6 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -11,6 +11,7 @@ const ( ) const ( + RuleSetTypeInline = "inline" RuleSetTypeLocal = "local" RuleSetTypeRemote = "remote" RuleSetFormatSource = "source" diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index b92d80f3d7..dfe71d9e39 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -1,48 +1,56 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: `type: inline` + # rule-set !!! question "Since sing-box 1.8.0" ### Structure -```json -{ - "type": "", - "tag": "", - "format": "", - - ... // Typed Fields -} -``` - -#### Local Structure - -```json -{ - "type": "local", - - ... - - "path": "" -} -``` - -#### Remote Structure - -!!! info "" - - Remote rule-set will be cached if `experimental.cache_file.enabled`. - -```json -{ - "type": "remote", - - ..., - - "url": "", - "download_detour": "", - "update_interval": "" -} -``` +=== "Inline" + + !!! question "Since sing-box 1.10.0" + + ```json + { + "type": "inline", // optional + "tag": "", + "rules": [] + } + ``` + +=== "Local File" + + ```json + { + "type": "local", + "tag": "", + "format": "source", // or binary + "path": "" + } + ``` + +=== "Remote File" + + !!! info "" + + Remote rule-set will be cached if `experimental.cache_file.enabled`. + + ```json + { + "type": "remote", + "tag": "", + "format": "source", // or binary + "url": "", + "download_detour": "", // optional + "update_interval": "" // optional + } + ``` ### Fields @@ -58,11 +66,23 @@ Type of rule-set, `local` or `remote`. Tag of rule-set. +### Inline Fields + +!!! question "Since sing-box 1.10.0" + +#### rules + +==Required== + +List of [Headless Rule](./headless-rule.md/). + +### Local or Remote Fields + #### format ==Required== -Format of rule-set, `source` or `binary`. +Format of rule-set file, `source` or `binary`. ### Local Fields @@ -70,6 +90,10 @@ Format of rule-set, `source` or `binary`. ==Required== +!!! note "" + + Will be automatically reloaded if file modified since sing-box 1.10.0. + File path of rule-set. ### Remote Fields diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index b1441a8abc..799aa0b09d 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -178,6 +178,10 @@ The server certificate line array, in PEM format. #### certificate_path +!!! note "" + + Will be automatically reloaded if file modified. + The path to the server certificate, in PEM format. #### key @@ -190,6 +194,10 @@ The server private key line array, in PEM format. ==Server only== +!!! note "" + + Will be automatically reloaded if file modified. + The path to the server private key, in PEM format. ## Custom TLS support @@ -266,6 +274,10 @@ ECH key line array, in PEM format. ==Server only== +!!! note "" + + Will be automatically reloaded if file modified. + The path to ECH key, in PEM format. #### config @@ -397,8 +409,4 @@ A hexadecimal string with zero to eight digits. The maximum time difference between the server and the client. -Check disabled if empty. - -### Reload - -For server configuration, certificate, key and ECH key will be automatically reloaded if modified. \ No newline at end of file +Check disabled if empty. \ No newline at end of file diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 360c453642..68de98459f 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -176,12 +176,20 @@ TLS 版本值: #### certificate_path +!!! note "" + + 文件更改时将自动重新加载。 + 服务器 PEM 证书路径。 #### key ==仅服务器== +!!! note "" + + 文件更改时将自动重新加载。 + 服务器 PEM 私钥行数组。 #### key_path @@ -258,6 +266,10 @@ ECH PEM 密钥行数组 ==仅服务器== +!!! note "" + + 文件更改时将自动重新加载。 + ECH PEM 密钥路径 #### config @@ -384,7 +396,3 @@ ACME DNS01 验证字段。如果配置,将禁用其他验证方法。 服务器与和客户端之间允许的最大时间差。 默认禁用检查。 - -### 重载 - -对于服务器配置,如果修改,证书和密钥将自动重新加载。 \ No newline at end of file diff --git a/go.mod b/go.mod index ffcd0d796d..7666dd24e5 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 - github.com/fsnotify/fsnotify v1.7.0 github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 @@ -23,6 +22,7 @@ require ( github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 + github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.4 github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f github.com/sagernet/quic-go v0.47.0-beta.2 @@ -59,6 +59,7 @@ require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect @@ -81,7 +82,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect - github.com/sagernet/fswatch v0.1.1 // 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.5 // indirect diff --git a/option/rule_set.go b/option/rule_set.go index ec32d0a13a..4e9eb0f8b3 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -14,9 +14,10 @@ import ( ) type _RuleSet struct { - Type string `json:"type"` + Type string `json:"type,omitempty"` Tag string `json:"tag"` - Format string `json:"format"` + Format string `json:"format,omitempty"` + InlineOptions PlainRuleSet `json:"-"` LocalOptions LocalRuleSet `json:"-"` RemoteOptions RemoteRuleSet `json:"-"` } @@ -26,6 +27,9 @@ type RuleSet _RuleSet func (r RuleSet) MarshalJSON() ([]byte, error) { var v any switch r.Type { + case "", C.RuleSetTypeInline: + r.Type = "" + v = r.InlineOptions case C.RuleSetTypeLocal: v = r.LocalOptions case C.RuleSetTypeRemote: @@ -44,21 +48,26 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { if r.Tag == "" { return E.New("missing tag") } - switch r.Format { - case "": - return E.New("missing format") - case C.RuleSetFormatSource, C.RuleSetFormatBinary: - default: - return E.New("unknown rule-set format: " + r.Format) + if r.Type != C.RuleSetTypeInline { + switch r.Format { + case "": + return E.New("missing format") + case C.RuleSetFormatSource, C.RuleSetFormatBinary: + default: + return E.New("unknown rule-set format: " + r.Format) + } + } else { + r.Format = "" } var v any switch r.Type { + case "", C.RuleSetTypeInline: + r.Type = C.RuleSetTypeInline + v = &r.InlineOptions case C.RuleSetTypeLocal: v = &r.LocalOptions case C.RuleSetTypeRemote: v = &r.RemoteOptions - case "": - return E.New("missing type") default: return E.New("unknown rule-set type: " + r.Type) } @@ -214,15 +223,13 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { return nil } -func (r PlainRuleSetCompat) Upgrade() PlainRuleSet { - var result PlainRuleSet +func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) { switch r.Version { case C.RuleSetVersion1, C.RuleSetVersion2: - result = r.Options default: - panic("unknown rule-set version: " + F.ToString(r.Version)) + return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version)) } - return result + return r.Options, nil } type PlainRuleSet struct { diff --git a/route/rule_set.go b/route/rule_set.go index 9907a1c673..39d51e6f54 100644 --- a/route/rule_set.go +++ b/route/rule_set.go @@ -20,8 +20,8 @@ import ( func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) { switch options.Type { - case C.RuleSetTypeLocal: - return NewLocalRuleSet(ctx, router, options) + case C.RuleSetTypeInline, C.RuleSetTypeLocal, "": + return NewLocalRuleSet(ctx, router, logger, options) case C.RuleSetTypeRemote: return NewRemoteRuleSet(ctx, router, logger, options), nil default: diff --git a/route/rule_set_local.go b/route/rule_set_local.go index 96d30dd5e8..893842d5cc 100644 --- a/route/rule_set_local.go +++ b/route/rule_set_local.go @@ -3,8 +3,10 @@ package route import ( "context" "os" + "path/filepath" "strings" + "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/srs" C "github.com/sagernet/sing-box/constant" @@ -14,6 +16,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service/filemanager" @@ -23,50 +26,55 @@ import ( var _ adapter.RuleSet = (*LocalRuleSet)(nil) type LocalRuleSet struct { - tag string - rules []adapter.HeadlessRule - metadata adapter.RuleSetMetadata - refs atomic.Int32 + router adapter.Router + logger logger.Logger + tag string + rules []adapter.HeadlessRule + metadata adapter.RuleSetMetadata + fileFormat string + watcher *fswatch.Watcher + refs atomic.Int32 } -func NewLocalRuleSet(ctx context.Context, router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) { - var plainRuleSet option.PlainRuleSet - switch options.Format { - case C.RuleSetFormatSource, "": - content, err := os.ReadFile(filemanager.BasePath(ctx, options.LocalOptions.Path)) - if err != nil { - return nil, err - } - compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content) - if err != nil { - return nil, err +func NewLocalRuleSet(ctx context.Context, router adapter.Router, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { + ruleSet := &LocalRuleSet{ + router: router, + logger: logger, + tag: options.Tag, + fileFormat: options.Format, + } + if options.Type == C.RuleSetTypeInline { + if len(options.InlineOptions.Rules) == 0 { + return nil, E.New("empty inline rule-set") } - plainRuleSet = compat.Upgrade() - case C.RuleSetFormatBinary: - setFile, err := os.Open(filemanager.BasePath(ctx, options.LocalOptions.Path)) + err := ruleSet.reloadRules(options.InlineOptions.Rules) if err != nil { return nil, err } - plainRuleSet, err = srs.Read(setFile, false) + } else { + err := ruleSet.reloadFile(filemanager.BasePath(ctx, options.LocalOptions.Path)) if err != nil { return nil, err } - default: - return nil, E.New("unknown rule-set format: ", options.Format) } - rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) - var err error - for i, ruleOptions := range plainRuleSet.Rules { - rules[i], err = NewHeadlessRule(router, ruleOptions) + if options.Type == C.RuleSetTypeLocal { + var watcher *fswatch.Watcher + filePath, _ := filepath.Abs(options.LocalOptions.Path) + watcher, err := fswatch.NewWatcher(fswatch.Options{ + Path: []string{filePath}, + Callback: func(path string) { + uErr := ruleSet.reloadFile(path) + if uErr != nil { + logger.Error(E.Cause(uErr, "reload rule-set ", options.Tag)) + } + }, + }) if err != nil { - return nil, E.Cause(err, "parse rule_set.rules.[", i, "]") + return nil, err } + ruleSet.watcher = watcher } - var metadata adapter.RuleSetMetadata - metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) - metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) - metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) - return &LocalRuleSet{tag: options.Tag, rules: rules, metadata: metadata}, nil + return ruleSet, nil } func (s *LocalRuleSet) Name() string { @@ -78,6 +86,61 @@ func (s *LocalRuleSet) String() string { } func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error { + if s.watcher != nil { + err := s.watcher.Start() + if err != nil { + s.logger.Error(E.Cause(err, "watch rule-set file")) + } + } + return nil +} + +func (s *LocalRuleSet) reloadFile(path string) error { + var plainRuleSet option.PlainRuleSet + switch s.fileFormat { + case C.RuleSetFormatSource, "": + content, err := os.ReadFile(path) + if err != nil { + return err + } + compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content) + if err != nil { + return err + } + plainRuleSet, err = compat.Upgrade() + if err != nil { + return err + } + case C.RuleSetFormatBinary: + setFile, err := os.Open(path) + if err != nil { + return err + } + plainRuleSet, err = srs.Read(setFile, false) + if err != nil { + return err + } + default: + return E.New("unknown rule-set format: ", s.fileFormat) + } + return s.reloadRules(plainRuleSet.Rules) +} + +func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error { + rules := make([]adapter.HeadlessRule, len(headlessRules)) + var err error + for i, ruleOptions := range headlessRules { + rules[i], err = NewHeadlessRule(s.router, ruleOptions) + if err != nil { + return E.Cause(err, "parse rule_set.rules.[", i, "]") + } + } + var metadata adapter.RuleSetMetadata + metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule) + metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule) + metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule) + s.rules = rules + s.metadata = metadata return nil } @@ -118,7 +181,7 @@ func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetU func (s *LocalRuleSet) Close() error { s.rules = nil - return nil + return common.Close(common.PtrOrNil(s.watcher)) } func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool { diff --git a/route/rule_set_remote.go b/route/rule_set_remote.go index 1473a494a9..03662ee42c 100644 --- a/route/rule_set_remote.go +++ b/route/rule_set_remote.go @@ -168,7 +168,10 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { if err != nil { return err } - plainRuleSet = compat.Upgrade() + plainRuleSet, err = compat.Upgrade() + if err != nil { + return err + } case C.RuleSetFormatBinary: plainRuleSet, err = srs.Read(bytes.NewReader(content), false) if err != nil { From 9dc3bb975a5c44fef2e5e8ee4f80e87f242070ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 7 Jul 2024 15:45:50 +0800 Subject: [PATCH 0059/1272] Improve QUIC sniffer --- adapter/inbound.go | 9 +- common/ja3/LICENSE | 29 +++ common/ja3/README.md | 3 + common/ja3/error.go | 31 +++ common/ja3/ja3.go | 83 +++++++ common/ja3/parser.go | 357 +++++++++++++++++++++++++++ common/sniff/bittorrent.go | 44 ++-- common/sniff/bittorrent_test.go | 11 +- common/sniff/dns.go | 20 +- common/sniff/dtls.go | 13 +- common/sniff/dtls_test.go | 7 +- common/sniff/http.go | 8 +- common/sniff/http_test.go | 7 +- common/sniff/quic.go | 214 ++++++++++------ common/sniff/quic_blacklist.go | 24 ++ common/sniff/quic_test.go | 56 ++++- common/sniff/sniff.go | 29 ++- common/sniff/stun.go | 11 +- common/sniff/stun_test.go | 7 +- common/sniff/tls.go | 8 +- constant/protocol.go | 8 + docs/configuration/route/rule.md | 15 +- docs/configuration/route/rule.zh.md | 15 +- docs/configuration/route/sniff.md | 29 ++- docs/configuration/route/sniff.zh.md | 29 ++- go.mod | 2 +- option/rule.go | 1 + route/router.go | 139 ++++++----- route/rule_default.go | 5 + route/rule_item_client.go | 37 +++ 30 files changed, 1012 insertions(+), 239 deletions(-) create mode 100644 common/ja3/LICENSE create mode 100644 common/ja3/README.md create mode 100644 common/ja3/error.go create mode 100644 common/ja3/ja3.go create mode 100644 common/ja3/parser.go create mode 100644 common/sniff/quic_blacklist.go create mode 100644 route/rule_item_client.go diff --git a/adapter/inbound.go b/adapter/inbound.go index 56eb562308..ffc4c56454 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -31,11 +31,16 @@ type InboundContext struct { Network string Source M.Socksaddr Destination M.Socksaddr - Domain string - Protocol string User string Outbound string + // sniffer + + Protocol string + Domain string + Client string + SniffContext any + // cache InboundDetour string diff --git a/common/ja3/LICENSE b/common/ja3/LICENSE new file mode 100644 index 0000000000..b42ec95db2 --- /dev/null +++ b/common/ja3/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, Open Systems AG +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/common/ja3/README.md b/common/ja3/README.md new file mode 100644 index 0000000000..5c0bd8ae27 --- /dev/null +++ b/common/ja3/README.md @@ -0,0 +1,3 @@ +# JA3 + +mod from: https://github.com/open-ch/ja3 \ No newline at end of file diff --git a/common/ja3/error.go b/common/ja3/error.go new file mode 100644 index 0000000000..cab8549255 --- /dev/null +++ b/common/ja3/error.go @@ -0,0 +1,31 @@ +// Copyright (c) 2018, Open Systems AG. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +package ja3 + +import "fmt" + +// Error types +const ( + LengthErr string = "length check %v failed" + ContentTypeErr string = "content type not matching" + VersionErr string = "version check %v failed" + HandshakeTypeErr string = "handshake type not matching" + SNITypeErr string = "SNI type not supported" +) + +// ParseError can be encountered while parsing a segment +type ParseError struct { + errType string + check int +} + +func (e *ParseError) Error() string { + if e.errType == LengthErr || e.errType == VersionErr { + return fmt.Sprintf(e.errType, e.check) + } + return fmt.Sprint(e.errType) +} diff --git a/common/ja3/ja3.go b/common/ja3/ja3.go new file mode 100644 index 0000000000..608819fe1e --- /dev/null +++ b/common/ja3/ja3.go @@ -0,0 +1,83 @@ +// Copyright (c) 2018, Open Systems AG. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +package ja3 + +import ( + "crypto/md5" + "encoding/hex" + + "golang.org/x/exp/slices" +) + +type ClientHello struct { + Version uint16 + CipherSuites []uint16 + Extensions []uint16 + EllipticCurves []uint16 + EllipticCurvePF []uint8 + Versions []uint16 + SignatureAlgorithms []uint16 + ServerName string + ja3ByteString []byte + ja3Hash string +} + +func (j *ClientHello) Equals(another *ClientHello, ignoreExtensionsSequence bool) bool { + if j.Version != another.Version { + return false + } + if !slices.Equal(j.CipherSuites, another.CipherSuites) { + return false + } + if !ignoreExtensionsSequence && !slices.Equal(j.Extensions, another.Extensions) { + return false + } + if ignoreExtensionsSequence && !slices.Equal(j.Extensions, another.sortedExtensions()) { + return false + } + if !slices.Equal(j.EllipticCurves, another.EllipticCurves) { + return false + } + if !slices.Equal(j.EllipticCurvePF, another.EllipticCurvePF) { + return false + } + if !slices.Equal(j.SignatureAlgorithms, another.SignatureAlgorithms) { + return false + } + return true +} + +func (j *ClientHello) sortedExtensions() []uint16 { + extensions := make([]uint16, len(j.Extensions)) + copy(extensions, j.Extensions) + slices.Sort(extensions) + return extensions +} + +func Compute(payload []byte) (*ClientHello, error) { + ja3 := ClientHello{} + err := ja3.parseSegment(payload) + return &ja3, err +} + +func (j *ClientHello) String() string { + if j.ja3ByteString == nil { + j.marshalJA3() + } + return string(j.ja3ByteString) +} + +func (j *ClientHello) Hash() string { + if j.ja3ByteString == nil { + j.marshalJA3() + } + if j.ja3Hash == "" { + h := md5.Sum(j.ja3ByteString) + j.ja3Hash = hex.EncodeToString(h[:]) + } + return j.ja3Hash +} diff --git a/common/ja3/parser.go b/common/ja3/parser.go new file mode 100644 index 0000000000..f9cca60382 --- /dev/null +++ b/common/ja3/parser.go @@ -0,0 +1,357 @@ +// Copyright (c) 2018, Open Systems AG. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +package ja3 + +import ( + "encoding/binary" + "strconv" +) + +const ( + // Constants used for parsing + recordLayerHeaderLen int = 5 + handshakeHeaderLen int = 6 + randomDataLen int = 32 + sessionIDHeaderLen int = 1 + cipherSuiteHeaderLen int = 2 + compressMethodHeaderLen int = 1 + extensionsHeaderLen int = 2 + extensionHeaderLen int = 4 + sniExtensionHeaderLen int = 5 + ecExtensionHeaderLen int = 2 + ecpfExtensionHeaderLen int = 1 + versionExtensionHeaderLen int = 1 + signatureAlgorithmsExtensionHeaderLen int = 2 + contentType uint8 = 22 + handshakeType uint8 = 1 + sniExtensionType uint16 = 0 + sniNameDNSHostnameType uint8 = 0 + ecExtensionType uint16 = 10 + ecpfExtensionType uint16 = 11 + versionExtensionType uint16 = 43 + signatureAlgorithmsExtensionType uint16 = 13 + + // Versions + // The bitmask covers the versions SSL3.0 to TLS1.2 + tlsVersionBitmask uint16 = 0xFFFC + tls13 uint16 = 0x0304 + + // GREASE values + // The bitmask covers all GREASE values + GreaseBitmask uint16 = 0x0F0F + + // Constants used for marshalling + dashByte = byte(45) + commaByte = byte(44) +) + +// parseSegment to populate the corresponding ClientHello object or return an error +func (j *ClientHello) parseSegment(segment []byte) error { + // Check if we can decode the next fields + if len(segment) < recordLayerHeaderLen { + return &ParseError{LengthErr, 1} + } + + // Check if we have "Content Type: Handshake (22)" + contType := uint8(segment[0]) + if contType != contentType { + return &ParseError{errType: ContentTypeErr} + } + + // Check if TLS record layer version is supported + tlsRecordVersion := uint16(segment[1])<<8 | uint16(segment[2]) + if tlsRecordVersion&tlsVersionBitmask != 0x0300 && tlsRecordVersion != tls13 { + return &ParseError{VersionErr, 1} + } + + // Check that the Handshake is as long as expected from the length field + segmentLen := uint16(segment[3])<<8 | uint16(segment[4]) + if len(segment[recordLayerHeaderLen:]) < int(segmentLen) { + return &ParseError{LengthErr, 2} + } + // Keep the Handshake messege, ignore any additional following record types + hs := segment[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)] + + err := j.parseHandshake(hs) + + return err +} + +// parseHandshake body +func (j *ClientHello) parseHandshake(hs []byte) error { + // Check if we can decode the next fields + if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen { + return &ParseError{LengthErr, 3} + } + + // Check if we have "Handshake Type: Client Hello (1)" + handshType := uint8(hs[0]) + if handshType != handshakeType { + return &ParseError{errType: HandshakeTypeErr} + } + + // Check if actual length of handshake matches (this is a great exclusion criterion for false positives, + // as these fields have to match the actual length of the rest of the segment) + handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3]) + if len(hs[4:]) != int(handshakeLen) { + return &ParseError{LengthErr, 4} + } + + // Check if Client Hello version is supported + tlsVersion := uint16(hs[4])<<8 | uint16(hs[5]) + if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 { + return &ParseError{VersionErr, 2} + } + j.Version = tlsVersion + + // Check if we can decode the next fields + sessionIDLen := uint8(hs[38]) + if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) { + return &ParseError{LengthErr, 5} + } + + // Cipher Suites + cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):] + + // Check if we can decode the next fields + if len(cs) < cipherSuiteHeaderLen { + return &ParseError{LengthErr, 6} + } + + csLen := uint16(cs[0])<<8 | uint16(cs[1]) + numCiphers := int(csLen / 2) + cipherSuites := make([]uint16, 0, numCiphers) + + // Check if we can decode the next fields + if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen { + return &ParseError{LengthErr, 7} + } + + for i := 0; i < numCiphers; i++ { + cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1]) + cipherSuites = append(cipherSuites, cipherSuite) + } + j.CipherSuites = cipherSuites + + // Check if we can decode the next fields + compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)]) + if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) { + return &ParseError{LengthErr, 8} + } + + // Extensions + exs := cs[cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen):] + + err := j.parseExtensions(exs) + + return err +} + +// parseExtensions of the handshake +func (j *ClientHello) parseExtensions(exs []byte) error { + // Check for no extensions, this fields header is nonexistent if no body is used + if len(exs) == 0 { + return nil + } + + // Check if we can decode the next fields + if len(exs) < extensionsHeaderLen { + return &ParseError{LengthErr, 9} + } + + exsLen := uint16(exs[0])<<8 | uint16(exs[1]) + exs = exs[extensionsHeaderLen:] + + // Check if we can decode the next fields + if len(exs) < int(exsLen) { + return &ParseError{LengthErr, 10} + } + + var sni []byte + var extensions, ellipticCurves []uint16 + var ellipticCurvePF []uint8 + var versions []uint16 + var signatureAlgorithms []uint16 + for len(exs) > 0 { + + // Check if we can decode the next fields + if len(exs) < extensionHeaderLen { + return &ParseError{LengthErr, 11} + } + + exType := uint16(exs[0])<<8 | uint16(exs[1]) + exLen := uint16(exs[2])<<8 | uint16(exs[3]) + // Ignore any GREASE extensions + extensions = append(extensions, exType) + // Check if we can decode the next fields + if len(exs) < extensionHeaderLen+int(exLen) { + return &ParseError{LengthErr, 12} + } + + sex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)] + + switch exType { + case sniExtensionType: // Extensions: server_name + + // Check if we can decode the next fields + if len(sex) < sniExtensionHeaderLen { + return &ParseError{LengthErr, 13} + } + + sniType := uint8(sex[2]) + sniLen := uint16(sex[3])<<8 | uint16(sex[4]) + sex = sex[sniExtensionHeaderLen:] + + // Check if we can decode the next fields + if len(sex) != int(sniLen) { + return &ParseError{LengthErr, 14} + } + + switch sniType { + case sniNameDNSHostnameType: + sni = sex + default: + return &ParseError{errType: SNITypeErr} + } + case ecExtensionType: // Extensions: supported_groups + + // Check if we can decode the next fields + if len(sex) < ecExtensionHeaderLen { + return &ParseError{LengthErr, 15} + } + + ecsLen := uint16(sex[0])<<8 | uint16(sex[1]) + numCurves := int(ecsLen / 2) + ellipticCurves = make([]uint16, 0, numCurves) + sex = sex[ecExtensionHeaderLen:] + + // Check if we can decode the next fields + if len(sex) != int(ecsLen) { + return &ParseError{LengthErr, 16} + } + + for i := 0; i < numCurves; i++ { + ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2]) + ellipticCurves = append(ellipticCurves, ecType) + } + + case ecpfExtensionType: // Extensions: ec_point_formats + + // Check if we can decode the next fields + if len(sex) < ecpfExtensionHeaderLen { + return &ParseError{LengthErr, 17} + } + + ecpfsLen := uint8(sex[0]) + numPF := int(ecpfsLen) + ellipticCurvePF = make([]uint8, numPF) + sex = sex[ecpfExtensionHeaderLen:] + + // Check if we can decode the next fields + if len(sex) != numPF { + return &ParseError{LengthErr, 18} + } + + for i := 0; i < numPF; i++ { + ellipticCurvePF[i] = uint8(sex[i]) + } + case versionExtensionType: + if len(sex) < versionExtensionHeaderLen { + return &ParseError{LengthErr, 19} + } + versionsLen := int(sex[0]) + for i := 0; i < versionsLen; i += 2 { + versions = append(versions, binary.BigEndian.Uint16(sex[1:][i:])) + } + case signatureAlgorithmsExtensionType: + if len(sex) < signatureAlgorithmsExtensionHeaderLen { + return &ParseError{LengthErr, 20} + } + ssaLen := binary.BigEndian.Uint16(sex) + for i := 0; i < int(ssaLen); i += 2 { + signatureAlgorithms = append(signatureAlgorithms, binary.BigEndian.Uint16(sex[2:][i:])) + } + } + exs = exs[4+exLen:] + } + j.ServerName = string(sni) + j.Extensions = extensions + j.EllipticCurves = ellipticCurves + j.EllipticCurvePF = ellipticCurvePF + j.Versions = versions + j.SignatureAlgorithms = signatureAlgorithms + return nil +} + +// marshalJA3 into a byte string +func (j *ClientHello) marshalJA3() { + // An uint16 can contain numbers with up to 5 digits and an uint8 can contain numbers with up to 3 digits, but we + // also need a byte for each separating character, except at the end. + byteStringLen := 6*(1+len(j.CipherSuites)+len(j.Extensions)+len(j.EllipticCurves)) + 4*len(j.EllipticCurvePF) - 1 + byteString := make([]byte, 0, byteStringLen) + + // Version + byteString = strconv.AppendUint(byteString, uint64(j.Version), 10) + byteString = append(byteString, commaByte) + + // Cipher Suites + if len(j.CipherSuites) != 0 { + for _, val := range j.CipherSuites { + if val&GreaseBitmask != 0x0A0A { + continue + } + byteString = strconv.AppendUint(byteString, uint64(val), 10) + byteString = append(byteString, dashByte) + } + // Replace last dash with a comma + byteString[len(byteString)-1] = commaByte + } else { + byteString = append(byteString, commaByte) + } + + // Extensions + if len(j.Extensions) != 0 { + for _, val := range j.Extensions { + if val&GreaseBitmask != 0x0A0A { + continue + } + byteString = strconv.AppendUint(byteString, uint64(val), 10) + byteString = append(byteString, dashByte) + } + // Replace last dash with a comma + byteString[len(byteString)-1] = commaByte + } else { + byteString = append(byteString, commaByte) + } + + // Elliptic curves + if len(j.EllipticCurves) != 0 { + for _, val := range j.EllipticCurves { + if val&GreaseBitmask != 0x0A0A { + continue + } + byteString = strconv.AppendUint(byteString, uint64(val), 10) + byteString = append(byteString, dashByte) + } + // Replace last dash with a comma + byteString[len(byteString)-1] = commaByte + } else { + byteString = append(byteString, commaByte) + } + + // ECPF + if len(j.EllipticCurvePF) != 0 { + for _, val := range j.EllipticCurvePF { + byteString = strconv.AppendUint(byteString, uint64(val), 10) + byteString = append(byteString, dashByte) + } + // Remove last dash + byteString = byteString[:len(byteString)-1] + } + + j.ja3ByteString = byteString +} diff --git a/common/sniff/bittorrent.go b/common/sniff/bittorrent.go index debc35ca4f..9e123c41e9 100644 --- a/common/sniff/bittorrent.go +++ b/common/sniff/bittorrent.go @@ -19,45 +19,44 @@ const ( // BitTorrent detects if the stream is a BitTorrent connection. // For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html -func BitTorrent(_ context.Context, reader io.Reader) (*adapter.InboundContext, error) { +func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { var first byte err := binary.Read(reader, binary.BigEndian, &first) if err != nil { - return nil, err + return err } if first != 19 { - return nil, os.ErrInvalid + return os.ErrInvalid } var protocol [19]byte _, err = reader.Read(protocol[:]) if err != nil { - return nil, err + return err } if string(protocol[:]) != "BitTorrent protocol" { - return nil, os.ErrInvalid + return os.ErrInvalid } - return &adapter.InboundContext{ - Protocol: C.ProtocolBitTorrent, - }, nil + metadata.Protocol = C.ProtocolBitTorrent + return nil } // UTP detects if the packet is a uTP connection packet. // For the uTP protocol specification, see // 1. https://www.bittorrent.org/beps/bep_0029.html // 2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112 -func UTP(_ context.Context, packet []byte) (*adapter.InboundContext, error) { +func UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) error { // A valid uTP packet must be at least 20 bytes long. if len(packet) < 20 { - return nil, os.ErrInvalid + return os.ErrInvalid } version := packet[0] & 0x0F ty := packet[0] >> 4 if version != 1 || ty > 4 { - return nil, os.ErrInvalid + return os.ErrInvalid } // Validate the extensions @@ -66,36 +65,35 @@ func UTP(_ context.Context, packet []byte) (*adapter.InboundContext, error) { for extension != 0 { err := binary.Read(reader, binary.BigEndian, &extension) if err != nil { - return nil, err + return err } var length byte err = binary.Read(reader, binary.BigEndian, &length) if err != nil { - return nil, err + return err } _, err = reader.Seek(int64(length), io.SeekCurrent) if err != nil { - return nil, err + return err } } - - return &adapter.InboundContext{ - Protocol: C.ProtocolBitTorrent, - }, nil + metadata.Protocol = C.ProtocolBitTorrent + return nil } // UDPTracker detects if the packet is a UDP Tracker Protocol packet. // For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html -func UDPTracker(_ context.Context, packet []byte) (*adapter.InboundContext, error) { +func UDPTracker(_ context.Context, metadata *adapter.InboundContext, packet []byte) error { if len(packet) < trackerConnectMinSize { - return nil, os.ErrInvalid + return os.ErrInvalid } if binary.BigEndian.Uint64(packet[:8]) != trackerProtocolID { - return nil, os.ErrInvalid + return os.ErrInvalid } if binary.BigEndian.Uint32(packet[8:12]) != trackerConnectFlag { - return nil, os.ErrInvalid + return os.ErrInvalid } - return &adapter.InboundContext{Protocol: C.ProtocolBitTorrent}, nil + metadata.Protocol = C.ProtocolBitTorrent + return nil } diff --git a/common/sniff/bittorrent_test.go b/common/sniff/bittorrent_test.go index 6b3ab64e95..65f095bdac 100644 --- a/common/sniff/bittorrent_test.go +++ b/common/sniff/bittorrent_test.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "testing" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" @@ -24,7 +25,8 @@ func TestSniffBittorrent(t *testing.T) { for _, pkt := range packets { pkt, err := hex.DecodeString(pkt) require.NoError(t, err) - metadata, err := sniff.BitTorrent(context.TODO(), bytes.NewReader(pkt)) + var metadata adapter.InboundContext + err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt)) require.NoError(t, err) require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) } @@ -43,8 +45,8 @@ func TestSniffUTP(t *testing.T) { for _, pkt := range packets { pkt, err := hex.DecodeString(pkt) require.NoError(t, err) - - metadata, err := sniff.UTP(context.TODO(), pkt) + var metadata adapter.InboundContext + err = sniff.UTP(context.TODO(), &metadata, pkt) require.NoError(t, err) require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) } @@ -63,7 +65,8 @@ func TestSniffUDPTracker(t *testing.T) { pkt, err := hex.DecodeString(pkt) require.NoError(t, err) - metadata, err := sniff.UDPTracker(context.TODO(), pkt) + var metadata adapter.InboundContext + err = sniff.UDPTracker(context.TODO(), &metadata, pkt) require.NoError(t, err) require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) } diff --git a/common/sniff/dns.go b/common/sniff/dns.go index d69f3b158c..96670eca9c 100644 --- a/common/sniff/dns.go +++ b/common/sniff/dns.go @@ -17,18 +17,17 @@ import ( mDNS "github.com/miekg/dns" ) -func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.InboundContext, error) { +func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundContext, reader io.Reader) error { var length uint16 err := binary.Read(reader, binary.BigEndian, &length) if err != nil { - return nil, err + return os.ErrInvalid } if length == 0 { - return nil, os.ErrInvalid + return os.ErrInvalid } buffer := buf.NewSize(int(length)) defer buffer.Release() - readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100) var readTask task.Group readTask.Append0(func(ctx context.Context) error { @@ -37,19 +36,20 @@ func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter. err = readTask.Run(readCtx) cancel() if err != nil { - return nil, err + return err } - return DomainNameQuery(readCtx, buffer.Bytes()) + return DomainNameQuery(readCtx, metadata, buffer.Bytes()) } -func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { +func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { var msg mDNS.Msg err := msg.Unpack(packet) if err != nil { - return nil, err + return err } if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) { - return nil, os.ErrInvalid + return os.ErrInvalid } - return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil + metadata.Protocol = C.ProtocolDNS + return nil } diff --git a/common/sniff/dtls.go b/common/sniff/dtls.go index 6469b09709..e2704a27ae 100644 --- a/common/sniff/dtls.go +++ b/common/sniff/dtls.go @@ -8,24 +8,25 @@ import ( C "github.com/sagernet/sing-box/constant" ) -func DTLSRecord(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { +func DTLSRecord(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { const fixedHeaderSize = 13 if len(packet) < fixedHeaderSize { - return nil, os.ErrInvalid + return os.ErrInvalid } contentType := packet[0] switch contentType { case 20, 21, 22, 23, 25: default: - return nil, os.ErrInvalid + return os.ErrInvalid } versionMajor := packet[1] if versionMajor != 0xfe { - return nil, os.ErrInvalid + return os.ErrInvalid } versionMinor := packet[2] if versionMinor != 0xff && versionMinor != 0xfd { - return nil, os.ErrInvalid + return os.ErrInvalid } - return &adapter.InboundContext{Protocol: C.ProtocolDTLS}, nil + metadata.Protocol = C.ProtocolDTLS + return nil } diff --git a/common/sniff/dtls_test.go b/common/sniff/dtls_test.go index 45f7712642..48c9b42b31 100644 --- a/common/sniff/dtls_test.go +++ b/common/sniff/dtls_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "testing" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" @@ -15,7 +16,8 @@ func TestSniffDTLSClientHello(t *testing.T) { t.Parallel() packet, err := hex.DecodeString("16fefd0000000000000000007e010000720000000000000072fefd668a43523798e064bd806d0c87660de9c611a59bbdfc3892c4e072d94f2cafc40000000cc02bc02fc00ac014c02cc0300100003c000d0010000e0403050306030401050106010807ff01000100000a00080006001d00170018000b00020100000e000900060008000700010000170000") require.NoError(t, err) - metadata, err := sniff.DTLSRecord(context.Background(), packet) + var metadata adapter.InboundContext + err = sniff.DTLSRecord(context.Background(), &metadata, packet) require.NoError(t, err) require.Equal(t, metadata.Protocol, C.ProtocolDTLS) } @@ -24,7 +26,8 @@ func TestSniffDTLSClientApplicationData(t *testing.T) { t.Parallel() packet, err := hex.DecodeString("17fefd000100000000000100440001000000000001a4f682b77ecadd10f3f3a2f78d90566212366ff8209fd77314f5a49352f9bb9bd12f4daba0b4736ae29e46b9714d3b424b3e6d0234736619b5aa0d3f") require.NoError(t, err) - metadata, err := sniff.DTLSRecord(context.Background(), packet) + var metadata adapter.InboundContext + err = sniff.DTLSRecord(context.Background(), &metadata, packet) require.NoError(t, err) require.Equal(t, metadata.Protocol, C.ProtocolDTLS) } diff --git a/common/sniff/http.go b/common/sniff/http.go index faf05fd304..0e6ab406c6 100644 --- a/common/sniff/http.go +++ b/common/sniff/http.go @@ -11,10 +11,12 @@ import ( "github.com/sagernet/sing/protocol/http" ) -func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) { +func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { request, err := http.ReadRequest(std_bufio.NewReader(reader)) if err != nil { - return nil, err + return err } - return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: M.ParseSocksaddr(request.Host).AddrString()}, nil + metadata.Protocol = C.ProtocolHTTP + metadata.Domain = M.ParseSocksaddr(request.Host).AddrString() + return nil } diff --git a/common/sniff/http_test.go b/common/sniff/http_test.go index 98100e092a..9f64efa85e 100644 --- a/common/sniff/http_test.go +++ b/common/sniff/http_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/sniff" "github.com/stretchr/testify/require" @@ -13,7 +14,8 @@ import ( func TestSniffHTTP1(t *testing.T) { t.Parallel() pkt := "GET / HTTP/1.1\r\nHost: www.google.com\r\nAccept: */*\r\n\r\n" - metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt)) + var metadata adapter.InboundContext + err := sniff.HTTPHost(context.Background(), &metadata, strings.NewReader(pkt)) require.NoError(t, err) require.Equal(t, metadata.Domain, "www.google.com") } @@ -21,7 +23,8 @@ func TestSniffHTTP1(t *testing.T) { func TestSniffHTTP1WithPort(t *testing.T) { t.Parallel() pkt := "GET / HTTP/1.1\r\nHost: www.gov.cn:8080\r\nAccept: */*\r\n\r\n" - metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt)) + var metadata adapter.InboundContext + err := sniff.HTTPHost(context.Background(), &metadata, strings.NewReader(pkt)) require.NoError(t, err) require.Equal(t, metadata.Domain, "www.gov.cn") } diff --git a/common/sniff/quic.go b/common/sniff/quic.go index 55dc1c0023..adec7008b1 100644 --- a/common/sniff/quic.go +++ b/common/sniff/quic.go @@ -5,95 +5,99 @@ import ( "context" "crypto" "crypto/aes" + "crypto/tls" "encoding/binary" "io" "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/ja3" "github.com/sagernet/sing-box/common/sniff/internal/qtls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" "golang.org/x/crypto/hkdf" ) -func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { - reader := bytes.NewReader(packet) +var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection") +func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { + reader := bytes.NewReader(packet) typeByte, err := reader.ReadByte() if err != nil { - return nil, err + return err } if typeByte&0x40 == 0 { - return nil, E.New("bad type byte") + return E.New("bad type byte") } var versionNumber uint32 err = binary.Read(reader, binary.BigEndian, &versionNumber) if err != nil { - return nil, err + return err } if versionNumber != qtls.VersionDraft29 && versionNumber != qtls.Version1 && versionNumber != qtls.Version2 { - return nil, E.New("bad version") + return E.New("bad version") } packetType := (typeByte & 0x30) >> 4 if packetType == 0 && versionNumber == qtls.Version2 || packetType == 2 && versionNumber != qtls.Version2 || packetType > 2 { - return nil, E.New("bad packet type") + return E.New("bad packet type") } destConnIDLen, err := reader.ReadByte() if err != nil { - return nil, err + return err } if destConnIDLen == 0 || destConnIDLen > 20 { - return nil, E.New("bad destination connection id length") + return E.New("bad destination connection id length") } destConnID := make([]byte, destConnIDLen) _, err = io.ReadFull(reader, destConnID) if err != nil { - return nil, err + return err } srcConnIDLen, err := reader.ReadByte() if err != nil { - return nil, err + return err } _, err = io.CopyN(io.Discard, reader, int64(srcConnIDLen)) if err != nil { - return nil, err + return err } tokenLen, err := qtls.ReadUvarint(reader) if err != nil { - return nil, err + return err } _, err = io.CopyN(io.Discard, reader, int64(tokenLen)) if err != nil { - return nil, err + return err } packetLen, err := qtls.ReadUvarint(reader) if err != nil { - return nil, err + return err } hdrLen := int(reader.Size()) - reader.Len() if hdrLen+int(packetLen) > len(packet) { - return nil, os.ErrInvalid + return os.ErrInvalid } _, err = io.CopyN(io.Discard, reader, 4) if err != nil { - return nil, err + return err } pnBytes := make([]byte, aes.BlockSize) _, err = io.ReadFull(reader, pnBytes) if err != nil { - return nil, err + return err } var salt []byte @@ -117,7 +121,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex hpKey := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, hkdfHeaderProtectionLabel, 16) block, err := aes.NewCipher(hpKey) if err != nil { - return nil, err + return err } mask := make([]byte, aes.BlockSize) block.Encrypt(mask, pnBytes) @@ -129,7 +133,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex } packetNumberLength := newPacket[0]&0x3 + 1 if hdrLen+int(packetNumberLength) > int(packetLen)+hdrLen { - return nil, os.ErrInvalid + return os.ErrInvalid } var packetNumber uint32 switch packetNumberLength { @@ -142,7 +146,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex case 4: packetNumber = binary.BigEndian.Uint32(newPacket[hdrLen:]) default: - return nil, E.New("bad packet number length") + return E.New("bad packet number length") } extHdrLen := hdrLen + int(packetNumberLength) copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:]) @@ -166,138 +170,208 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber)) decrypted, err := cipher.Open(newPacket[extHdrLen:extHdrLen], nonce, data, newPacket[:extHdrLen]) if err != nil { - return nil, err + return err } var frameType byte - var frameLen uint64 - var fragments []struct { - offset uint64 - length uint64 - payload []byte - } + var fragments []qCryptoFragment decryptedReader := bytes.NewReader(decrypted) + const ( + frameTypePadding = 0x00 + frameTypePing = 0x01 + frameTypeAck = 0x02 + frameTypeAck2 = 0x03 + frameTypeCrypto = 0x06 + frameTypeConnectionClose = 0x1c + ) + var frameTypeList []uint8 for { frameType, err = decryptedReader.ReadByte() if err == io.EOF { break } + frameTypeList = append(frameTypeList, frameType) switch frameType { - case 0x00: // PADDING + case frameTypePadding: continue - case 0x01: // PING + case frameTypePing: continue - case 0x02, 0x03: // ACK + case frameTypeAck, frameTypeAck2: _, err = qtls.ReadUvarint(decryptedReader) // Largest Acknowledged if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // ACK Delay if err != nil { - return nil, err + return err } ackRangeCount, err := qtls.ReadUvarint(decryptedReader) // ACK Range Count if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // First ACK Range if err != nil { - return nil, err + return err } for i := 0; i < int(ackRangeCount); i++ { _, err = qtls.ReadUvarint(decryptedReader) // Gap if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // ACK Range Length if err != nil { - return nil, err + return err } } if frameType == 0x03 { _, err = qtls.ReadUvarint(decryptedReader) // ECT0 Count if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // ECT1 Count if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // ECN-CE Count if err != nil { - return nil, err + return err } } - case 0x06: // CRYPTO + case frameTypeCrypto: var offset uint64 offset, err = qtls.ReadUvarint(decryptedReader) if err != nil { - return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err + return err } var length uint64 length, err = qtls.ReadUvarint(decryptedReader) if err != nil { - return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err + return err } index := len(decrypted) - decryptedReader.Len() - fragments = append(fragments, struct { - offset uint64 - length uint64 - payload []byte - }{offset, length, decrypted[index : index+int(length)]}) - frameLen += length + fragments = append(fragments, qCryptoFragment{offset, length, decrypted[index : index+int(length)]}) _, err = decryptedReader.Seek(int64(length), io.SeekCurrent) if err != nil { - return nil, err + return err } - case 0x1c: // CONNECTION_CLOSE + case frameTypeConnectionClose: _, err = qtls.ReadUvarint(decryptedReader) // Error Code if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // Frame Type if err != nil { - return nil, err + return err } var length uint64 length, err = qtls.ReadUvarint(decryptedReader) // Reason Phrase Length if err != nil { - return nil, err + return err } _, err = decryptedReader.Seek(int64(length), io.SeekCurrent) // Reason Phrase if err != nil { - return nil, err + return err } default: - return nil, os.ErrInvalid + return os.ErrInvalid } } - tlsHdr := make([]byte, 5) - tlsHdr[0] = 0x16 - binary.BigEndian.PutUint16(tlsHdr[1:], uint16(0x0303)) - binary.BigEndian.PutUint16(tlsHdr[3:], uint16(frameLen)) + if metadata.SniffContext != nil { + fragments = append(fragments, metadata.SniffContext.([]qCryptoFragment)...) + metadata.SniffContext = nil + } + var frameLen uint64 + for _, fragment := range fragments { + frameLen += fragment.length + } + buffer := buf.NewSize(5 + int(frameLen)) + defer buffer.Release() + buffer.WriteByte(0x16) + binary.Write(buffer, binary.BigEndian, uint16(0x0303)) + binary.Write(buffer, binary.BigEndian, uint16(frameLen)) var index uint64 var length int - var readers []io.Reader - readers = append(readers, bytes.NewReader(tlsHdr)) find: for { for _, fragment := range fragments { if fragment.offset == index { - readers = append(readers, bytes.NewReader(fragment.payload)) + buffer.Write(fragment.payload) index = fragment.offset + fragment.length length++ continue find } } - if length == len(fragments) { + break + } + metadata.Protocol = C.ProtocolQUIC + fingerprint, err := ja3.Compute(buffer.Bytes()) + if err != nil { + metadata.Protocol = C.ProtocolQUIC + metadata.Client = C.ClientChromium + metadata.SniffContext = fragments + return ErrClientHelloFragmented + } + metadata.Domain = fingerprint.ServerName + for metadata.Client == "" { + if len(frameTypeList) == 1 { + metadata.Client = C.ClientFirefox + break + } + if frameTypeList[0] == frameTypeCrypto && isZero(frameTypeList[1:]) { + if len(fingerprint.Versions) == 2 && fingerprint.Versions[0]&ja3.GreaseBitmask == 0x0A0A && + len(fingerprint.EllipticCurves) == 5 && fingerprint.EllipticCurves[0]&ja3.GreaseBitmask == 0x0A0A { + metadata.Client = C.ClientSafari + break + } + if len(fingerprint.CipherSuites) == 1 && fingerprint.CipherSuites[0] == tls.TLS_AES_256_GCM_SHA384 && + len(fingerprint.EllipticCurves) == 1 && fingerprint.EllipticCurves[0] == uint16(tls.X25519) && + len(fingerprint.SignatureAlgorithms) == 1 && fingerprint.SignatureAlgorithms[0] == uint16(tls.ECDSAWithP256AndSHA256) { + metadata.Client = C.ClientSafari + break + } + } + + if frameTypeList[len(frameTypeList)-1] == frameTypeCrypto && isZero(frameTypeList[:len(frameTypeList)-1]) { + metadata.Client = C.ClientQUICGo break } - return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, E.New("bad fragments") + + if count(frameTypeList, frameTypeCrypto) > 1 || count(frameTypeList, frameTypePing) > 0 { + if maybeUQUIC(fingerprint) { + metadata.Client = C.ClientQUICGo + } else { + metadata.Client = C.ClientChromium + } + break + } + + metadata.Client = C.ClientUnknown + //nolint:staticcheck + break } - metadata, err := TLSClientHello(ctx, io.MultiReader(readers...)) - if err != nil { - return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err + return nil +} + +func isZero(slices []uint8) bool { + for _, slice := range slices { + if slice != 0 { + return false + } } - metadata.Protocol = C.ProtocolQUIC - return metadata, nil + return true +} + +func count(slices []uint8, value uint8) int { + var times int + for _, slice := range slices { + if slice == value { + times++ + } + } + return times +} + +type qCryptoFragment struct { + offset uint64 + length uint64 + payload []byte } diff --git a/common/sniff/quic_blacklist.go b/common/sniff/quic_blacklist.go new file mode 100644 index 0000000000..7d5f91e79b --- /dev/null +++ b/common/sniff/quic_blacklist.go @@ -0,0 +1,24 @@ +package sniff + +import ( + "crypto/tls" + + "github.com/sagernet/sing-box/common/ja3" +) + +// Chromium sends separate client hello packets, but UQUIC has not yet implemented this behavior +// The cronet without this behavior does not have version 115 +var uQUICChrome115 = &ja3.ClientHello{ + Version: tls.VersionTLS12, + CipherSuites: []uint16{4865, 4866, 4867}, + Extensions: []uint16{0, 10, 13, 16, 27, 43, 45, 51, 57, 17513}, + EllipticCurves: []uint16{29, 23, 24}, + SignatureAlgorithms: []uint16{1027, 2052, 1025, 1283, 2053, 1281, 2054, 1537, 513}, +} + +func maybeUQUIC(fingerprint *ja3.ClientHello) bool { + if uQUICChrome115.Equals(fingerprint, true) { + return true + } + return false +} diff --git a/common/sniff/quic_test.go b/common/sniff/quic_test.go index 807197654b..e75e86a5d2 100644 --- a/common/sniff/quic_test.go +++ b/common/sniff/quic_test.go @@ -5,31 +5,69 @@ import ( "encoding/hex" "testing" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" "github.com/stretchr/testify/require" ) -func TestSniffQUICv1(t *testing.T) { +func TestSniffQUICChromium(t *testing.T) { t.Parallel() - pkt, err := hex.DecodeString("cc0000000108d2dc7bad02241f5003796e71004215a71bfcb05159416c724be418537389acdd9a4047306283dcb4d7a9cad5cc06322042d204da67a8dbaa328ab476bb428b48fd001501863afd203f8d4ef085629d664f1a734a65969a47e4a63d4e01a21f18c1d90db0c027180906dc135f9ae421bb8617314c8d54c175fef3d3383d310d0916ebcbd6eed9329befbbb109d8fd4af1d2cf9d6adce8e6c1260a7f8256e273e326da0aa7cc148d76e7a08489dc9d52ade89c027cbc3491ada46417c2c04e2ca768e9a7dd6aa00c594e48b678927325da796817693499bb727050cb3baf3d3291a397c3a8d868e8ec7b8f7295e347455c9dadbe2252ae917ac793d958c7fb8a3d2cdb34e3891eb4286f18617556ff7216dd60256aa5b1d11ff4753459fc5f9dedf11d483a26a0835dc6cd50e1c1f54f86e8f1e502821183cd874f6447a74e818bf3445c7795acf4559d1c1fac474911d2ead5c8d23e4aa4f67afb66efe305a30a0b5d825679b31ddc186cbea936535795c7e8c378c87b8c5adc065154d15bae8f85ac8fec2da40c3aa623b682a065440831555011d7647cde44446a0fb4cf5892f2c088ae1920643094be72e3c499fe8d265caf939e8ab607a5b9317917d2a32a812e8a0e6a2f84721bbb5984ffd242838f705d13f4cfb249bc6a5c80d58ac2595edf56648ec3fe21d787573c253a79805252d6d81e26d367d4ff29ef66b5fe8992086af7bada8cad10b82a7c0dc406c5b6d0c5ec3c583e767f759ce08cad6c3c8f91e5a8") + pkt, err := hex.DecodeString("c30000000108f40d654cc09b27f5000044d08a94548e57e43cc5483f129986187c432d58d46674830442988f869566a6e31e2ae37c9f7acbf61cc81621594fab0b3dfdc1635460b32389563dc8e74006315661cd22694114612973c1c45910621713a48b375854f095e8a77ccf3afa64e972f0f7f7002f50e0b014b1b146ea47c07fb20b73ad5587872b51a0b3fafdf1c4cf4fe6f8b112142392efa25d993abe2f42582be145148bdfe12edcd96c3655b65a4781b093e5594ba8e3ae5320f12e8314fc3ca374128cc43381046c322b964681ed4395c813b28534505118201459665a44b8f0abead877de322e9040631d20b05f15b81fa7ff785d4041aecc37c7e2ccdc5d1532787ce566517e8985fd5c200dbfd1e67bc255efaba94cfc07bb52fea4a90887413b134f2715b5643542aa897c6116486f428d82da64d2a2c1e1bdd40bd592558901a554b003d6966ac5a7b8b9413eddbf6ef21f28386c74981e3ce1d724c341e95494907626659692720c81114ca4acea35a14c402cfa3dc2228446e78dc1b81fa4325cf7e314a9cad6a6bdff33b3351dcba74eb15fae67f1227283aa4cdd64bcadf8f19358333f8549b596f4350297b5c65274565869d497398339947b9d3d064e5b06d39d34b436d8a41c1a3880de10bd26c3b1c5b4e2a49b0d4d07b8d90cd9e92bc611564d19ea8ec33099e92033caf21f5307dbeaa4708b99eb313bff99e2081ac25fd12d6a72e8335e0724f6718fe023cd0ad0d6e6a6309f09c9c391eec2bc08e9c3210a043c08e1759f354c121f6517fff4d6e20711a871e41285d48d930352fddffb92c96ba57df045ce99f8bfdfa8edc0969ce68a51e9fbb4f54b956d9df74a9e4af27ed2b27839bce1cffeca8333c0aaee81a570217442f9029ba8fedb84a2cf4be4d910982d891ea00e816c7fb98e8020e896a9c6fdd9106611da0a99dde18df1b7a8f6327acb1eed9ad93314451e48cb0dfb9571728521ca3db2ac0968159d5622556a55d51a422d11995b650949aaefc5d24c16080446dfc4fbc10353f9f93ce161ab513367bb89ab83988e0630b689e174e27bcfcc31996ee7b0bca909e251b82d69a28fee5a5d662e127508cd19dbbe5097b7d5b62a49203d66764197a527e472e2627e44a93d44177dace9d60e7d0e03305ddf4cfe47cdf2362e14de79ef46a6763ce696cd7854a48d9419a0817507a4713ffd4977b906d4f2b5fb6dbe1bd15bc505d5fea582190bf531a45d5ee026da8918547fd5105f15e5d061c7b0cf80a34990366ed8e91e13c2f0d85e5dad537298808d193cf54b7eaac33f10051f74cb6b75e52f81618c36f03d86aef613ba237a1a793ba1539938a38f62ccaf7bd5f6c5e0ce53cde4012fcf2b758214a0422d2faaa798e86e19d7481b42df2b36a73d287ff28c20cce01ce598771fec16a8f1f00305c06010126013a6c1de9f589b4e79d693717cd88ad1c42a2d99fa96617ba0bc6365b68e21a70ebc447904aa27979e1514433cfd83bfec09f137c747d47582cb63eb28f873fb94cf7a59ff764ddfbb687d79a58bb10f85949269f7f72c611a5e0fbb52adfa298ff060ec2eb7216fd7302ea8fb07798cbb3be25cb53ac8161aac2b5bbcfbcfb01c113d28bd1cb0333fb89ac82a95930f7abded0a2f5a623cc6a1f62bf3f38ef1b81c1e50a634f657dbb6770e4af45879e2fb1e00c742e7b52205c8015b5c0f5b1e40186ff9aa7288ab3e01a51fb87761f9bc6837082af109b39cc9f620") require.NoError(t, err) - metadata, err := sniff.QUICClientHello(context.Background(), pkt) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Equal(t, metadata.Client, C.ClientChromium) + require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) + pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28") require.NoError(t, err) - require.Equal(t, metadata.Domain, "cloudflare-quic.com") + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, metadata.Domain, "google.com") +} + +func TestSniffUQUICChrome115(t *testing.T) { + t.Parallel() + pkt, err := hex.DecodeString("cb0000000108181e17c387120abc000044d0705b6a3ef9ee37a8d3949a7d393ed078243c2ee2c3627fad1c3f107c117f4f071131ad61848068fcbbe5c65803c147f7f8ec5e2cd77b77beea23ba779d936dccac540f8396400e3190ea35cc2942af4171a04cb14272491920f90124959f44e80143678c0b52f5d31af319aaa589db2f940f004562724d0af40f737e1bb0002a071e6a1dbc9f52c64f070806a5010abed0298053634d9c9126bd7949ae5087998ade762c0ad06691d99c0875a38c601fc1ee77bfc3b8c11381829f2c9bdd022f4499c43ff1d6aee1a0d296861461dda217d22c568b276016ef3929e59d2f7d7ddf7809920fb7dc805641608949f3f8466ab3d37149aac501f0b107d808f3add4acfc657e4a82e2b88e97a6c74a00c419548760ab3414ba13915c78a1ca79dceee8d59fbe299f20b671ac44823218368b2a026baa55170cf549519ac21dbb6d31d248bd339438a4e663bcdca1fe3ae3f045a5dc19b122e9db9d7af9757076666dda4e9ace1c67def77fa14786f0cab3ebf7a270ea6e2b37838318c95779f80c3b8471948d0046c3614b3a13477c939a39a7855d85d13522a45ae0765739cd5eedef87237e824a929983ace27640c6495dbf5a72fa0b96893dc5d28f3988249a57bdb458d460b4a57043de3da750a76b6e5d2259247ca27cd864ea18f0d09aa62ab6eb7c014fb43179b2a1963d170b756cce83eeaebff78a828d025c811848e16ff862a8080d093478cd2208c8ab0803178325bc0d9d6bb25e62fa50c4ad15cf80916da6578796932036c72e43eb480d1e423ed812ac75a97722f8416529b82ba8ee2219c535012282bb17066bd53e78b87a71abdb7ebdb2a7c2766ff8397962e87d0f85485b64b4ee81cc84f99c47f33f2b0872716441992773f59186e38d32dbf5609a6fda94cb928cd25f5a7a3ab736b5a4236b6d5409ab18892c6a4d3480fc2350abfdf0bab1cedb55bdf0760fdb703e6688f4de596254eed4ed3e67eb03d0717b8e15b31e735214e588c87ae36bc6c310e1894b4c15143e4ccf287b2dbc707a946bf9671ae3c574f9486b2c82eec784bba4cbc76113cbe0f97ac8c13cfa38f2925ab9d06887a612ce48280a91d7e074e6caf898d88e2bbf71360899abf48a03f9a70cf2891199f2d63b116f4871af0ebb4f4906792f66cc21d1609f189138532875c129a68c73e7bcd3b5d8100beac1d8ac4b20d94a59ac8df5a5af58a9acb20413eadf97189f5f19ff889155f0c4d37514ec184eb6903967ff38a41fc087abb0f2cad3761d6e3f95f92a09a72f5c065b16e188088b87460241f27ecdb1bc6ece92c8d36b2d68b58d0fb4d4b3c928c579ade8ae5a995833aadd297c30a37f7bc35440fc97070e1b198e0fac00157452177d16d2803b4239997452b4ad3a951173bdec47a033fd7f8a7942accaa9aaa905b3c5a2175e7c3e07c48bf25331727fd69cd1e64d74d8c9d4a6f8f4491adb7bc911505cb19877083d8f21a12475e313fccf57877ff3556318e81ed9145dd9427f2b65275440893035f417481f721c69215af8ae103530cd0a1d35bf2cb5a27628f8d44d7c6f5ec12ce79d0a8333e0eb48771115d0a191304e46b8db19bbe5c40f1c346dde98e76ff5e21ff38d2c34e60cb07766ed529dd6d2cbacd7fbf1ed8a0e6e40decad0ca5021e91552be87c156d3ae2fffef41c65b14ba6d488f2c3227a1ab11ffce0e2dc47723a69da27a67a7f26e1cb13a7103af9b87a8db8e18ea") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Equal(t, metadata.Client, C.ClientQUICGo) + require.Equal(t, metadata.Domain, "www.google.com") +} + +func TestSniffQUICFirefox(t *testing.T) { + t.Parallel() + pkt, err := hex.DecodeString("c8000000010867f174d7ebfe1b0803cd9c20004286de068f7963cf1736349ee6ebe0ddcd3e4cd0041a51ced3f7ce9eea1fb595458e74bdb4b792b16449bd8cae71419862c4fcbe766eaec7d1af65cd298e1dd46f8bd94a77ab4ca28c54b8e9773de3f02d7cb2463c9f7dcacfb311f024b0266ec6ab7bfb615b4148333fb4d4ece7c4cd90029ca30c2cbae2216b428499ec873fa125797e71c5a5da85087760ad37ca610020f71b76e82651c47576e20bf33cf676cb2d400b8c09d3c8cb4e21c47d2b21f6b68732bef30c8cefd5c723fc23eb29e6f7f65a5e52aad9055c1fb3d8b1811f0380b38d7e2eee8eb37dd5bd5d4ca4b66540175d916289d88a9df7c161964d713999c5057d27edb298ef5164352568b0d4bac3c15d90456e8fd460e41b81d0ec1b1e94b87d3333cc6908b018e0914ae1f214d73e75398da3d55a0106161d3a75897b4eb66e98c59010fae75f0d367d38be48c3a5c58bc8a30773c3fff50690ac9d487822f85d4f5713d626baa92d36e858dd21259cf814bce0b90d18da88a1ade40113e5a088cdb304a2558879152a8cf15c1839e056378aa41acba6fcb9974dee54bd50b5d4eb2c475654e06c0ec06b7f18f4462c808684843a1071041b9bfb2688324e0120144944416e30e83eedbbbcbc275b1f53762d3db18f0998ce54f0e1c512946b4098f07781d49264fa148f4c8220a3b02e73d7f15554aa370aafeff73cb75c52c494edf90f0261abfdd32a4d670f729de50266162687aa8efe14b8506f313b058b02aaaab5825428f5f4510b8e49451fdcb7b5a4af4b59c831afcb89fb4f64dba78e3b38387e87e9e8cdaa1f3b700a87c7d442388863b8950296e5773b38f308d62f52548c0bbf308e40540747cca5bf99b1345bc0d70b8f0e69a83b85a8d69f795b87f93e2bfccf52b529afea4ff6fd456957000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Equal(t, metadata.Client, C.ClientFirefox) + require.Equal(t, metadata.Domain, "www.google.com") } -func TestSniffQUICFragment(t *testing.T) { +func TestSniffQUICSafari(t *testing.T) { t.Parallel() - pkt, err := hex.DecodeString("cc00000001082e3d5d1b64040c55000044d0ccea69e773f6631c1d18b04ae9ee75fcfc34ef74fa62533c93534338a86f101a05d70e0697fb483063fa85db1c59ccfbda5c35234931d8524d8aac37eaaad649470a67794cd754b23c98695238b8363452333bc8c4858376b4166e001da2006e35cf98a91e11a56419b2786775284942d0f7163982f7c248867d12dd374957481dbc564013ff785e1916195eef671f725908f761099d992d69231336ba81d9e25fe2fa3a6eff4318a6ccf10176fc841a1b315f7b35c5b292266fc869d76ca533e7d14e86d82db2e22eacd350977e47d2e012d8a5891c5aaf2a0f4c2b2dae897c161e5b68cbb4dee952472bdc1e21504b8f02534ec4366ce3f8bf86efc78e0232778fbd554457567112abdcafcf6d4d8fcf35083c25d9495679614aba21696e338c62b585046cc55ba8c09c844361d889a47c3ea703b4e23545a9ab2c0bb369693a9ddfb5daffa85cf80fdd6ad66738664e5b0a551729b4955cff7255afcb04dee88c2f072c9de7400947a1bd9327ac5d012a33000ada021d4c03d249fb017d6ac9200b2f9436beab8183ddfbe2d8aee31ffb7df9e1cc181c1af80c39a89965d18ed12da8e3ebe2ae1fbe4b348f83ba19e3e3d1c9b22bcf03ab6ad9b30fe180623faa291ebad83bcd71d7b57f2f5e2f3b8e81d24fb70b2f2159239e8f21ffafef2747aba47d97ab4081e603c018b10678cf99cab1fb42156a14486fa435153979d7279fd22cd40af7088bfc7eff41af2f4b3c0c8864d0040d74dff427f7bffdb8c278474ea00311326cf4925471a8cf596cb92119f19e0f789490ba9cb77b98015a987d93e0324cf1a38b55109f00c3e6ddc5180fb107bf468323afec9bb49fd6a86418569789d66cafe3b8253c2aebb3af3782c1c54dd560487d031d28e6a6e23e159581bb1d47efc4da3fe1d169f9ffb0ca9ba61af0a38a92fde5bc5e6ec026e8378a6315a7b95abf1d2da790a391306ce74d0baf8e2ce648ca74c487f2c0a76a28a80cdf5bd34316eb607684fe7e6d9e83824a00e07660d0b90e3cddd61ebf10748263474afa88c300549e64ce2e90560bb1a12dee7e9484f729a8a4ee7c5651adb5194b3b3ae38e501567c7dbf36e7bb37a2c20b74655f47f2d9af18e52e9d4c9c9eee8e63745779b8f0b06f3a09d846ba62eb978ad77c85de1ee2fee3fbb4c2d283c73e1ccba56a4658e48a2665d200f7f9342f8e84c2ba490094a4f94feec89e42d2f654f564c2beb2997bafa1fc2c68ad8e160b63587d49abc31b834878d52acfb05fb73d0e059b206162e3c90b40c4bc08407ffcb3c08431895b691a3fea923f1f3b48db75d3e6b91fd319ffe4d486e0e14bd5c6affc838dee63d9e0b80f169b5e6c02c7321dcb20deb2b8e707b60e345a308d505bbf26a93d8f18b39d62632e9a77cbe48b3b32eb8819d6311a49820d40f5acbf0273c91c36b2269a03e72ee64df3dfb10ddefe73c64ef60870b2b77bd99dea655f5fe791b538a929a14d99f6d69685d72431ea5f0f4b27a044f2f575ab474fcc3857895934de1ca2581798eaef2c17fe5aaf2e6add97fa32997c7026f15c1b1ad0e6043ae506027a7c0242546fdc851cca39a204e56879f2cef838be8ec66e0f2292f8c862e06f810eb9b80c7a467ce6e90155206352c7f82b1173ba3b98d35bb72c259a60db20dd1a43fe6d7aef0265e6eaa5caafd9b64b448ff745a2046acbdb65cf2a5007809808a4828dc99097feedc734c236260c584") + pkt, err := hex.DecodeString("c70000000108e4e75af2e223198a0000449ef2d83cb4473a62765eba67424cd4a5817315cbf55a9e8daaca360904b0bae60b1629cfeba11e2dfbbf5ea4c588cb134e31af36fd7a409fb0fcc0187e9b56037ac37964ed20a8c1ca19fd6cfd53398324b3d0c71537294f769db208fa998b6811234a4a7eb3b5eceb457ae92e3a2d98f7c110702db8064b5c29fa3298eb1d0529fd445a84a5fd6ff8709be90f8af4f94998d8a8f2953bb05ad08c80668eca784c6aec959114e68e5b827e7c41c79f2277c716a967e7fcc8d1b77442e6cb18329dbedb34b473516b468cba5fc20659e655fbe37f36408289b9a475fcee091bd82828d3be00367e9e5cec9423bb97854abdada1d7562a3777756eb3bddef826ddc1ef46137cb01bb504a54d410d9bcb74cd5f959050c84edf343fa6a49708c228a758ee7adbbadf260b2f1984911489712e2cb364a3d6520badba4b7e539b9c163eeddfd96c0abb0de151e47496bb9750be76ee17ccdb61d35d2c6795174037d6f9d282c3f36c4d9a90b64f3b6ddd0cf4d9ed8e6f7805e25928fa04b087e63ae02761df30720cc01dfc32b64c575c8a66ef82e9a17400ff80cd8609b93ba16d668f4aa734e71c4a5d145f14ee1151bec970214e0ff83fc3e1e85d8694f2975f9155c57c18b7b69bb6a36832a9435f1f4b346a7be188f3a75f9ad2cc6ad0a3d26d6fa7d4c1179bd49bd5989d15ba43ff602890107db96484695086627356750d7b2b3b714ba65d564654e8f60ac10f5b6d3bfb507e8eaa31bab1da2d676195046d165c7f8b32829c9f9b68d97b2af7ac04a1369357e4b65de2b2f24eaf27cc8d95e05db001adebe726f927a94e43e62ce671e6e306e16f05aafcbe6c49080e80286d7939f375023d110a5ad9069364ae928ca480454a9dcddd61bc48b7efeb716a5bd6c7cd39c486ceb20c738af6abf22ba1ddd8b4a3b781fc2f251173409e1aadccbd7514e97106d0ebfc3af6e59445f74cd733a1ba99b10fce3fb4e9f7c88f5e25b567f5ba2b8dabacd375e7faf7634bfa178cbe51aee63032c5126b196ea47b02385fc3062a000fb7e4b4d0d12e74579f8830ede20d10829496032b2cc56743287f9a9b4d5091877a82fea44deb2cffac8a379f78a151d99e28cbc74d732c083bf06d50584e3f18f254e71a48d6ababaf6fff6f425e9be001510dfbe6a32a27792c00ada036b62ddb90c706d7b882c76a7072f5dd11c69a1f49d4ba183cb0b57545419fa27b9b9706098848935ae9c9e8fbe9fac165d1339128b991a73d20e7795e8d6a8c6adfbf20bf13ada43f2aef3ba78c14697910507132623f721387dce60c4707225b84d9782d469a5d9eaa099f35d6a590ef142ddef766495cf3337815ceef5ff2b3ed352637e72b5c23a2a8ff7d7440236a19b981d47f8e519a0431ebfbc0b78d8a36798b4c060c0c6793499f1e2e818862560a5b501c8d02ba1517be1941da2af5b174e0189c62978d878eb0f9c9db3a9221c28fb94645cf6e85ff2eea8c65ba3083a7382b131b83102dd67aa5453ad7375a4eb8c69fc479fbd29dab8924f801d253f2c997120b705c6e5217fb74702e2f1038917dd5fb0eeb7ae1bf7a668fc7d50c034b4cd5a057a8482e6bc9c921297f44e76967265623a167cd9883eb6e64bc77856dc333bd605d7df3bed0e5cecb5a99fe8b62873d58530f") require.NoError(t, err) - metadata, err := sniff.QUICClientHello(context.Background(), pkt) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) require.NoError(t, err) - require.Equal(t, metadata.Domain, "cloudflare-quic.com") + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Equal(t, metadata.Client, C.ClientSafari) + require.Equal(t, metadata.Domain, "www.google.com") } func FuzzSniffQUIC(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { - sniff.QUICClientHello(context.Background(), data) + var metadata adapter.InboundContext + err := sniff.QUICClientHello(context.Background(), &metadata, data) + require.Error(t, err) }) } diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 2d2999a83e..80bb2984f9 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -14,8 +14,8 @@ import ( ) type ( - StreamSniffer = func(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) - PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error) + StreamSniffer = func(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error + PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error ) func Skip(metadata adapter.InboundContext) bool { @@ -34,7 +34,7 @@ func Skip(metadata adapter.InboundContext) bool { return false } -func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) (*adapter.InboundContext, error) { +func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error { if timeout == 0 { timeout = C.ReadPayloadTimeout } @@ -43,7 +43,7 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout for i := 0; ; i++ { err := conn.SetReadDeadline(deadline) if err != nil { - return nil, E.Cause(err, "set read deadline") + return E.Cause(err, "set read deadline") } _, err = buffer.ReadOnceFrom(conn) _ = conn.SetReadDeadline(time.Time{}) @@ -51,29 +51,28 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout if i > 0 { break } - return nil, E.Cause(err, "read payload") + return E.Cause(err, "read payload") } errors = nil - var metadata *adapter.InboundContext for _, sniffer := range sniffers { - metadata, err = sniffer(ctx, bytes.NewReader(buffer.Bytes())) - if metadata != nil { - return metadata, nil + err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes())) + if err == nil { + return nil } errors = append(errors, err) } } - return nil, E.Errors(errors...) + return E.Errors(errors...) } -func PeekPacket(ctx context.Context, packet []byte, sniffers ...PacketSniffer) (*adapter.InboundContext, error) { +func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error { var errors []error for _, sniffer := range sniffers { - metadata, err := sniffer(ctx, packet) - if metadata != nil { - return metadata, nil + err := sniffer(ctx, metadata, packet) + if err == nil { + return nil } errors = append(errors, err) } - return nil, E.Errors(errors...) + return E.Errors(errors...) } diff --git a/common/sniff/stun.go b/common/sniff/stun.go index 66a72d7e17..dfd1125978 100644 --- a/common/sniff/stun.go +++ b/common/sniff/stun.go @@ -9,16 +9,17 @@ import ( C "github.com/sagernet/sing-box/constant" ) -func STUNMessage(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { +func STUNMessage(_ context.Context, metadata *adapter.InboundContext, packet []byte) error { pLen := len(packet) if pLen < 20 { - return nil, os.ErrInvalid + return os.ErrInvalid } if binary.BigEndian.Uint32(packet[4:8]) != 0x2112A442 { - return nil, os.ErrInvalid + return os.ErrInvalid } if len(packet) < 20+int(binary.BigEndian.Uint16(packet[2:4])) { - return nil, os.ErrInvalid + return os.ErrInvalid } - return &adapter.InboundContext{Protocol: C.ProtocolSTUN}, nil + metadata.Protocol = C.ProtocolSTUN + return nil } diff --git a/common/sniff/stun_test.go b/common/sniff/stun_test.go index 6da89162a9..f465763e1f 100644 --- a/common/sniff/stun_test.go +++ b/common/sniff/stun_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "testing" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" @@ -15,14 +16,16 @@ func TestSniffSTUN(t *testing.T) { t.Parallel() packet, err := hex.DecodeString("000100002112a44224b1a025d0c180c484341306") require.NoError(t, err) - metadata, err := sniff.STUNMessage(context.Background(), packet) + var metadata adapter.InboundContext + err = sniff.STUNMessage(context.Background(), &metadata, packet) require.NoError(t, err) require.Equal(t, metadata.Protocol, C.ProtocolSTUN) } func FuzzSniffSTUN(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { - if _, err := sniff.STUNMessage(context.Background(), data); err == nil { + var metadata adapter.InboundContext + if err := sniff.STUNMessage(context.Background(), &metadata, data); err == nil { t.Fail() } }) diff --git a/common/sniff/tls.go b/common/sniff/tls.go index dcba195afe..6fe430e2d8 100644 --- a/common/sniff/tls.go +++ b/common/sniff/tls.go @@ -10,7 +10,7 @@ import ( "github.com/sagernet/sing/common/bufio" ) -func TLSClientHello(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) { +func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error { var clientHello *tls.ClientHelloInfo err := tls.Server(bufio.NewReadOnlyConn(reader), &tls.Config{ GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) { @@ -19,7 +19,9 @@ func TLSClientHello(ctx context.Context, reader io.Reader) (*adapter.InboundCont }, }).HandshakeContext(ctx) if clientHello != nil { - return &adapter.InboundContext{Protocol: C.ProtocolTLS, Domain: clientHello.ServerName}, nil + metadata.Protocol = C.ProtocolTLS + metadata.Domain = clientHello.ServerName + return nil } - return nil, err + return err } diff --git a/constant/protocol.go b/constant/protocol.go index 915e64ca19..8b1854d419 100644 --- a/constant/protocol.go +++ b/constant/protocol.go @@ -9,3 +9,11 @@ const ( ProtocolBitTorrent = "bittorrent" ProtocolDTLS = "dtls" ) + +const ( + ClientChromium = "chromium" + ClientSafari = "safari" + ClientFirefox = "firefox" + ClientQUICGo = "quic-go" + ClientUnknown = "unknown" +) diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 23d2bf1b0c..8e61e51df2 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -4,6 +4,7 @@ icon: material/alert-decagram !!! quote "Changes in sing-box 1.10.0" + :material-plus: [client](#client) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) @@ -40,6 +41,12 @@ icon: material/alert-decagram "http", "quic" ], + "client": [ + "chromium", + "safari", + "firefox", + "quic-go" + ], "domain": [ "test.com" ], @@ -166,7 +173,13 @@ Username, see each inbound for details. #### protocol -Sniffed protocol, see [Sniff](/configuration/route/sniff/) for details. +Sniffed protocol, see [Protocol Sniff](/configuration/route/sniff/) for details. + +#### client + +!!! question "Since sing-box 1.10.0" + +Sniffed client type, see [Protocol Sniff](/configuration/route/sniff/) for details. #### network diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 52d334f22c..68e66cf5c9 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -4,8 +4,9 @@ icon: material/alert-decagram !!! quote "sing-box 1.10.0 中的更改" + :material-plus: [client](#client) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) - :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) !!! quote "sing-box 1.8.0 中的更改" @@ -40,6 +41,12 @@ icon: material/alert-decagram "http", "quic" ], + "client": [ + "chromium", + "safari", + "firefox", + "quic-go" + ], "domain": [ "test.com" ], @@ -166,6 +173,12 @@ icon: material/alert-decagram 探测到的协议, 参阅 [协议探测](/zh/configuration/route/sniff/)。 +#### client + +!!! question "自 sing-box 1.10.0 起" + +探测到的客户端类型, 参阅 [协议探测](/zh/configuration/route/sniff/)。 + #### network `tcp` 或 `udp`。 diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index cc9a5c130d..ab96e75a6f 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -4,19 +4,28 @@ icon: material/new-box !!! quote "Changes in sing-box 1.10.0" - :material-plus: BitTorrent support + :material-plus: QUIC client type detect support for QUIC + :material-plus: Chromium support for QUIC + :material-plus: BitTorrent support :material-plus: DTLS support If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed. #### Supported Protocols -| Network | Protocol | Domain Name | -|:-------:|:------------:|:-----------:| -| TCP | `http` | Host | -| TCP | `tls` | Server Name | -| UDP | `quic` | Server Name | -| UDP | `stun` | / | -| TCP/UDP | `dns` | / | -| TCP/UDP | `bittorrent` | / | -| UDP | `dtls` | / | +| Network | Protocol | Domain Name | Client | +|:-------:|:------------:|:-----------:|:----------------:| +| TCP | `http` | Host | / | +| TCP | `tls` | Server Name | / | +| UDP | `quic` | Server Name | QUIC Client Type | +| UDP | `stun` | / | / | +| TCP/UDP | `dns` | / | / | +| TCP/UDP | `bittorrent` | / | / | +| UDP | `dtls` | / | / | + +| QUIC Client | Type | +|:------------------------:|:----------:| +| Chromium/Cronet | `chrimium` | +| Safari/Apple Network API | `safari` | +| Firefox / uquic firefox | `firefox` | +| quic-go / uquic chrome | `quic-go` | \ No newline at end of file diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index 523ed44b90..1ebc6d38d1 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -4,19 +4,28 @@ icon: material/new-box !!! quote "sing-box 1.10.0 中的更改" - :material-plus: BitTorrent 支持 + :material-plus: QUIC 的 客户端类型探测支持 + :material-plus: QUIC 的 Chromium 支持 + :material-plus: BitTorrent 支持 :material-plus: DTLS 支持 如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。 #### 支持的协议 -| 网络 | 协议 | 域名 | -|:-------:|:------------:|:-----------:| -| TCP | `http` | Host | -| TCP | `tls` | Server Name | -| UDP | `quic` | Server Name | -| UDP | `stun` | / | -| TCP/UDP | `dns` | / | -| TCP/UDP | `bittorrent` | / | -| UDP | `dtls` | / | +| 网络 | 协议 | 域名 | 客户端 | +|:-------:|:------------:|:-----------:|:----------:| +| TCP | `http` | Host | / | +| TCP | `tls` | Server Name | / | +| UDP | `quic` | Server Name | QUIC 客户端类型 | +| UDP | `stun` | / | / | +| TCP/UDP | `dns` | / | / | +| TCP/UDP | `bittorrent` | / | / | +| UDP | `dtls` | / | / | + +| QUIC 客户端 | 类型 | +|:------------------------:|:----------:| +| Chromium/Cronet | `chrimium` | +| Safari/Apple Network API | `safari` | +| Firefox / uquic firefox | `firefox` | +| quic-go / uquic chrome | `quic-go` | \ No newline at end of file diff --git a/go.mod b/go.mod index 7666dd24e5..f9b11d6736 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,7 @@ require ( go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.25.0 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/net v0.27.0 golang.org/x/sys v0.25.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 @@ -89,7 +90,6 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.18.0 // indirect diff --git a/option/rule.go b/option/rule.go index 74dd13c615..5c69ef00e3 100644 --- a/option/rule.go +++ b/option/rule.go @@ -70,6 +70,7 @@ type _DefaultRule struct { Network Listable[string] `json:"network,omitempty"` AuthUser Listable[string] `json:"auth_user,omitempty"` Protocol Listable[string] `json:"protocol,omitempty"` + Client Listable[string] `json:"client,omitempty"` Domain Listable[string] `json:"domain,omitempty"` DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` diff --git a/route/router.go b/route/router.go index 3e0e90db88..35e1e21f28 100644 --- a/route/router.go +++ b/route/router.go @@ -854,8 +854,9 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad if metadata.InboundOptions.SniffEnabled && !sniff.Skip(metadata) { buffer := buf.NewPacket() - sniffMetadata, err := sniff.PeekStream( + err := sniff.PeekStream( ctx, + &metadata, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), @@ -864,9 +865,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad sniff.HTTPHost, sniff.BitTorrent, ) - if sniffMetadata != nil { - metadata.Protocol = sniffMetadata.Protocol - metadata.Domain = sniffMetadata.Domain + if err == nil { if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { metadata.Destination = M.Socksaddr{ Fqdn: metadata.Domain, @@ -878,8 +877,6 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad } else { r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) } - } else if err != nil { - r.logger.TraceContext(ctx, "sniffed no protocol: ", err) } if !buffer.IsEmpty() { conn = bufio.NewCachedConn(conn, buffer) @@ -980,65 +977,87 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m }*/ if metadata.InboundOptions.SniffEnabled || metadata.Destination.Addr.IsUnspecified() { - var ( - buffer = buf.NewPacket() - destination M.Socksaddr - done = make(chan struct{}) - err error - ) - go func() { - sniffTimeout := C.ReadPayloadTimeout - if metadata.InboundOptions.SniffTimeout > 0 { - sniffTimeout = time.Duration(metadata.InboundOptions.SniffTimeout) - } - conn.SetReadDeadline(time.Now().Add(sniffTimeout)) - destination, err = conn.ReadPacket(buffer) - conn.SetReadDeadline(time.Time{}) - close(done) - }() - select { - case <-done: - case <-ctx.Done(): - conn.Close() - return ctx.Err() - } - if err != nil { - buffer.Release() - if !errors.Is(err, os.ErrDeadlineExceeded) { - return err - } - } else { - if metadata.Destination.Addr.IsUnspecified() { - metadata.Destination = destination + var bufferList []*buf.Buffer + for { + var ( + buffer = buf.NewPacket() + destination M.Socksaddr + done = make(chan struct{}) + err error + ) + go func() { + sniffTimeout := C.ReadPayloadTimeout + if metadata.InboundOptions.SniffTimeout > 0 { + sniffTimeout = time.Duration(metadata.InboundOptions.SniffTimeout) + } + conn.SetReadDeadline(time.Now().Add(sniffTimeout)) + destination, err = conn.ReadPacket(buffer) + conn.SetReadDeadline(time.Time{}) + close(done) + }() + select { + case <-done: + case <-ctx.Done(): + conn.Close() + return ctx.Err() } - if metadata.InboundOptions.SniffEnabled { - sniffMetadata, _ := sniff.PeekPacket( - ctx, - buffer.Bytes(), - sniff.DomainNameQuery, - sniff.QUICClientHello, - sniff.STUNMessage, - sniff.UTP, - sniff.UDPTracker, - sniff.DTLSRecord, - ) - if sniffMetadata != nil { - metadata.Protocol = sniffMetadata.Protocol - metadata.Domain = sniffMetadata.Domain - if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { - metadata.Destination = M.Socksaddr{ - Fqdn: metadata.Domain, - Port: metadata.Destination.Port, - } - } - if metadata.Domain != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) + if err != nil { + buffer.Release() + if !errors.Is(err, os.ErrDeadlineExceeded) { + return err + } + } else { + if metadata.Destination.Addr.IsUnspecified() { + metadata.Destination = destination + } + if metadata.InboundOptions.SniffEnabled { + if len(bufferList) > 0 { + err = sniff.PeekPacket( + ctx, + &metadata, + buffer.Bytes(), + sniff.QUICClientHello, + ) } else { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) + err = sniff.PeekPacket( + ctx, &metadata, + buffer.Bytes(), + sniff.DomainNameQuery, + sniff.QUICClientHello, + sniff.STUNMessage, + sniff.UTP, + sniff.UDPTracker, + sniff.DTLSRecord) + } + if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(bufferList) == 0 { + bufferList = append(bufferList, buffer) + r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") + continue + } + if metadata.Protocol != "" { + if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { + metadata.Destination = M.Socksaddr{ + Fqdn: metadata.Domain, + Port: metadata.Destination.Port, + } + } + if metadata.Domain != "" && metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) + } else if metadata.Domain != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) + } else if metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client) + } else { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) + } } } + conn = bufio.NewCachedPacketConn(conn, buffer, destination) } - conn = bufio.NewCachedPacketConn(conn, buffer, destination) + for _, cachedBuffer := range common.Reverse(bufferList) { + conn = bufio.NewCachedPacketConn(conn, cachedBuffer, destination) + } + break } } if r.dnsReverseMapping != nil && metadata.Domain == "" { diff --git a/route/rule_default.go b/route/rule_default.go index 53e53bdf86..4bbc2b6b2e 100644 --- a/route/rule_default.go +++ b/route/rule_default.go @@ -79,6 +79,11 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.Client) > 0 { + item := NewClientItem(options.Client) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { item := NewDomainItem(options.Domain, options.DomainSuffix) rule.destinationAddressItems = append(rule.destinationAddressItems, item) diff --git a/route/rule_item_client.go b/route/rule_item_client.go new file mode 100644 index 0000000000..eeab440240 --- /dev/null +++ b/route/rule_item_client.go @@ -0,0 +1,37 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*ClientItem)(nil) + +type ClientItem struct { + clients []string + clientMap map[string]bool +} + +func NewClientItem(clients []string) *ClientItem { + clientMap := make(map[string]bool) + for _, client := range clients { + clientMap[client] = true + } + return &ClientItem{ + clients: clients, + clientMap: clientMap, + } +} + +func (r *ClientItem) Match(metadata *adapter.InboundContext) bool { + return r.clientMap[metadata.Client] +} + +func (r *ClientItem) String() string { + if len(r.clients) == 1 { + return F.ToString("client=", r.clients[0]) + } + return F.ToString("client=[", strings.Join(r.clients, " "), "]") +} From a9209bb3e5b3900e3457b25d0a2b6c693df775ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 26 Jul 2024 08:03:08 +0800 Subject: [PATCH 0060/1272] Add AdGuard DNS filter support --- cmd/sing-box/cmd_rule_set_convert.go | 88 +++++ .../internal/convertor/adguard/convertor.go | 346 ++++++++++++++++++ .../convertor/adguard/convertor_test.go | 140 +++++++ common/srs/binary.go | 22 ++ docs/configuration/rule-set/adguard.md | 71 ++++ mkdocs.yml | 1 + option/rule_set.go | 3 + route/rule_headless.go | 9 + route/rule_item_adguard.go | 43 +++ 9 files changed, 723 insertions(+) create mode 100644 cmd/sing-box/cmd_rule_set_convert.go create mode 100644 cmd/sing-box/internal/convertor/adguard/convertor.go create mode 100644 cmd/sing-box/internal/convertor/adguard/convertor_test.go create mode 100644 docs/configuration/rule-set/adguard.md create mode 100644 route/rule_item_adguard.go diff --git a/cmd/sing-box/cmd_rule_set_convert.go b/cmd/sing-box/cmd_rule_set_convert.go new file mode 100644 index 0000000000..8d2092ea28 --- /dev/null +++ b/cmd/sing-box/cmd_rule_set_convert.go @@ -0,0 +1,88 @@ +package main + +import ( + "io" + "os" + "strings" + + "github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard" + "github.com/sagernet/sing-box/common/srs" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + + "github.com/spf13/cobra" +) + +var ( + flagRuleSetConvertType string + flagRuleSetConvertOutput string +) + +var commandRuleSetConvert = &cobra.Command{ + Use: "convert [source-path]", + Short: "Convert adguard DNS filter to rule-set", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + err := convertRuleSet(args[0]) + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandRuleSet.AddCommand(commandRuleSetConvert) + commandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertType, "type", "t", "", "Source type, available: adguard") + commandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertOutput, "output", "o", flagRuleSetCompileDefaultOutput, "Output file") +} + +func convertRuleSet(sourcePath string) error { + var ( + reader io.Reader + err error + ) + if sourcePath == "stdin" { + reader = os.Stdin + } else { + reader, err = os.Open(sourcePath) + if err != nil { + return err + } + } + var rules []option.HeadlessRule + switch flagRuleSetConvertType { + case "adguard": + rules, err = adguard.Convert(reader) + case "": + return E.New("source type is required") + default: + return E.New("unsupported source type: ", flagRuleSetConvertType) + } + if err != nil { + return err + } + var outputPath string + if flagRuleSetConvertOutput == flagRuleSetCompileDefaultOutput { + if strings.HasSuffix(sourcePath, ".txt") { + outputPath = sourcePath[:len(sourcePath)-4] + ".srs" + } else { + outputPath = sourcePath + ".srs" + } + } else { + outputPath = flagRuleSetConvertOutput + } + outputFile, err := os.Create(outputPath) + if err != nil { + return err + } + defer outputFile.Close() + err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, true) + if err != nil { + outputFile.Close() + os.Remove(outputPath) + return err + } + outputFile.Close() + return nil +} diff --git a/cmd/sing-box/internal/convertor/adguard/convertor.go b/cmd/sing-box/internal/convertor/adguard/convertor.go new file mode 100644 index 0000000000..ff60929b8d --- /dev/null +++ b/cmd/sing-box/internal/convertor/adguard/convertor.go @@ -0,0 +1,346 @@ +package adguard + +import ( + "bufio" + "io" + "net/netip" + "os" + "strconv" + "strings" + + 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" + M "github.com/sagernet/sing/common/metadata" +) + +type agdguardRuleLine struct { + ruleLine string + isRawDomain bool + isExclude bool + isSuffix bool + hasStart bool + hasEnd bool + isRegexp bool + isImportant bool +} + +func Convert(reader io.Reader) ([]option.HeadlessRule, error) { + scanner := bufio.NewScanner(reader) + var ( + ruleLines []agdguardRuleLine + ignoredLines int + ) +parseLine: + for scanner.Scan() { + ruleLine := scanner.Text() + if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' { + continue + } + originRuleLine := ruleLine + if M.IsDomainName(ruleLine) { + ruleLines = append(ruleLines, agdguardRuleLine{ + ruleLine: ruleLine, + isRawDomain: true, + }) + continue + } + hostLine, err := parseAdGuardHostLine(ruleLine) + if err == nil { + if hostLine != "" { + ruleLines = append(ruleLines, agdguardRuleLine{ + ruleLine: hostLine, + isRawDomain: true, + hasStart: true, + hasEnd: true, + }) + } + continue + } + if strings.HasSuffix(ruleLine, "|") { + ruleLine = ruleLine[:len(ruleLine)-1] + } + var ( + isExclude bool + isSuffix bool + hasStart bool + hasEnd bool + isRegexp bool + isImportant bool + ) + if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") { + params := common.SubstringAfter(ruleLine, "$") + for _, param := range strings.Split(params, ",") { + paramParts := strings.Split(param, "=") + var ignored bool + if len(paramParts) > 0 && len(paramParts) <= 2 { + switch paramParts[0] { + case "app", "network": + // maybe support by package_name/process_name + case "dnstype": + // maybe support by query_type + case "important": + ignored = true + isImportant = true + case "dnsrewrite": + if len(paramParts) == 2 && M.ParseAddr(paramParts[1]).IsUnspecified() { + ignored = true + } + } + } + if !ignored { + ignoredLines++ + log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine) + continue parseLine + } + } + ruleLine = common.SubstringBefore(ruleLine, "$") + } + if strings.HasPrefix(ruleLine, "@@") { + ruleLine = ruleLine[2:] + isExclude = true + } + if strings.HasSuffix(ruleLine, "|") { + ruleLine = ruleLine[:len(ruleLine)-1] + } + if strings.HasPrefix(ruleLine, "||") { + ruleLine = ruleLine[2:] + isSuffix = true + } else if strings.HasPrefix(ruleLine, "|") { + ruleLine = ruleLine[1:] + hasStart = true + } + if strings.HasSuffix(ruleLine, "^") { + ruleLine = ruleLine[:len(ruleLine)-1] + hasEnd = true + } + if strings.HasPrefix(ruleLine, "/") && strings.HasSuffix(ruleLine, "/") { + ruleLine = ruleLine[1 : len(ruleLine)-1] + if ignoreIPCIDRRegexp(ruleLine) { + ignoredLines++ + log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine) + continue + } + isRegexp = true + } else { + if strings.Contains(ruleLine, "://") { + ruleLine = common.SubstringAfter(ruleLine, "://") + } + if strings.Contains(ruleLine, "/") { + ignoredLines++ + log.Debug("ignored unsupported rule with path: ", ruleLine) + continue + } + if strings.Contains(ruleLine, "##") { + ignoredLines++ + log.Debug("ignored unsupported rule with element hiding: ", ruleLine) + continue + } + if strings.Contains(ruleLine, "#$#") { + ignoredLines++ + log.Debug("ignored unsupported rule with element hiding: ", ruleLine) + continue + } + var domainCheck string + if strings.HasPrefix(ruleLine, ".") || strings.HasPrefix(ruleLine, "-") { + domainCheck = "r" + ruleLine + } else { + domainCheck = ruleLine + } + if ruleLine == "" { + ignoredLines++ + log.Debug("ignored unsupported rule with empty domain", originRuleLine) + continue + } else { + domainCheck = strings.ReplaceAll(domainCheck, "*", "x") + if !M.IsDomainName(domainCheck) { + _, ipErr := parseADGuardIPCIDRLine(ruleLine) + if ipErr == nil { + ignoredLines++ + log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine) + continue + } + if M.ParseSocksaddr(domainCheck).Port != 0 { + log.Debug("ignored unsupported rule with port: ", ruleLine) + } else { + log.Debug("ignored unsupported rule with invalid domain: ", ruleLine) + } + ignoredLines++ + continue + } + } + } + ruleLines = append(ruleLines, agdguardRuleLine{ + ruleLine: ruleLine, + isExclude: isExclude, + isSuffix: isSuffix, + hasStart: hasStart, + hasEnd: hasEnd, + isRegexp: isRegexp, + isImportant: isImportant, + }) + } + if len(ruleLines) == 0 { + return nil, E.New("AdGuard rule-set is empty or all rules are unsupported") + } + if common.All(ruleLines, func(it agdguardRuleLine) bool { + return it.isRawDomain + }) { + return []option.HeadlessRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + Domain: common.Map(ruleLines, func(it agdguardRuleLine) string { + return it.ruleLine + }), + }, + }, + }, nil + } + mapDomain := func(it agdguardRuleLine) string { + ruleLine := it.ruleLine + if it.isSuffix { + ruleLine = "||" + ruleLine + } else if it.hasStart { + ruleLine = "|" + ruleLine + } + if it.hasEnd { + ruleLine += "^" + } + return ruleLine + } + + importantDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && !it.isRegexp && !it.isExclude }), mapDomain) + importantDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && it.isRegexp && !it.isExclude }), mapDomain) + importantExcludeDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && !it.isRegexp && it.isExclude }), mapDomain) + importantExcludeDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && it.isRegexp && it.isExclude }), mapDomain) + domain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && !it.isRegexp && !it.isExclude }), mapDomain) + domainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && it.isRegexp && !it.isExclude }), mapDomain) + excludeDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && !it.isRegexp && it.isExclude }), mapDomain) + excludeDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && it.isRegexp && it.isExclude }), mapDomain) + currentRule := option.HeadlessRule{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + AdGuardDomain: domain, + DomainRegex: domainRegex, + }, + } + if len(excludeDomain) > 0 || len(excludeDomainRegex) > 0 { + currentRule = option.HeadlessRule{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalHeadlessRule{ + Mode: C.LogicalTypeAnd, + Rules: []option.HeadlessRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + AdGuardDomain: excludeDomain, + DomainRegex: excludeDomainRegex, + Invert: true, + }, + }, + currentRule, + }, + }, + } + } + if len(importantDomain) > 0 || len(importantDomainRegex) > 0 { + currentRule = option.HeadlessRule{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalHeadlessRule{ + Mode: C.LogicalTypeOr, + Rules: []option.HeadlessRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + AdGuardDomain: importantDomain, + DomainRegex: importantDomainRegex, + }, + }, + currentRule, + }, + }, + } + } + if len(importantExcludeDomain) > 0 || len(importantExcludeDomainRegex) > 0 { + currentRule = option.HeadlessRule{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalHeadlessRule{ + Mode: C.LogicalTypeAnd, + Rules: []option.HeadlessRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + AdGuardDomain: importantExcludeDomain, + DomainRegex: importantExcludeDomainRegex, + Invert: true, + }, + }, + currentRule, + }, + }, + } + } + log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines) + return []option.HeadlessRule{currentRule}, nil +} + +func ignoreIPCIDRRegexp(ruleLine string) bool { + if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") { + ruleLine = ruleLine[12:] + } else if strings.HasPrefix(ruleLine, "(https?:\\/\\/)") { + ruleLine = ruleLine[13:] + } else if strings.HasPrefix(ruleLine, "^") { + ruleLine = ruleLine[1:] + } else { + return false + } + _, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8) + return parseErr == nil +} + +func parseAdGuardHostLine(ruleLine string) (string, error) { + idx := strings.Index(ruleLine, " ") + if idx == -1 { + return "", os.ErrInvalid + } + address, err := netip.ParseAddr(ruleLine[:idx]) + if err != nil { + return "", err + } + if !address.IsUnspecified() { + return "", nil + } + domain := ruleLine[idx+1:] + if !M.IsDomainName(domain) { + return "", E.New("invalid domain name: ", domain) + } + return domain, nil +} + +func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) { + var isPrefix bool + if strings.HasSuffix(ruleLine, ".") { + isPrefix = true + ruleLine = ruleLine[:len(ruleLine)-1] + } + ruleStringParts := strings.Split(ruleLine, ".") + if len(ruleStringParts) > 4 || len(ruleStringParts) < 4 && !isPrefix { + return netip.Prefix{}, os.ErrInvalid + } + ruleParts := make([]uint8, 0, len(ruleStringParts)) + for _, part := range ruleStringParts { + rulePart, err := strconv.ParseUint(part, 10, 8) + if err != nil { + return netip.Prefix{}, err + } + ruleParts = append(ruleParts, uint8(rulePart)) + } + bitLen := len(ruleParts) * 8 + for len(ruleParts) < 4 { + ruleParts = append(ruleParts, 0) + } + return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil +} diff --git a/cmd/sing-box/internal/convertor/adguard/convertor_test.go b/cmd/sing-box/internal/convertor/adguard/convertor_test.go new file mode 100644 index 0000000000..7da8a22841 --- /dev/null +++ b/cmd/sing-box/internal/convertor/adguard/convertor_test.go @@ -0,0 +1,140 @@ +package adguard + +import ( + "strings" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/route" + + "github.com/stretchr/testify/require" +) + +func TestConverter(t *testing.T) { + t.Parallel() + rules, err := Convert(strings.NewReader(` +||example.org^ +|example.com^ +example.net^ +||example.edu +||example.edu.tw^ +|example.gov +example.arpa +@@|sagernet.example.org| +||sagernet.org^$important +@@|sing-box.sagernet.org^$important +`)) + require.NoError(t, err) + require.Len(t, rules, 1) + rule, err := route.NewHeadlessRule(nil, rules[0]) + require.NoError(t, err) + matchDomain := []string{ + "example.org", + "www.example.org", + "example.com", + "example.net", + "isexample.net", + "www.example.net", + "example.edu", + "example.edu.cn", + "example.edu.tw", + "www.example.edu", + "www.example.edu.cn", + "example.gov", + "example.gov.cn", + "example.arpa", + "www.example.arpa", + "isexample.arpa", + "example.arpa.cn", + "www.example.arpa.cn", + "isexample.arpa.cn", + "sagernet.org", + "www.sagernet.org", + } + notMatchDomain := []string{ + "example.org.cn", + "notexample.org", + "example.com.cn", + "www.example.com.cn", + "example.net.cn", + "notexample.edu", + "notexample.edu.cn", + "www.example.gov", + "notexample.gov", + "sagernet.example.org", + "sing-box.sagernet.org", + } + for _, domain := range matchDomain { + require.True(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } + for _, domain := range notMatchDomain { + require.False(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } +} + +func TestHosts(t *testing.T) { + t.Parallel() + rules, err := Convert(strings.NewReader(` +127.0.0.1 localhost +::1 localhost #[IPv6] +0.0.0.0 google.com +`)) + require.NoError(t, err) + require.Len(t, rules, 1) + rule, err := route.NewHeadlessRule(nil, rules[0]) + require.NoError(t, err) + matchDomain := []string{ + "google.com", + } + notMatchDomain := []string{ + "www.google.com", + "notgoogle.com", + "localhost", + } + for _, domain := range matchDomain { + require.True(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } + for _, domain := range notMatchDomain { + require.False(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } +} + +func TestSimpleHosts(t *testing.T) { + t.Parallel() + rules, err := Convert(strings.NewReader(` +example.com +www.example.org +`)) + require.NoError(t, err) + require.Len(t, rules, 1) + rule, err := route.NewHeadlessRule(nil, rules[0]) + require.NoError(t, err) + matchDomain := []string{ + "example.com", + "www.example.org", + } + notMatchDomain := []string{ + "example.com.cn", + "www.example.com", + "notexample.com", + "example.org", + } + for _, domain := range matchDomain { + require.True(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } + for _, domain := range notMatchDomain { + require.False(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } +} diff --git a/common/srs/binary.go b/common/srs/binary.go index 69075f7881..7e4f402b39 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -36,6 +36,7 @@ const ( ruleItemPackageName ruleItemWIFISSID ruleItemWIFIBSSID + ruleItemAdGuardDomain ruleItemFinal uint8 = 0xFF ) @@ -212,6 +213,17 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea rule.WIFISSID, err = readRuleItemString(reader) case ruleItemWIFIBSSID: rule.WIFIBSSID, err = readRuleItemString(reader) + case ruleItemAdGuardDomain: + if recover { + err = E.New("unable to decompile binary AdGuard rules to rule-set") + return + } + var matcher *domain.AdGuardMatcher + matcher, err = domain.ReadAdGuardMatcher(reader) + if err != nil { + return + } + rule.AdGuardDomainMatcher = matcher case ruleItemFinal: err = binary.Read(reader, binary.BigEndian, &rule.Invert) return @@ -332,6 +344,16 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen return err } } + if len(rule.AdGuardDomain) > 0 { + err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain) + if err != nil { + return err + } + err = domain.NewAdGuardMatcher(rule.AdGuardDomain).Write(writer) + if err != nil { + return err + } + } err = binary.Write(writer, binary.BigEndian, ruleItemFinal) if err != nil { return err diff --git a/docs/configuration/rule-set/adguard.md b/docs/configuration/rule-set/adguard.md new file mode 100644 index 0000000000..870972bf2f --- /dev/null +++ b/docs/configuration/rule-set/adguard.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +# AdGuard DNS Filter + +!!! question "Since sing-box 1.10.0" + +sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box, +currently only AdGuard DNS Filter. + +These formats are not directly supported as source formats, +instead you need to convert them to binary rule-set. + +## Convert + +Use `sing-box rule-set convert --type adguard [--output .srs] .txt` to convert to binary rule-set. + +## Performance + +AdGuard keeps all rules in memory and matches them sequentially, +while sing-box chooses high performance and smaller memory usage. +As a trade-off, you cannot know which rule item is matched. + +## Compatibility + +Almost all rules in [AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter) +and rules in rule-sets listed in [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list) +are supported. + +## Supported formats + +### AdGuard Filter + +#### Basic rule syntax + +| Syntax | Supported | +|--------|------------------| +| `@@` | :material-check: | +| `\|\|` | :material-check: | +| `\|` | :material-check: | +| `^` | :material-check: | +| `*` | :material-check: | + +#### Host syntax + +| Syntax | Example | Supported | +|-------------|--------------------------|--------------------------| +| Scheme | `https://` | :material-alert: Ignored | +| Domain Host | `example.org` | :material-check: | +| IP Host | `1.1.1.1`, `10.0.0.` | :material-close: | +| Regexp | `/regexp/` | :material-check: | +| Port | `example.org:80` | :material-close: | +| Path | `example.org/path/ad.js` | :material-close: | + +#### Modifier syntax + +| Modifier | Supported | +|-----------------------|--------------------------| +| `$important` | :material-check: | +| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored | +| Any other modifiers | :material-close: | + +### Hosts + +Only items with `0.0.0.0` IP addresses will be accepted. + +### Simple + +When all rule lines are valid domains, they are treated as simple line-by-line domain rules which, +like hosts, only match the exact same domain. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index f8a40765f2..56bdf96a47 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -91,6 +91,7 @@ nav: - configuration/rule-set/index.md - Source Format: configuration/rule-set/source-format.md - Headless Rule: configuration/rule-set/headless-rule.md + - AdGuard DNS Filer: configuration/rule-set/adguard.md - Experimental: - configuration/experimental/index.md - Cache File: configuration/experimental/cache-file.md diff --git a/option/rule_set.go b/option/rule_set.go index 4e9eb0f8b3..a83b2c47b6 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -166,6 +166,9 @@ type DefaultHeadlessRule struct { DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` IPSet *netipx.IPSet `json:"-"` + + AdGuardDomain Listable[string] `json:"-"` + AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"` } func (r DefaultHeadlessRule) IsValid() bool { diff --git a/route/rule_headless.go b/route/rule_headless.go index 67ac3a1e44..d537df572c 100644 --- a/route/rule_headless.go +++ b/route/rule_headless.go @@ -142,6 +142,15 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles rule.allItems = append(rule.allItems, item) } } + if len(options.AdGuardDomain) > 0 { + item := NewAdGuardDomainItem(options.AdGuardDomain) + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) + } else if options.AdGuardDomainMatcher != nil { + item := NewRawAdGuardDomainItem(options.AdGuardDomainMatcher) + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) + } return rule, nil } diff --git a/route/rule_item_adguard.go b/route/rule_item_adguard.go new file mode 100644 index 0000000000..bdbb3b75fd --- /dev/null +++ b/route/rule_item_adguard.go @@ -0,0 +1,43 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/domain" +) + +var _ RuleItem = (*AdGuardDomainItem)(nil) + +type AdGuardDomainItem struct { + matcher *domain.AdGuardMatcher +} + +func NewAdGuardDomainItem(ruleLines []string) *AdGuardDomainItem { + return &AdGuardDomainItem{ + domain.NewAdGuardMatcher(ruleLines), + } +} + +func NewRawAdGuardDomainItem(matcher *domain.AdGuardMatcher) *AdGuardDomainItem { + return &AdGuardDomainItem{ + matcher, + } +} + +func (r *AdGuardDomainItem) Match(metadata *adapter.InboundContext) bool { + var domainHost string + if metadata.Domain != "" { + domainHost = metadata.Domain + } else { + domainHost = metadata.Destination.Fqdn + } + if domainHost == "" { + return false + } + return r.matcher.Match(strings.ToLower(domainHost)) +} + +func (r *AdGuardDomainItem) String() string { + return "!adguard_domain_rules=" +} From b2c708a3e68e44f007ef0f039d4de45deea0f920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 10 Aug 2024 16:47:44 +0800 Subject: [PATCH 0061/1272] Write close error to log --- cmd/sing-box/cmd_run.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/sing-box/cmd_run.go b/cmd/sing-box/cmd_run.go index e717c5945d..6850cd19e3 100644 --- a/cmd/sing-box/cmd_run.go +++ b/cmd/sing-box/cmd_run.go @@ -188,9 +188,12 @@ func run() error { cancel() closeCtx, closed := context.WithCancel(context.Background()) go closeMonitor(closeCtx) - instance.Close() + err = instance.Close() closed() if osSignal != syscall.SIGHUP { + if err != nil { + log.Error(E.Cause(err, "sing-box did not closed properly")) + } return nil } break From e39a28ed5a7c118db3d012cfcf3b908fca2c6319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 20 Aug 2024 18:56:50 +0800 Subject: [PATCH 0062/1272] Add SSH sniffer --- common/sniff/ssh.go | 26 ++++++++++++++++++++++++++ common/sniff/ssh_test.go | 26 ++++++++++++++++++++++++++ constant/protocol.go | 1 + docs/configuration/route/sniff.md | 8 +++++--- docs/configuration/route/sniff.zh.md | 24 +++++++++++++----------- route/router.go | 3 ++- 6 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 common/sniff/ssh.go create mode 100644 common/sniff/ssh_test.go diff --git a/common/sniff/ssh.go b/common/sniff/ssh.go new file mode 100644 index 0000000000..194d0bda8c --- /dev/null +++ b/common/sniff/ssh.go @@ -0,0 +1,26 @@ +package sniff + +import ( + "bufio" + "context" + "io" + "os" + "strings" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" +) + +func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { + scanner := bufio.NewScanner(reader) + if !scanner.Scan() { + return os.ErrInvalid + } + fistLine := scanner.Text() + if !strings.HasPrefix(fistLine, "SSH-2.0-") { + return os.ErrInvalid + } + metadata.Protocol = C.ProtocolSSH + metadata.Client = fistLine[8:] + return nil +} diff --git a/common/sniff/ssh_test.go b/common/sniff/ssh_test.go new file mode 100644 index 0000000000..be53098079 --- /dev/null +++ b/common/sniff/ssh_test.go @@ -0,0 +1,26 @@ +package sniff_test + +import ( + "bytes" + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffSSH(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("5353482d322e302d64726f70626561720d0a000001a40a1492892570d1223aef61b0d647972c8bd30000009f637572766532353531392d7368613235362c637572766532353531392d736861323536406c69627373682e6f72672c6469666669652d68656c6c6d616e2d67726f757031342d7368613235362c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6b6578677565737332406d6174742e7563632e61736e2e61752c6b65782d7374726963742d732d763030406f70656e7373682e636f6d000000207373682d656432353531392c7273612d736861322d3235362c7373682d7273610000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d6374720000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d63747200000017686d61632d736861312c686d61632d736861322d32353600000017686d61632d736861312c686d61632d736861322d323536000000046e6f6e65000000046e6f6e65000000000000000000000000002aa6ed090585b7d635b6") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NoError(t, err) + require.Equal(t, C.ProtocolSSH, metadata.Protocol) + require.Equal(t, "dropbear", metadata.Client) +} diff --git a/constant/protocol.go b/constant/protocol.go index 8b1854d419..f867f428f6 100644 --- a/constant/protocol.go +++ b/constant/protocol.go @@ -8,6 +8,7 @@ const ( ProtocolSTUN = "stun" ProtocolBitTorrent = "bittorrent" ProtocolDTLS = "dtls" + ProtocolSSH = "ssh" ) const ( diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index ab96e75a6f..70e2acc85a 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -7,14 +7,15 @@ icon: material/new-box :material-plus: QUIC client type detect support for QUIC :material-plus: Chromium support for QUIC :material-plus: BitTorrent support - :material-plus: DTLS support + :material-plus: DTLS support + :material-plus: SSH support If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed. #### Supported Protocols | Network | Protocol | Domain Name | Client | -|:-------:|:------------:|:-----------:|:----------------:| +| :-----: | :----------: | :---------: | :--------------: | | TCP | `http` | Host | / | | TCP | `tls` | Server Name | / | | UDP | `quic` | Server Name | QUIC Client Type | @@ -22,9 +23,10 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c | TCP/UDP | `dns` | / | / | | TCP/UDP | `bittorrent` | / | / | | UDP | `dtls` | / | / | +| TCP | `ssh` | / | SSH Client Name | | QUIC Client | Type | -|:------------------------:|:----------:| +| :----------------------: | :--------: | | Chromium/Cronet | `chrimium` | | Safari/Apple Network API | `safari` | | Firefox / uquic firefox | `firefox` | diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index 1ebc6d38d1..7421fd07e1 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -7,24 +7,26 @@ icon: material/new-box :material-plus: QUIC 的 客户端类型探测支持 :material-plus: QUIC 的 Chromium 支持 :material-plus: BitTorrent 支持 - :material-plus: DTLS 支持 + :material-plus: DTLS 支持 + :material-plus: SSH 支持 如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。 #### 支持的协议 -| 网络 | 协议 | 域名 | 客户端 | -|:-------:|:------------:|:-----------:|:----------:| -| TCP | `http` | Host | / | -| TCP | `tls` | Server Name | / | +| 网络 | 协议 | 域名 | 客户端 | +| :-----: | :----------: | :---------: | :-------------: | +| TCP | `http` | Host | / | +| TCP | `tls` | Server Name | / | | UDP | `quic` | Server Name | QUIC 客户端类型 | -| UDP | `stun` | / | / | -| TCP/UDP | `dns` | / | / | -| TCP/UDP | `bittorrent` | / | / | -| UDP | `dtls` | / | / | +| UDP | `stun` | / | / | +| TCP/UDP | `dns` | / | / | +| TCP/UDP | `bittorrent` | / | / | +| UDP | `dtls` | / | / | +| TCP | `SSH` | / | SSH 客户端名称 | -| QUIC 客户端 | 类型 | -|:------------------------:|:----------:| +| QUIC 客户端 | 类型 | +| :----------------------: | :--------: | | Chromium/Cronet | `chrimium` | | Safari/Apple Network API | `safari` | | Firefox / uquic firefox | `firefox` | diff --git a/route/router.go b/route/router.go index 35e1e21f28..aa121cb44e 100644 --- a/route/router.go +++ b/route/router.go @@ -860,9 +860,10 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), - sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost, + sniff.StreamDomainNameQuery, + sniff.SSH, sniff.BitTorrent, ) if err == nil { From 4a95558c531b913d7fa4e3793320efa7de95be77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 28 Aug 2024 14:22:19 +0800 Subject: [PATCH 0063/1272] Add RDP sniffer --- common/sniff/rdp.go | 90 ++++++++++++++++++++++++++++ common/sniff/rdp_test.go | 25 ++++++++ constant/protocol.go | 1 + docs/configuration/route/sniff.md | 8 ++- docs/configuration/route/sniff.zh.md | 26 ++++---- 5 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 common/sniff/rdp.go create mode 100644 common/sniff/rdp_test.go diff --git a/common/sniff/rdp.go b/common/sniff/rdp.go new file mode 100644 index 0000000000..391ebd2695 --- /dev/null +++ b/common/sniff/rdp.go @@ -0,0 +1,90 @@ +package sniff + +import ( + "context" + "encoding/binary" + "io" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/rw" +) + +func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { + var tpktVersion uint8 + err := binary.Read(reader, binary.BigEndian, &tpktVersion) + if err != nil { + return err + } + if tpktVersion != 0x03 { + return os.ErrInvalid + } + + var tpktReserved uint8 + err = binary.Read(reader, binary.BigEndian, &tpktReserved) + if err != nil { + return err + } + if tpktReserved != 0x00 { + return os.ErrInvalid + } + + var tpktLength uint16 + err = binary.Read(reader, binary.BigEndian, &tpktLength) + if err != nil { + return err + } + + if tpktLength != 19 { + return os.ErrInvalid + } + + var cotpLength uint8 + err = binary.Read(reader, binary.BigEndian, &cotpLength) + if err != nil { + return err + } + + if cotpLength != 14 { + return os.ErrInvalid + } + + var cotpTpduType uint8 + err = binary.Read(reader, binary.BigEndian, &cotpTpduType) + if err != nil { + return err + } + if cotpTpduType != 0xE0 { + return os.ErrInvalid + } + + err = rw.SkipN(reader, 5) + if err != nil { + return err + } + + var rdpType uint8 + err = binary.Read(reader, binary.BigEndian, &rdpType) + if err != nil { + return err + } + if rdpType != 0x01 { + return os.ErrInvalid + } + var rdpFlags uint8 + err = binary.Read(reader, binary.BigEndian, &rdpFlags) + if err != nil { + return err + } + var rdpLength uint8 + err = binary.Read(reader, binary.BigEndian, &rdpLength) + if err != nil { + return err + } + if rdpLength != 8 { + return os.ErrInvalid + } + metadata.Protocol = C.ProtocolRDP + return nil +} diff --git a/common/sniff/rdp_test.go b/common/sniff/rdp_test.go new file mode 100644 index 0000000000..06fa3ab275 --- /dev/null +++ b/common/sniff/rdp_test.go @@ -0,0 +1,25 @@ +package sniff_test + +import ( + "bytes" + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffRDP(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("030000130ee00000000000010008000b000000010008000b000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.RDP(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NoError(t, err) + require.Equal(t, C.ProtocolRDP, metadata.Protocol) +} diff --git a/constant/protocol.go b/constant/protocol.go index f867f428f6..1485408945 100644 --- a/constant/protocol.go +++ b/constant/protocol.go @@ -9,6 +9,7 @@ const ( ProtocolBitTorrent = "bittorrent" ProtocolDTLS = "dtls" ProtocolSSH = "ssh" + ProtocolRDP = "rdp" ) const ( diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index 70e2acc85a..40de038cb2 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -8,14 +8,15 @@ icon: material/new-box :material-plus: Chromium support for QUIC :material-plus: BitTorrent support :material-plus: DTLS support - :material-plus: SSH support + :material-plus: SSH support + :material-plus: RDP support If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed. #### Supported Protocols | Network | Protocol | Domain Name | Client | -| :-----: | :----------: | :---------: | :--------------: | +|:-------:|:------------:|:-----------:|:----------------:| | TCP | `http` | Host | / | | TCP | `tls` | Server Name | / | | UDP | `quic` | Server Name | QUIC Client Type | @@ -24,9 +25,10 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c | TCP/UDP | `bittorrent` | / | / | | UDP | `dtls` | / | / | | TCP | `ssh` | / | SSH Client Name | +| TCP | `rdp` | / | / | | QUIC Client | Type | -| :----------------------: | :--------: | +|:------------------------:|:----------:| | Chromium/Cronet | `chrimium` | | Safari/Apple Network API | `safari` | | Firefox / uquic firefox | `firefox` | diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index 7421fd07e1..4efa453814 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -8,25 +8,27 @@ icon: material/new-box :material-plus: QUIC 的 Chromium 支持 :material-plus: BitTorrent 支持 :material-plus: DTLS 支持 - :material-plus: SSH 支持 + :material-plus: SSH 支持 + :material-plus: RDP 支持 如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。 #### 支持的协议 -| 网络 | 协议 | 域名 | 客户端 | -| :-----: | :----------: | :---------: | :-------------: | -| TCP | `http` | Host | / | -| TCP | `tls` | Server Name | / | +| 网络 | 协议 | 域名 | 客户端 | +|:-------:|:------------:|:-----------:|:----------:| +| TCP | `http` | Host | / | +| TCP | `tls` | Server Name | / | | UDP | `quic` | Server Name | QUIC 客户端类型 | -| UDP | `stun` | / | / | -| TCP/UDP | `dns` | / | / | -| TCP/UDP | `bittorrent` | / | / | -| UDP | `dtls` | / | / | -| TCP | `SSH` | / | SSH 客户端名称 | +| UDP | `stun` | / | / | +| TCP/UDP | `dns` | / | / | +| TCP/UDP | `bittorrent` | / | / | +| UDP | `dtls` | / | / | +| TCP | `ssh` | / | SSH 客户端名称 | +| TCP | `rdp` | / | / | -| QUIC 客户端 | 类型 | -| :----------------------: | :--------: | +| QUIC 客户端 | 类型 | +|:------------------------:|:----------:| | Chromium/Cronet | `chrimium` | | Safari/Apple Network API | `safari` | | Firefox / uquic firefox | `firefox` | From 06533b7a3b2fd8625d7b7d1ce3857353d80e223d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 23 Aug 2024 13:38:38 +0800 Subject: [PATCH 0064/1272] clash-api: Add PNA support --- docs/configuration/experimental/clash-api.md | 96 +++++++++++++++---- .../experimental/clash-api.zh.md | 96 +++++++++++++++---- experimental/clashapi/server.go | 15 ++- go.mod | 2 +- go.sum | 4 +- option/experimental.go | 16 ++-- 6 files changed, 178 insertions(+), 51 deletions(-) diff --git a/docs/configuration/experimental/clash-api.md b/docs/configuration/experimental/clash-api.md index e1ca981521..7425143eb3 100644 --- a/docs/configuration/experimental/clash-api.md +++ b/docs/configuration/experimental/clash-api.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: [access_control_allow_origin](#access_control_allow_origin) + :material-plus: [access_control_allow_private_network](#access_control_allow_private_network) + !!! quote "Changes in sing-box 1.8.0" :material-delete-alert: [store_mode](#store_mode) @@ -8,24 +17,59 @@ ### Structure -```json -{ - "external_controller": "127.0.0.1:9090", - "external_ui": "", - "external_ui_download_url": "", - "external_ui_download_detour": "", - "secret": "", - "default_mode": "", - - // Deprecated - - "store_mode": false, - "store_selected": false, - "store_fakeip": false, - "cache_file": "", - "cache_id": "" -} -``` +=== "Structure" + + ```json + { + "external_controller": "127.0.0.1:9090", + "external_ui": "", + "external_ui_download_url": "", + "external_ui_download_detour": "", + "secret": "", + "default_mode": "", + "access_control_allow_origin": [], + "access_control_allow_private_network": false, + + // Deprecated + + "store_mode": false, + "store_selected": false, + "store_fakeip": false, + "cache_file": "", + "cache_id": "" + } + ``` + +=== "Example (online)" + + !!! question "Since sing-box 1.10.0" + + ```json + { + "external_controller": "127.0.0.1:9090", + "access_control_allow_origin": [ + "http://127.0.0.1", + "http://yacd.haishan.me" + ], + "access_control_allow_private_network": true + } + ``` + +=== "Example (download)" + + !!! question "Since sing-box 1.10.0" + + ```json + { + "external_controller": "0.0.0.0:9090", + "external_ui": "dashboard" + // external_ui_download_detour: "direct" + } + ``` + +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item ### Fields @@ -63,6 +107,22 @@ Default mode in clash, `Rule` will be used if empty. This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item. +#### access_control_allow_origin + +!!! question "Since sing-box 1.10.0" + +CORS allowed origins, `*` will be used if empty. + +To access the Clash API on a private network from a public website, you must explicitly specify it in `access_control_allow_origin` instead of using `*`. + +#### access_control_allow_private_network + +!!! question "Since sing-box 1.10.0" + +Allow access from private network. + +To access the Clash API on a private network from a public website, `access_control_allow_private_network` must be enabled. + #### store_mode !!! failure "Deprecated in sing-box 1.8.0" diff --git a/docs/configuration/experimental/clash-api.zh.md b/docs/configuration/experimental/clash-api.zh.md index 092769ac93..b3d8aeaf99 100644 --- a/docs/configuration/experimental/clash-api.zh.md +++ b/docs/configuration/experimental/clash-api.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: [access_control_allow_origin](#access_control_allow_origin) + :material-plus: [access_control_allow_private_network](#access_control_allow_private_network) + !!! quote "sing-box 1.8.0 中的更改" :material-delete-alert: [store_mode](#store_mode) @@ -8,24 +17,59 @@ ### 结构 -```json -{ - "external_controller": "127.0.0.1:9090", - "external_ui": "", - "external_ui_download_url": "", - "external_ui_download_detour": "", - "secret": "", - "default_mode": "", - - // Deprecated - - "store_mode": false, - "store_selected": false, - "store_fakeip": false, - "cache_file": "", - "cache_id": "" -} -``` +=== "结构" + + ```json + { + "external_controller": "127.0.0.1:9090", + "external_ui": "", + "external_ui_download_url": "", + "external_ui_download_detour": "", + "secret": "", + "default_mode": "", + "access_control_allow_origin": [], + "access_control_allow_private_network": false, + + // Deprecated + + "store_mode": false, + "store_selected": false, + "store_fakeip": false, + "cache_file": "", + "cache_id": "" + } + ``` + +=== "示例 (在线)" + + !!! question "自 sing-box 1.10.0 起" + + ```json + { + "external_controller": "127.0.0.1:9090", + "access_control_allow_origin": [ + "http://127.0.0.1", + "http://yacd.haishan.me" + ], + "access_control_allow_private_network": true + } + ``` + +=== "示例 (下载)" + + !!! question "自 sing-box 1.10.0 起" + + ```json + { + "external_controller": "0.0.0.0:9090", + "external_ui": "dashboard" + // external_ui_download_detour: "direct" + } + ``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 ### Fields @@ -61,6 +105,22 @@ Clash 中的默认模式,默认使用 `Rule`。 此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。 +#### access_control_allow_origin + +!!! question "自 sing-box 1.10.0 起" + +允许的 CORS 来源,默认使用 `*`。 + +要从公共网站访问私有网络上的 Clash API,必须在 `access_control_allow_origin` 中明确指定它而不是使用 `*`。 + +#### access_control_allow_private_network + +!!! question "自 sing-box 1.10.0 起" + +允许从私有网络访问。 + +要从公共网站访问私有网络上的 Clash API,必须启用 `access_control_allow_private_network`。 + #### store_mode !!! failure "已在 sing-box 1.8.0 废弃" diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 3428bbb771..889d191e0e 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + "github.com/sagernet/cors" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" @@ -29,7 +30,6 @@ import ( "github.com/sagernet/ws/wsutil" "github.com/go-chi/chi/v5" - "github.com/go-chi/cors" "github.com/go-chi/render" ) @@ -90,11 +90,16 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" { return nil, E.New("cache_file and related fields in Clash API is deprecated in sing-box 1.8.0, use experimental.cache_file instead.") } + allowedOrigins := options.AccessControlAllowOrigin + if len(allowedOrigins) == 0 { + allowedOrigins = []string{"*"} + } cors := cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Content-Type", "Authorization"}, - MaxAge: 300, + AllowedOrigins: allowedOrigins, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, + AllowedHeaders: []string{"Content-Type", "Authorization"}, + AllowPrivateNetwork: options.AccessControlAllowPrivateNetwork, + MaxAge: 300, }) chiRouter.Use(cors.Handler) chiRouter.Group(func(r chi.Router) { diff --git a/go.mod b/go.mod index f9b11d6736..9bfdbebffd 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.0.12 - github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.2.0 github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 @@ -22,6 +21,7 @@ require ( github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 + github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.4 github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f diff --git a/go.sum b/go.sum index 6deefbe260..21cd7047ec 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,6 @@ github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXb github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= -github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -102,6 +100,8 @@ 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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= +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/gomobile v0.1.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY= diff --git a/option/experimental.go b/option/experimental.go index 9f6071baee..6ab6638550 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -17,13 +17,15 @@ type CacheFileOptions struct { } type ClashAPIOptions struct { - ExternalController string `json:"external_controller,omitempty"` - ExternalUI string `json:"external_ui,omitempty"` - ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` - ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` - Secret string `json:"secret,omitempty"` - DefaultMode string `json:"default_mode,omitempty"` - ModeList []string `json:"-"` + ExternalController string `json:"external_controller,omitempty"` + ExternalUI string `json:"external_ui,omitempty"` + ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` + ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` + Secret string `json:"secret,omitempty"` + DefaultMode string `json:"default_mode,omitempty"` + ModeList []string `json:"-"` + AccessControlAllowOrigin Listable[string] `json:"access_control_allow_origin,omitempty"` + AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` // Deprecated: migrated to global cache file CacheFile string `json:"cache_file,omitempty"` From 846777cd0c4a44dd719e5cec092adf71c619b306 Mon Sep 17 00:00:00 2001 From: srk24 <25836646+srk24@users.noreply.github.com> Date: Sun, 15 Sep 2024 11:42:57 +0800 Subject: [PATCH 0065/1272] Add `process_path_regex` rule type --- common/srs/binary.go | 9 ++++ docs/clients/android/features.md | 1 + docs/clients/apple/features.md | 1 + docs/configuration/dns/rule.md | 16 +++++- docs/configuration/dns/rule.zh.md | 16 +++++- docs/configuration/route/rule.md | 14 +++++ docs/configuration/route/rule.zh.md | 16 +++++- docs/configuration/rule-set/headless-rule.md | 13 +++++ option/rule.go | 1 + option/rule_dns.go | 1 + option/rule_set.go | 37 +++++++------- route/rule_default.go | 8 +++ route/rule_dns.go | 8 +++ route/rule_headless.go | 8 +++ route/rule_item_process_path_regex.go | 54 ++++++++++++++++++++ 15 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 route/rule_item_process_path_regex.go diff --git a/common/srs/binary.go b/common/srs/binary.go index 7e4f402b39..489dc22e2f 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -37,6 +37,7 @@ const ( ruleItemWIFISSID ruleItemWIFIBSSID ruleItemAdGuardDomain + ruleItemProcessPathRegex ruleItemFinal uint8 = 0xFF ) @@ -207,6 +208,8 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea rule.ProcessName, err = readRuleItemString(reader) case ruleItemProcessPath: rule.ProcessPath, err = readRuleItemString(reader) + case ruleItemProcessPathRegex: + rule.ProcessPathRegex, err = readRuleItemString(reader) case ruleItemPackageName: rule.PackageName, err = readRuleItemString(reader) case ruleItemWIFISSID: @@ -326,6 +329,12 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen return err } } + if len(rule.ProcessPathRegex) > 0 { + err = writeRuleItemString(writer, ruleItemProcessPathRegex, rule.ProcessPathRegex) + if err != nil { + return err + } + } if len(rule.PackageName) > 0 { err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName) if err != nil { diff --git a/docs/clients/android/features.md b/docs/clients/android/features.md index 8fe84add26..f7f2caeec4 100644 --- a/docs/clients/android/features.md +++ b/docs/clients/android/features.md @@ -40,6 +40,7 @@ SFA provides an unprivileged TUN implementation through Android VpnService. |-----------------------|------------------|-----------------------------------| | `process_name` | :material-close: | No permission | | `process_path` | :material-close: | No permission | +| `process_path_regex` | :material-close: | No permission | | `package_name` | :material-check: | / | | `user` | :material-close: | Use `package_name` instead | | `user_id` | :material-close: | Use `package_name` instead | diff --git a/docs/clients/apple/features.md b/docs/clients/apple/features.md index 7d419103b5..d638517322 100644 --- a/docs/clients/apple/features.md +++ b/docs/clients/apple/features.md @@ -42,6 +42,7 @@ SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension |-----------------------|------------------|-----------------------| | `process_name` | :material-close: | No permission | | `process_path` | :material-close: | No permission | +| `process_path_regex` | :material-close: | No permission | | `package_name` | :material-close: | / | | `user` | :material-close: | No permission | | `user_id` | :material-close: | No permission | diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 6b3d65194f..03ca66bcc3 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -6,7 +6,8 @@ icon: material/new-box :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) - :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + :material-plus: [process_path_regex](#process_path_regex) !!! quote "Changes in sing-box 1.9.0" @@ -103,6 +104,9 @@ icon: material/new-box "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -268,6 +272,16 @@ Match process name. Match process path. +#### process_path_regex + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using regular expression. + #### package_name Match android package name. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index eecddb3816..b484cbed6b 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -6,7 +6,8 @@ icon: material/new-box :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) - :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + :material-plus: [process_path_regex](#process_path_regex) !!! quote "sing-box 1.9.0 中的更改" @@ -103,6 +104,9 @@ icon: material/new-box "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -266,6 +270,16 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 匹配进程路径。 +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + #### package_name 匹配 Android 应用包名。 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 8e61e51df2..91b432df1a 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -7,6 +7,7 @@ icon: material/alert-decagram :material-plus: [client](#client) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [process_path_regex](#process_path_regex) !!! quote "Changes in sing-box 1.8.0" @@ -101,6 +102,9 @@ icon: material/alert-decagram "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -277,6 +281,16 @@ Match process name. Match process path. +#### process_path_regex + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using regular expression. + #### package_name Match android package name. diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 68e66cf5c9..a93ce5e53d 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -6,7 +6,8 @@ icon: material/alert-decagram :material-plus: [client](#client) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) - :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [process_path_regex](#process_path_regex) + !!! quote "sing-box 1.8.0 中的更改" @@ -99,6 +100,9 @@ icon: material/alert-decagram "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -275,6 +279,16 @@ icon: material/alert-decagram 匹配进程路径。 +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + #### package_name 匹配 Android 应用包名。 diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index e766904b5f..891b127840 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -57,6 +57,9 @@ "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], @@ -160,6 +163,16 @@ Match process name. Match process path. +#### process_path_regex + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using regular expression. + #### package_name Match android package name. diff --git a/option/rule.go b/option/rule.go index 5c69ef00e3..5f15645c9b 100644 --- a/option/rule.go +++ b/option/rule.go @@ -88,6 +88,7 @@ type _DefaultRule struct { PortRange Listable[string] `json:"port_range,omitempty"` ProcessName Listable[string] `json:"process_name,omitempty"` ProcessPath Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` PackageName Listable[string] `json:"package_name,omitempty"` User Listable[string] `json:"user,omitempty"` UserID Listable[int32] `json:"user_id,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index 2afe245ea4..2e03b4252e 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -88,6 +88,7 @@ type _DefaultDNSRule struct { PortRange Listable[string] `json:"port_range,omitempty"` ProcessName Listable[string] `json:"process_name,omitempty"` ProcessPath Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` PackageName Listable[string] `json:"package_name,omitempty"` User Listable[string] `json:"user,omitempty"` UserID Listable[int32] `json:"user_id,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index a83b2c47b6..b6ec113e35 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -144,24 +144,25 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network Listable[string] `json:"network,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - Invert bool `json:"invert,omitempty"` + QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` + Network Listable[string] `json:"network,omitempty"` + Domain Listable[string] `json:"domain,omitempty"` + DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR Listable[string] `json:"ip_cidr,omitempty"` + SourcePort Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange Listable[string] `json:"source_port_range,omitempty"` + Port Listable[uint16] `json:"port,omitempty"` + PortRange Listable[string] `json:"port_range,omitempty"` + ProcessName Listable[string] `json:"process_name,omitempty"` + ProcessPath Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` + PackageName Listable[string] `json:"package_name,omitempty"` + WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` diff --git a/route/rule_default.go b/route/rule_default.go index 4bbc2b6b2e..40b93e5f96 100644 --- a/route/rule_default.go +++ b/route/rule_default.go @@ -179,6 +179,14 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.ProcessPathRegex) > 0 { + item, err := NewProcessPathRegexItem(options.ProcessPathRegex) + if err != nil { + return nil, E.Cause(err, "process_path_regex") + } + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.PackageName) > 0 { item := NewPackageNameItem(options.PackageName) rule.items = append(rule.items, item) diff --git a/route/rule_dns.go b/route/rule_dns.go index 1b79d30b25..616f956aaf 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -183,6 +183,14 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.ProcessPathRegex) > 0 { + item, err := NewProcessPathRegexItem(options.ProcessPathRegex) + if err != nil { + return nil, E.Cause(err, "process_path_regex") + } + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.PackageName) > 0 { item := NewPackageNameItem(options.PackageName) rule.items = append(rule.items, item) diff --git a/route/rule_headless.go b/route/rule_headless.go index d537df572c..23a98c7237 100644 --- a/route/rule_headless.go +++ b/route/rule_headless.go @@ -123,6 +123,14 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.ProcessPathRegex) > 0 { + item, err := NewProcessPathRegexItem(options.ProcessPathRegex) + if err != nil { + return nil, E.Cause(err, "process_path_regex") + } + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.PackageName) > 0 { item := NewPackageNameItem(options.PackageName) rule.items = append(rule.items, item) diff --git a/route/rule_item_process_path_regex.go b/route/rule_item_process_path_regex.go new file mode 100644 index 0000000000..01b2723cc6 --- /dev/null +++ b/route/rule_item_process_path_regex.go @@ -0,0 +1,54 @@ +package route + +import ( + "regexp" + "strings" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*ProcessPathRegexItem)(nil) + +type ProcessPathRegexItem struct { + matchers []*regexp.Regexp + description string +} + +func NewProcessPathRegexItem(expressions []string) (*ProcessPathRegexItem, error) { + matchers := make([]*regexp.Regexp, 0, len(expressions)) + for i, regex := range expressions { + matcher, err := regexp.Compile(regex) + if err != nil { + return nil, E.Cause(err, "parse expression ", i) + } + matchers = append(matchers, matcher) + } + description := "process_path_regex=" + eLen := len(expressions) + if eLen == 1 { + description += expressions[0] + } else if eLen > 3 { + description += F.ToString("[", strings.Join(expressions[:3], " "), "]") + } else { + description += F.ToString("[", strings.Join(expressions, " "), "]") + } + return &ProcessPathRegexItem{matchers, description}, nil +} + +func (r *ProcessPathRegexItem) Match(metadata *adapter.InboundContext) bool { + if metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == "" { + return false + } + for _, matcher := range r.matchers { + if matcher.MatchString(metadata.ProcessInfo.ProcessPath) { + return true + } + } + return false +} + +func (r *ProcessPathRegexItem) String() string { + return r.description +} From 926d6f769eeda74ec6988b4d7037b7ec4b8a59e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 23 Sep 2024 16:51:55 +0800 Subject: [PATCH 0066/1272] Update utls to v1.6.7 --- common/tls/utls_client.go | 12 ++-------- docs/configuration/inbound/trojan.md | 2 +- docs/configuration/route/rule.md | 2 +- docs/configuration/shared/tls.md | 31 +++++++++++++------------- docs/configuration/shared/tls.zh.md | 33 ++++++++++++++-------------- go.mod | 3 +-- go.sum | 6 ++--- 7 files changed, 40 insertions(+), 49 deletions(-) diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index 71ce8a4e0d..6364740f68 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -217,18 +217,10 @@ func init() { func uTLSClientHelloID(name string) (utls.ClientHelloID, error) { switch name { + case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq": + fallthrough case "chrome", "": return utls.HelloChrome_Auto, nil - case "chrome_psk": - return utls.HelloChrome_100_PSK, nil - case "chrome_psk_shuffle": - return utls.HelloChrome_112_PSK_Shuf, nil - case "chrome_padding_psk_shuffle": - return utls.HelloChrome_114_Padding_PSK_Shuf, nil - case "chrome_pq": - return utls.HelloChrome_115_PQ, nil - case "chrome_pq_psk": - return utls.HelloChrome_115_PQ_PSK, nil case "firefox": return utls.HelloFirefox_Auto, nil case "edge": diff --git a/docs/configuration/inbound/trojan.md b/docs/configuration/inbound/trojan.md index bd6c73b311..e277236b06 100644 --- a/docs/configuration/inbound/trojan.md +++ b/docs/configuration/inbound/trojan.md @@ -47,7 +47,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound). #### fallback -!!! quote "" +!!! failure "" There is no evidence that GFW detects and blocks Trojan servers based on HTTP responses, and opening the standard http/s port on the server is a much bigger signature. diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 91b432df1a..b5d17f215b 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -4,7 +4,7 @@ icon: material/alert-decagram !!! quote "Changes in sing-box 1.10.0" - :material-plus: [client](#client) + :material-plus: [client](#client) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) :material-plus: [process_path_regex](#process_path_regex) diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index 799aa0b09d..40343e18ef 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -1,4 +1,8 @@ -!!! quote "Changes in sing-box 1.8.0" +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.10.0" :material-alert-decagram: [utls](#utls) @@ -210,28 +214,25 @@ The path to the server private key, in PEM format. ==Client only== -!!! note "" - - uTLS is poorly maintained and the effect may be unproven, use at your own risk. +!!! failure "" + + There is no evidence that GFW detects and blocks servers based on TLS client fingerprinting, and using an imperfect emulation that has not been security reviewed could pose security risks. uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance. Available fingerprint values: -!!! question "Since sing-box 1.8.0" +!!! warning "Removed since sing-box 1.10.0" + + Some legacy chrome fingerprints have been removed and will fallback to chrome: - :material-plus: chrome_psk - :material-plus: chrome_psk_shuffle - :material-plus: chrome_padding_psk_shuffle - :material-plus: chrome_pq - :material-plus: chrome_pq_psk + :material-close: chrome_psk + :material-close: chrome_psk_shuffle + :material-close: chrome_padding_psk_shuffle + :material-close: chrome_pq + :material-close: chrome_pq_psk * chrome -* chrome_psk -* chrome_psk_shuffle -* chrome_padding_psk_shuffle -* chrome_pq -* chrome_pq_psk * firefox * edge * safari diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 68de98459f..b254e08389 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -1,4 +1,8 @@ -!!! quote "sing-box 1.8.0 中的更改" +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.10.0 中的更改" :material-alert-decagram: [utls](#utls) @@ -44,8 +48,8 @@ "handshake": { "server": "google.com", "server_port": 443, - - ... // 拨号字段 + ... + // 拨号字段 }, "private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", "short_id": [ @@ -202,28 +206,25 @@ TLS 版本值: ==仅客户端== -!!! note "" +!!! failure "" - uTLS 维护不善且其效果可能未经证实,使用风险自负。 + 没有证据表明 GFW 根据 TLS 客户端指纹检测并阻止服务器,并且,使用一个未经安全审查的不完美模拟可能带来安全隐患。 uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻力。 可用的指纹值: -!!! question "自 sing-box 1.8.0 起" +!!! warning "已在 sing-box 1.10.0 移除" + + 一些旧 chrome 指纹已被删除,并将会退到 chrome: - :material-plus: chrome_psk - :material-plus: chrome_psk_shuffle - :material-plus: chrome_padding_psk_shuffle - :material-plus: chrome_pq - :material-plus: chrome_pq_psk + :material-close: chrome_psk + :material-close: chrome_psk_shuffle + :material-close: chrome_padding_psk_shuffle + :material-close: chrome_pq + :material-close: chrome_pq_psk * chrome -* chrome_psk -* chrome_psk_shuffle -* chrome_padding_psk_shuffle -* chrome_pq -* chrome_pq_psk * firefox * edge * safari diff --git a/go.mod b/go.mod index 9bfdbebffd..24a6181f09 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/sagernet/sing-tun v0.4.0-beta.16 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 - github.com/sagernet/utls v1.5.4 + github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.8.0 @@ -61,7 +61,6 @@ require ( github.com/andybalholm/brotli v1.0.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect diff --git a/go.sum b/go.sum index 21cd7047ec..19d8f9184c 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= -github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= @@ -137,8 +135,8 @@ github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEY github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= 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/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= -github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= +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.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= From 2f4d2d97f97944bfc3726e9a17e38ae1d92277a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 8 Oct 2024 20:59:12 +0800 Subject: [PATCH 0067/1272] auto-redirect: Let fw4 take precedence over prerouting --- go.mod | 4 ++-- go.sum | 8 ++++---- route/router.go | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 24a6181f09..ab8c91f5d8 100644 --- a/go.mod +++ b/go.mod @@ -27,14 +27,14 @@ require ( github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f github.com/sagernet/quic-go v0.47.0-beta.2 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.5.0-beta.2 + github.com/sagernet/sing v0.5.0-beta.2.0.20240922141512-c63546470b53 github.com/sagernet/sing-dns v0.3.0-beta.14 github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-quic v0.3.0-beta.3 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.4.0-beta.16 + github.com/sagernet/sing-tun v0.4.0-beta.16.0.20241011135911-06d32a6e4382 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 19d8f9184c..75c006a2b4 100644 --- a/go.sum +++ b/go.sum @@ -115,8 +115,8 @@ github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYi 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.5.0-beta.2 h1:V12EpwtsgYo5OLGjAiGoJobDJZeUsKv0b5y+yGAM6W0= -github.com/sagernet/sing v0.5.0-beta.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.5.0-beta.2.0.20240922141512-c63546470b53 h1:skd2mM7USFsPTmyaPnnJJVrGWIMGv8PdzVyoKQPdqpU= +github.com/sagernet/sing v0.5.0-beta.2.0.20240922141512-c63546470b53/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.3.0-beta.14 h1:/s+fJzYKsvLaNDt/2rjpsrDcN8wmCO2JbX6OFrl8Nww= github.com/sagernet/sing-dns v0.3.0-beta.14/go.mod h1:rscgSr5ixOPk8XM9ZMLuMXCyldEQ1nLvdl0nfv+lp00= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= @@ -129,8 +129,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.4.0-beta.16 h1:05VdL5BZiKLQsDNrpdXMseSO1NwPfl9Y4o76PqAd9sY= -github.com/sagernet/sing-tun v0.4.0-beta.16/go.mod h1:81JwnnYw8X9W9XvmZetSTTiPgIE3SbAbnc+EHKwPJ5U= +github.com/sagernet/sing-tun v0.4.0-beta.16.0.20241011135911-06d32a6e4382 h1:DGwp7OxPaRrmUu6ENV5XFnEFCY36ZkI8lzN5Np2s538= +github.com/sagernet/sing-tun v0.4.0-beta.16.0.20241011135911-06d32a6e4382/go.mod h1:kh4vZpuCOX8aGnEO0wY9KvC6i+Wb3/uGeb/TkB99o6U= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= diff --git a/route/router.go b/route/router.go index aa121cb44e..c8fe94be5b 100644 --- a/route/router.go +++ b/route/router.go @@ -338,6 +338,7 @@ func NewRouter( _ = router.interfaceFinder.Update() }) interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ + InterfaceFinder: router.interfaceFinder, OverrideAndroidVPN: options.OverrideAndroidVPN, UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(), }) From e55723d84db1d372a8edbbd47784f6536ceafda5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:49:40 +0000 Subject: [PATCH 0068/1272] [dependencies] Update actions/checkout digest to eef6144 --- .github/workflows/debug.yml | 10 +++++----- .github/workflows/docker.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/linux.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml index 2bca96e050..804eced92b 100644 --- a/.github/workflows/debug.yml +++ b/.github/workflows/debug.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - name: Setup Go @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - name: Setup Go @@ -57,7 +57,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - name: Setup Go @@ -77,7 +77,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - name: Setup Go @@ -207,7 +207,7 @@ jobs: TAGS: with_clash_api,with_quic steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - name: Setup Go diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 61260b0fb5..bcf210ab1a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,7 @@ jobs: echo "ref=$ref" echo "ref=$ref" >> $GITHUB_OUTPUT - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: ref: ${{ steps.ref.outputs.ref }} fetch-depth: 0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 08fb6c54d6..ba6a544dd9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - name: Setup Go diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index bd2f1023f2..521477ed6f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - name: Setup Go From 82ab68b542d558ae9f0553b888a3703d55f8c68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 11 Oct 2024 21:46:42 +0800 Subject: [PATCH 0069/1272] build: Fix find NDK --- cmd/internal/build_shared/sdk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index b6c1ec9deb..a664abee56 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -86,7 +86,7 @@ func findNDK() bool { }) for _, versionName := range versionNames { currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName) - if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) { + if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) { androidNDKPath = currentNDKPath log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion) return true From 3ede29fb6d8c077096fa9e7685201d6968943263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 12 Oct 2024 08:57:24 +0800 Subject: [PATCH 0070/1272] documentation: Improve theme --- mkdocs.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 56bdf96a47..d5cdcee470 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,18 +11,22 @@ theme: logo: assets/icon.svg favicon: assets/icon.svg palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/link + name: Switch to light mode - media: "(prefers-color-scheme: light)" scheme: default primary: white toggle: - icon: material/brightness-7 + icon: material/toggle-switch name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate primary: black toggle: - icon: material/brightness-4 - name: Switch to light mode + icon: material/toggle-switch-off + name: Switch to system preference features: # - navigation.instant - navigation.tracking From 137832ff3ef3ab4ba5fc7d50c227253b77a0d68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 13 Oct 2024 13:20:49 +0800 Subject: [PATCH 0071/1272] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 355 ++++++++++++++++++++++++++- docs/configuration/inbound/tun.md | 4 +- docs/configuration/inbound/tun.zh.md | 4 +- docs/deprecated.md | 5 + docs/deprecated.zh.md | 7 +- go.mod | 12 +- go.sum | 24 +- 9 files changed, 387 insertions(+), 28 deletions(-) diff --git a/clients/android b/clients/android index 2aa661d507..2d0d2341c5 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 2aa661d5079fc3ad6857e0100fdb9418771f95b5 +Subproject commit 2d0d2341c56ebb4ef1cfddc9fe0c61a3cc634e66 diff --git a/clients/apple b/clients/apple index c223f50ffb..c7d9b49de7 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit c223f50ffb8b81d4519b43c38c8cdb96b9ac3d5c +Subproject commit c7d9b49de799fe31e256bcdb68f80c50e3aee41d diff --git a/docs/changelog.md b/docs/changelog.md index f835a68bd2..b346851bc3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,10 +2,128 @@ icon: material/alert-decagram --- +### 1.10.0 + +* Fixes and improvements + +Important changes since 1.9: + +* Introducing auto-redirect **1** +* Add AdGuard DNS Filter support **2** +* TUN address fields are merged **3** +* Add custom options for `auto-route` and `auto-redirect` **4** +* Drop support for go1.18 and go1.19 **5** +* Add tailing comma support in JSON configuration +* Improve sniffers **6** +* Add new `inline` rule-set type **7** +* Add access control options for Clash API **8** +* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **9** +* Add auto reload support for local rule-set +* Update fsnotify usages **10** +* Add IP address support for `rule-set match` command +* Add `rule-set decompile` command +* Add `process_path_regex` rule item +* Update uTLS to v1.6.7 **11** +* Optimize memory usages of rule-sets **12** + +**1**: + +The new auto-redirect feature allows TUN to automatically +configure connection redirection to improve proxy performance. + +When auto-redirect is enabled, new route address set options will allow you to +automatically configure destination IP CIDR rules from a specified rule set to the firewall. + +Specified or unspecified destinations will bypass the sing-box routes to get better performance +(for example, keep hardware offloading of direct traffics on the router). + +See [TUN](/configuration/inbound/tun). + +**2**: + +The new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home. + +See [AdGuard DNS Filter](/configuration/rule-set/adguard/). + +**3**: + +See [Migration](/migration/#tun-address-fields-are-merged). + +**4**: + +See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index), +[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index), +[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and +[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark). + +**5**: + +Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile. + +**6**: + +BitTorrent, DTLS, RDP, SSH sniffers are added. + +Now the QUIC sniffer can correctly extract the server name from Chromium requests and +can identify common QUIC clients, including +Chromium, Safari, Firefox, quic-go (including uquic disguised as Chrome). + +**7**: + +The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type) +allows you to write headless rules directly without creating a rule-set file. + +**8**: + +With the new access control options, not only can you allow Clash dashboards +to access the Clash API on your local network, +you can also manually limit the websites that can access the API instead of allowing everyone. + +See [Clash API](/configuration/experimental/clash-api/). + +**9**: + +See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty). + +**10**: + +sing-box now uses fsnotify correctly and will not cancel watching +if the target file is deleted or recreated via rename (e.g. `mv`). + +This affects all path options that support reload, including +`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`. + +**11**: + +Some legacy chrome fingerprints have been removed and will fallback to chrome, +see [utls](/configuration/shared/tls#utls). + +**12**: + +See [Source Format](/configuration/rule-set/source-format/#version). + ### 1.9.7 * Fixes and improvements +#### 1.10.0-beta.11 + +* Update uTLS to v1.6.7 **1** + +**1**: + +Some legacy chrome fingerprints have been removed and will fallback to chrome, +see [utls](/configuration/shared/tls#utls). + +#### 1.10.0-beta.10 + +* Add `process_path_regex` rule item +* Fixes and improvements + +_The macOS standalone versions of sing-box (>=1.9.5/<1.10.0-beta.11) now silently fail and require manual granting of +the **Full Disk Access** permission to system extension to start, probably due to Apple's changed security policy. We +will prompt users about this in feature versions._ + ### 1.9.6 * Fixes and improvements @@ -27,11 +145,41 @@ icon: material/alert-decagram See [Migration](/migration/#bundle-identifier-updates-in-apple-platform-clients). -We are still working on getting all sing-box apps back on the App Store. - -This work is expected to be completed within a week +We are still working on getting all sing-box apps back on the App Store, which should be completed within a week (SFI on the App Store and others on TestFlight are already available). +#### 1.10.0-beta.8 + +* Fixes and improvements + +_With the help of a netizen, we are in the process of getting sing-box apps back on the App Store, which should be +completed within a month (TestFlight is already available)._ + +#### 1.10.0-beta.7 + +* Update quic-go to v0.47.0 +* Fixes and improvements + +#### 1.10.0-beta.6 + +* Add RDP sniffer +* Fixes and improvements + +#### 1.10.0-beta.5 + +* Add PNA support for [Clash API](/configuration/experimental/clash-api/) +* Fixes and improvements + +#### 1.10.0-beta.3 + +* Add SSH sniffer +* Fixes and improvements + +#### 1.10.0-beta.2 + +* Build with go1.23 +* Fixes and improvements + ### 1.9.4 * Update quic-go to v0.46.0 @@ -52,18 +200,219 @@ sing-box apps on Apple platforms are temporarily unavailable for download or upd If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org)._ +#### 1.10.0-alpha.29 + +* Update quic-go to v0.46.0 +* Fixes and improvements + +#### 1.10.0-alpha.25 + +* Add AdGuard DNS Filter support **1** + +**1**: + +The new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home. + +See [AdGuard DNS Filter](/configuration/rule-set/adguard/). + +#### 1.10.0-alpha.23 + +* Add Chromium support for QUIC sniffer +* Add client type detect support for QUIC sniffer **1** +* Fixes and improvements + +**1**: + +Now the QUIC sniffer can correctly extract the server name from Chromium requests and +can identify common QUIC clients, including +Chromium, Safari, Firefox, quic-go (including uquic disguised as Chrome). + +See [Protocol Sniff](/configuration/route/sniff/) and [Route Rule](/configuration/route/rule/#client). + +#### 1.10.0-alpha.22 + +* Optimize memory usages of rule-sets **1** +* Fixes and improvements + +**1**: + +See [Source Format](/configuration/rule-set/source-format/#version). + +#### 1.10.0-alpha.20 + +* Add DTLS sniffer +* Fixes and improvements + +#### 1.10.0-alpha.19 + +* Add `rule-set decompile` command +* Add IP address support for `rule-set match` command +* Fixes and improvements + +#### 1.10.0-alpha.18 + +* Add new `inline` rule-set type **1** +* Add auto reload support for local rule-set +* Update fsnotify usages **2** +* Fixes and improvements + +**1**: + +The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type) +allows you to write headless rules directly without creating a rule-set file. + +**2**: + +sing-box now uses fsnotify correctly and will not cancel watching +if the target file is deleted or recreated via rename (e.g. `mv`). + +This affects all path options that support reload, including +`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`. + +#### 1.10.0-alpha.17 + +* Some chaotic changes **1** +* `rule_set_ipcidr_match_source` rule items are renamed **2** +* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **3** +* Update quic-go to v0.45.1 +* Fixes and improvements + +**1**: + +Something may be broken, please actively report problems with this version. + +**2**: + +`rule_set_ipcidr_match_source` route and DNS rule items are renamed to +`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0. + +**3**: + +See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty). + +#### 1.10.0-alpha.16 + +* Add custom options for `auto-route` and `auto-redirect` **1** +* Fixes and improvements + +**1**: + +See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index), +[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index), +[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and +[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark). + +#### 1.10.0-alpha.13 + +* TUN address fields are merged **1** +* Add route address set support for auto-redirect **2** + +**1**: + +See [Migration](/migration/#tun-address-fields-are-merged). + +**2**: + +The new feature will allow you to configure the destination IP CIDR rules +in the specified rule-sets to the firewall automatically. + +Specified or unspecified destinations will bypass the sing-box routes to get better performance +(for example, keep hardware offloading of direct traffics on the router). + +See [route_address_set](/configuration/inbound/tun/#route_address_set) +and [route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set). + +#### 1.10.0-alpha.12 + +* Fix auto-redirect not configuring nftables forward chain correctly +* Fixes and improvements + ### 1.9.3 * Fixes and improvements +#### 1.10.0-alpha.10 + +* Fixes and improvements + ### 1.9.2 * Fixes and improvements +#### 1.10.0-alpha.8 + +* Drop support for go1.18 and go1.19 **1** +* Update quic-go to v0.45.0 +* Update Hysteria2 BBR congestion control +* Fixes and improvements + +**1**: + +Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile. + ### 1.9.1 * Fixes and improvements +#### 1.10.0-alpha.7 + +* Fixes and improvements + +#### 1.10.0-alpha.5 + +* Improve auto-redirect **1** + +**1**: + +nftables support and DNS hijacking has been added. + +Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers **without intervention**. + +#### 1.10.0-alpha.4 + +* Fix auto-redirect **1** +* Improve auto-route on linux **2** + +**1**: + +Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers. + +**2**: + +Tun inbounds with `auto_route` and `strict_route` now works as expected on routers and servers, +but the usages of [exclude_interface](/configuration/inbound/tun/#exclude_interface) need to be updated. + +#### 1.10.0-alpha.2 + +* Move auto-redirect to Tun **1** +* Fixes and improvements + +**1**: + +Linux support are added. + +See [Tun](/configuration/inbound/tun/#auto_redirect). + +#### 1.10.0-alpha.1 + +* Add tailing comma support in JSON configuration +* Add simple auto-redirect for Android **1** +* Add BitTorrent sniffer **2** + +**1**: + +It allows you to use redirect inbound in the sing-box Android client +and automatically configures IPv4 TCP redirection via su. + +This may alleviate the symptoms of some OCD patients who think that +redirect can effectively save power compared to the system HTTP Proxy. + +See [Redirect](/configuration/inbound/redirect/). + +**2**: + +See [Protocol Sniff](/configuration/route/sniff/). + ### 1.9.0 * Fixes and improvements diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 812b20d163..420f77a900 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -232,12 +232,12 @@ Automatically configure iptables/nftables to redirect connections. *In Android*: -Only local connections are forwarded. To share your VPN connection over hotspot or repeater, +Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater, use [VPNHotspot](https://github.com/Mygod/VPNHotspot). *In Linux*: -`auto_route` with `auto_redirect` now works as expected on routers **without intervention**. +`auto_route` with `auto_redirect` works as expected on routers **without intervention**. #### auto_redirect_input_mark diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 88e02fe767..e7f69483ae 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -232,7 +232,7 @@ tun 接口的 IPv6 前缀。 仅支持 Linux,且需要 `auto_route` 已启用。 -自动配置 iptables 以重定向 TCP 连接。 +自动配置 iptables/nftables 以重定向连接。 *在 Android 中*: @@ -240,7 +240,7 @@ tun 接口的 IPv6 前缀。 *在 Linux 中*: -带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。 +带有 `auto_redirect `的 `auto_route` 可以在路由器上按预期工作,**无需干预**。 #### auto_redirect_input_mark diff --git a/docs/deprecated.md b/docs/deprecated.md index eb0c1925c8..9cb717cf28 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -14,6 +14,11 @@ icon: material/delete-alert Old fields are deprecated and will be removed in sing-box 1.11.0. +#### Match source rule items are renamed + +`rule_set_ipcidr_match_source` route and DNS rule items are renamed to +`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0. + #### Drop support for go1.18 and go1.19 Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile. diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 6815e9fc8b..5bd2ea4585 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -6,13 +6,18 @@ icon: material/delete-alert ## 1.10.0 +#### Match source 规则项已重命名 + +`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为 +`rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。 + #### TUN 地址字段已合并 `inet4_address` 和 `inet6_address` 已合并为 `address`, `inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`, `inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。 -旧字段已废弃,且将在 sing-box 1.11.0 中移除。 +旧字段已废弃,且将在 sing-box 1.11.0 中被移除。 #### 移除对 go1.18 和 go1.19 的支持 diff --git a/go.mod b/go.mod index ab8c91f5d8..ab3048ce85 100644 --- a/go.mod +++ b/go.mod @@ -9,14 +9,14 @@ require ( github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/render v1.0.3 - github.com/gofrs/uuid/v5 v5.2.0 + github.com/gofrs/uuid/v5 v5.3.0 github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/libdns/alidns v1.0.3 github.com/libdns/cloudflare v0.1.1 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d github.com/mholt/acmez v1.2.0 - github.com/miekg/dns v1.1.61 + github.com/miekg/dns v1.1.62 github.com/ooni/go-libtor v1.1.8 github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a @@ -27,14 +27,14 @@ require ( github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f github.com/sagernet/quic-go v0.47.0-beta.2 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.5.0-beta.2.0.20240922141512-c63546470b53 - github.com/sagernet/sing-dns v0.3.0-beta.14 + github.com/sagernet/sing v0.5.0-rc.2 + github.com/sagernet/sing-dns v0.3.0-rc.2 github.com/sagernet/sing-mux v0.2.0 - github.com/sagernet/sing-quic v0.3.0-beta.3 + github.com/sagernet/sing-quic v0.3.0-rc.1 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.4.0-beta.16.0.20241011135911-06d32a6e4382 + github.com/sagernet/sing-tun v0.4.0-rc.3 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 75c006a2b4..31b29b6d6b 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,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/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= -github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= +github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= @@ -74,8 +74,8 @@ github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNv github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= -github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= @@ -115,22 +115,22 @@ github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYi 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.5.0-beta.2.0.20240922141512-c63546470b53 h1:skd2mM7USFsPTmyaPnnJJVrGWIMGv8PdzVyoKQPdqpU= -github.com/sagernet/sing v0.5.0-beta.2.0.20240922141512-c63546470b53/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.3.0-beta.14 h1:/s+fJzYKsvLaNDt/2rjpsrDcN8wmCO2JbX6OFrl8Nww= -github.com/sagernet/sing-dns v0.3.0-beta.14/go.mod h1:rscgSr5ixOPk8XM9ZMLuMXCyldEQ1nLvdl0nfv+lp00= +github.com/sagernet/sing v0.5.0-rc.2 h1:tIrs6pRbjJWvI0ITRSg47P1wosY+iSuHpw9t5/hBx+Q= +github.com/sagernet/sing v0.5.0-rc.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-dns v0.3.0-rc.2 h1:z1yROBxd/6wik5h53Sz5df1DSmbPTaOu/Z0wAmyXGoQ= +github.com/sagernet/sing-dns v0.3.0-rc.2/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= -github.com/sagernet/sing-quic v0.3.0-beta.3 h1:8S98VXZxtSiOqVCFbCNbMEvKDPhOF/VNBYMjVC3xMhw= -github.com/sagernet/sing-quic v0.3.0-beta.3/go.mod h1:rFPUlYnSj1Bx9gFSghjCqrCzfGvpjhkisOiTKpjq5vQ= +github.com/sagernet/sing-quic v0.3.0-rc.1 h1:SlzL1yfEAKJyRduub8vzOVtbyTLAX7RZEEBZxO5utts= +github.com/sagernet/sing-quic v0.3.0-rc.1/go.mod h1:uX+aUHA0fgIN6U3WViseDpSdTQriViZ7qz0Wbsf1mNQ= 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.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.4.0-beta.16.0.20241011135911-06d32a6e4382 h1:DGwp7OxPaRrmUu6ENV5XFnEFCY36ZkI8lzN5Np2s538= -github.com/sagernet/sing-tun v0.4.0-beta.16.0.20241011135911-06d32a6e4382/go.mod h1:kh4vZpuCOX8aGnEO0wY9KvC6i+Wb3/uGeb/TkB99o6U= +github.com/sagernet/sing-tun v0.4.0-rc.3 h1:W/Odc87dGXkRhwRo2jhsKYHypNbajIcsGlIzDatmMKA= +github.com/sagernet/sing-tun v0.4.0-rc.3/go.mod h1:+lQdWhqD4atzrCgRhoyrxBCg1OBru/hAv2BT3kdgmGM= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= From e93d0408be93513adedea4c0a493643e9f2f7d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 16 Oct 2024 12:03:16 +0800 Subject: [PATCH 0072/1272] documentation: Fix release notes --- docs/changelog.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index b346851bc3..daa525714d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,8 +4,6 @@ icon: material/alert-decagram ### 1.10.0 -* Fixes and improvements - Important changes since 1.9: * Introducing auto-redirect **1** From 956ee361df8d4f5b4327838cfe4b69e822cece20 Mon Sep 17 00:00:00 2001 From: TsingShui <73029250+TsingShui@users.noreply.github.com> Date: Wed, 16 Oct 2024 20:40:03 +0800 Subject: [PATCH 0073/1272] Fix corrected improper use of `reader` and `bReader` Co-authored-by: x_123 --- experimental/libbox/profile_import.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/experimental/libbox/profile_import.go b/experimental/libbox/profile_import.go index 258c175a9f..17671e560f 100644 --- a/experimental/libbox/profile_import.go +++ b/experimental/libbox/profile_import.go @@ -218,7 +218,7 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) { if err != nil { return nil, err } - err = binary.Read(reader, binary.BigEndian, &content.Type) + err = binary.Read(bReader, binary.BigEndian, &content.Type) if err != nil { return nil, err } @@ -233,17 +233,17 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) { } } if content.Type == ProfileTypeRemote || (version == 0 && content.Type != ProfileTypeLocal) { - err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate) + err = binary.Read(bReader, binary.BigEndian, &content.AutoUpdate) if err != nil { return nil, err } if version >= 1 { - err = binary.Read(reader, binary.BigEndian, &content.AutoUpdateInterval) + err = binary.Read(bReader, binary.BigEndian, &content.AutoUpdateInterval) if err != nil { return nil, err } } - err = binary.Read(reader, binary.BigEndian, &content.LastUpdated) + err = binary.Read(bReader, binary.BigEndian, &content.LastUpdated) if err != nil { return nil, err } From 08718112ae0d9259e45d514f9154ceb4446a0b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 14 Oct 2024 22:11:06 +0800 Subject: [PATCH 0074/1272] Retry system forwarder listen --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ab3048ce85..5d10b44d15 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.4.0-rc.3 + github.com/sagernet/sing-tun v0.4.0-rc.4 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 31b29b6d6b..7afac346c4 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.4.0-rc.3 h1:W/Odc87dGXkRhwRo2jhsKYHypNbajIcsGlIzDatmMKA= -github.com/sagernet/sing-tun v0.4.0-rc.3/go.mod h1:+lQdWhqD4atzrCgRhoyrxBCg1OBru/hAv2BT3kdgmGM= +github.com/sagernet/sing-tun v0.4.0-rc.4 h1:EFz+UjLZTm+YS3ob1vpqjOga67wyvnxWcbQKfe5wE3Y= +github.com/sagernet/sing-tun v0.4.0-rc.4/go.mod h1:+lQdWhqD4atzrCgRhoyrxBCg1OBru/hAv2BT3kdgmGM= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= From b80ec55ba0aafc60774e19c409e55ced50ddaab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 16 Oct 2024 20:48:05 +0800 Subject: [PATCH 0075/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 2d0d2341c5..45a1f5f0aa 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 2d0d2341c56ebb4ef1cfddc9fe0c61a3cc634e66 +Subproject commit 45a1f5f0aa2b5cf7cb74720a2fa407fdaa591bbf diff --git a/docs/changelog.md b/docs/changelog.md index daa525714d..e70ec08b96 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +### 1.10.1 + +* Fixes and improvements + ### 1.10.0 Important changes since 1.9: From 6ed9a0639407c5820103617ec2fad9aa8b8a9ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 19 Oct 2024 21:07:43 +0800 Subject: [PATCH 0076/1272] Fix rule-set format --- option/rule_set.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/option/rule_set.go b/option/rule_set.go index b6ec113e35..d4368de38a 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -48,17 +48,6 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { if r.Tag == "" { return E.New("missing tag") } - if r.Type != C.RuleSetTypeInline { - switch r.Format { - case "": - return E.New("missing format") - case C.RuleSetFormatSource, C.RuleSetFormatBinary: - default: - return E.New("unknown rule-set format: " + r.Format) - } - } else { - r.Format = "" - } var v any switch r.Type { case "", C.RuleSetTypeInline: @@ -71,6 +60,17 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule-set type: " + r.Type) } + if r.Type != C.RuleSetTypeInline { + switch r.Format { + case "": + return E.New("missing format") + case C.RuleSetFormatSource, C.RuleSetFormatBinary: + default: + return E.New("unknown rule-set format: " + r.Format) + } + } else { + r.Format = "" + } err = UnmarshallExcluded(bytes, (*_RuleSet)(r), v) if err != nil { return err From 327bb35ddd1558f9b3d6a90f57bbd6a09bbb83cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Oct 2024 22:24:19 +0800 Subject: [PATCH 0077/1272] Rename HTTP start context --- adapter/router.go | 44 +++++++++++++++++++++++++++++++++++---- route/router.go | 9 +++++--- route/rule_set.go | 45 ---------------------------------------- route/rule_set_local.go | 3 +-- route/rule_set_remote.go | 23 ++++++++++---------- 5 files changed, 58 insertions(+), 66 deletions(-) diff --git a/adapter/router.go b/adapter/router.go index 619c1110cb..5dc4de538a 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -2,13 +2,17 @@ package adapter import ( "context" + "net" "net/http" "net/netip" + "sync" "github.com/sagernet/sing-box/common/geoip" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-dns" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" @@ -98,7 +102,7 @@ type DNSRule interface { type RuleSet interface { Name() string - StartContext(ctx context.Context, startContext RuleSetStartContext) error + StartContext(ctx context.Context, startContext *HTTPStartContext) error PostStart() error Metadata() RuleSetMetadata ExtractIPSet() []*netipx.IPSet @@ -118,10 +122,42 @@ type RuleSetMetadata struct { ContainsWIFIRule bool ContainsIPCIDRRule bool } +type HTTPStartContext struct { + access sync.Mutex + httpClientCache map[string]*http.Client +} + +func NewHTTPStartContext() *HTTPStartContext { + return &HTTPStartContext{ + httpClientCache: make(map[string]*http.Client), + } +} + +func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client { + c.access.Lock() + defer c.access.Unlock() + if httpClient, loaded := c.httpClientCache[detour]; loaded { + return httpClient + } + httpClient := &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + TLSHandshakeTimeout: C.TCPTimeout, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + c.httpClientCache[detour] = httpClient + return httpClient +} -type RuleSetStartContext interface { - HTTPClient(detour string, dialer N.Dialer) *http.Client - Close() +func (c *HTTPStartContext) Close() { + c.access.Lock() + defer c.access.Unlock() + for _, client := range c.httpClientCache { + client.CloseIdleConnections() + } } type InterfaceUpdateListener interface { diff --git a/route/router.go b/route/router.go index c8fe94be5b..aac5f603b8 100644 --- a/route/router.go +++ b/route/router.go @@ -659,14 +659,15 @@ func (r *Router) Close() error { func (r *Router) PostStart() error { monitor := taskmonitor.New(r.logger, C.StopTimeout) + var cacheContext *adapter.HTTPStartContext if len(r.ruleSets) > 0 { monitor.Start("initialize rule-set") - ruleSetStartContext := NewRuleSetStartContext() + cacheContext = adapter.NewHTTPStartContext() var ruleSetStartGroup task.Group for i, ruleSet := range r.ruleSets { ruleSetInPlace := ruleSet ruleSetStartGroup.Append0(func(ctx context.Context) error { - err := ruleSetInPlace.StartContext(ctx, ruleSetStartContext) + err := ruleSetInPlace.StartContext(ctx, cacheContext) if err != nil { return E.Cause(err, "initialize rule-set[", i, "]") } @@ -680,7 +681,9 @@ func (r *Router) PostStart() error { if err != nil { return err } - ruleSetStartContext.Close() + } + if cacheContext != nil { + cacheContext.Close() } needFindProcess := r.needFindProcess needWIFIState := r.needWIFIState diff --git a/route/rule_set.go b/route/rule_set.go index 39d51e6f54..a2c6d0c192 100644 --- a/route/rule_set.go +++ b/route/rule_set.go @@ -2,9 +2,6 @@ package route import ( "context" - "net" - "net/http" - "sync" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" @@ -12,8 +9,6 @@ import ( "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" "go4.org/netipx" ) @@ -46,43 +41,3 @@ func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet { panic("unexpected rule type") } } - -var _ adapter.RuleSetStartContext = (*RuleSetStartContext)(nil) - -type RuleSetStartContext struct { - access sync.Mutex - httpClientCache map[string]*http.Client -} - -func NewRuleSetStartContext() *RuleSetStartContext { - return &RuleSetStartContext{ - httpClientCache: make(map[string]*http.Client), - } -} - -func (c *RuleSetStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client { - c.access.Lock() - defer c.access.Unlock() - if httpClient, loaded := c.httpClientCache[detour]; loaded { - return httpClient - } - httpClient := &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: C.TCPTimeout, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - c.httpClientCache[detour] = httpClient - return httpClient -} - -func (c *RuleSetStartContext) Close() { - c.access.Lock() - defer c.access.Unlock() - for _, client := range c.httpClientCache { - client.CloseIdleConnections() - } -} diff --git a/route/rule_set_local.go b/route/rule_set_local.go index 893842d5cc..4485fbad41 100644 --- a/route/rule_set_local.go +++ b/route/rule_set_local.go @@ -58,7 +58,6 @@ func NewLocalRuleSet(ctx context.Context, router adapter.Router, logger logger.L } } if options.Type == C.RuleSetTypeLocal { - var watcher *fswatch.Watcher filePath, _ := filepath.Abs(options.LocalOptions.Path) watcher, err := fswatch.NewWatcher(fswatch.Options{ Path: []string{filePath}, @@ -85,7 +84,7 @@ func (s *LocalRuleSet) String() string { return strings.Join(F.MapToString(s.rules), " ") } -func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error { +func (s *LocalRuleSet) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error { if s.watcher != nil { err := s.watcher.Start() if err != nil { diff --git a/route/rule_set_remote.go b/route/rule_set_remote.go index 03662ee42c..115416092d 100644 --- a/route/rule_set_remote.go +++ b/route/rule_set_remote.go @@ -45,6 +45,7 @@ type RemoteRuleSet struct { lastUpdated time.Time lastEtag string updateTicker *time.Ticker + cacheFile adapter.CacheFile pauseManager pause.Manager callbackAccess sync.Mutex callbacks list.List[adapter.RuleSetUpdateCallback] @@ -78,7 +79,8 @@ func (s *RemoteRuleSet) String() string { return strings.Join(F.MapToString(s.rules), " ") } -func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error { +func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error { + s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx) var dialer N.Dialer if s.options.RemoteOptions.DownloadDetour != "" { outbound, loaded := s.router.Outbound(s.options.RemoteOptions.DownloadDetour) @@ -94,9 +96,8 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.R dialer = outbound } s.dialer = dialer - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - if savedSet := cacheFile.LoadRuleSet(s.options.Tag); savedSet != nil { + if s.cacheFile != nil { + if savedSet := s.cacheFile.LoadRuleSet(s.options.Tag); savedSet != nil { err := s.loadBytes(savedSet.Content) if err != nil { return E.Cause(err, "restore cached rule-set") @@ -226,7 +227,7 @@ func (s *RemoteRuleSet) loopUpdate() { } } -func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext adapter.RuleSetStartContext) error { +func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTTPStartContext) error { s.logger.Debug("updating rule-set ", s.options.Tag, " from URL: ", s.options.RemoteOptions.URL) var httpClient *http.Client if startContext != nil { @@ -257,12 +258,11 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext adapter.Rule case http.StatusOK: case http.StatusNotModified: s.lastUpdated = time.Now() - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - savedRuleSet := cacheFile.LoadRuleSet(s.options.Tag) + if s.cacheFile != nil { + savedRuleSet := s.cacheFile.LoadRuleSet(s.options.Tag) if savedRuleSet != nil { savedRuleSet.LastUpdated = s.lastUpdated - err = cacheFile.SaveRuleSet(s.options.Tag, savedRuleSet) + err = s.cacheFile.SaveRuleSet(s.options.Tag, savedRuleSet) if err != nil { s.logger.Error("save rule-set updated time: ", err) return nil @@ -290,9 +290,8 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext adapter.Rule s.lastEtag = eTagHeader } s.lastUpdated = time.Now() - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - err = cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{ + if s.cacheFile != nil { + err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{ LastUpdated: s.lastUpdated, Content: content, LastEtag: s.lastEtag, From 1a6047a61b41e7184a81f3292462ed38b79312f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 27 Oct 2024 07:45:15 +0800 Subject: [PATCH 0078/1272] Fix metadata context --- adapter/inbound.go | 9 --------- outbound/builder.go | 5 ----- outbound/direct.go | 4 ++-- outbound/http.go | 2 +- outbound/shadowsocks.go | 8 ++++---- outbound/shadowtls.go | 2 +- outbound/socks.go | 4 ++-- outbound/trojan.go | 2 +- outbound/vless.go | 4 ++-- outbound/vmess.go | 4 ++-- 10 files changed, 15 insertions(+), 29 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index ffc4c56454..82909d0112 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -91,15 +91,6 @@ func ContextFrom(ctx context.Context) *InboundContext { return metadata.(*InboundContext) } -func AppendContext(ctx context.Context) (context.Context, *InboundContext) { - metadata := ContextFrom(ctx) - if metadata != nil { - return ctx, metadata - } - metadata = new(InboundContext) - return WithContext(ctx, metadata), metadata -} - func ExtendContext(ctx context.Context) (context.Context, *InboundContext) { var newMetadata InboundContext if metadata := ContextFrom(ctx); metadata != nil { diff --git a/outbound/builder.go b/outbound/builder.go index e4d6a80e06..b99b49b7b1 100644 --- a/outbound/builder.go +++ b/outbound/builder.go @@ -11,11 +11,6 @@ import ( ) func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Outbound) (adapter.Outbound, error) { - var metadata *adapter.InboundContext - if tag != "" { - ctx, metadata = adapter.AppendContext(ctx) - metadata.Outbound = tag - } if options.Type == "" { return nil, E.New("missing outbound type") } diff --git a/outbound/direct.go b/outbound/direct.go index 11f650e4d9..c873941c11 100644 --- a/outbound/direct.go +++ b/outbound/direct.go @@ -70,7 +70,7 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti } func (h *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination switch h.overrideOption { @@ -98,7 +98,7 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M. } func (h *Direct) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination switch h.overrideOption { diff --git a/outbound/http.go b/outbound/http.go index cfc03216fa..cbcbce075a 100644 --- a/outbound/http.go +++ b/outbound/http.go @@ -54,7 +54,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge } func (h *HTTP) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination h.logger.InfoContext(ctx, "outbound connection to ", destination) diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index addb6e576f..9f8c1cbdb2 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -79,7 +79,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte } func (h *Shadowsocks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination if h.multiplexDialer == nil { @@ -107,7 +107,7 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati } func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination if h.multiplexDialer == nil { @@ -149,7 +149,7 @@ var _ N.Dialer = (*shadowsocksDialer)(nil) type shadowsocksDialer Shadowsocks func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination switch N.NetworkName(network) { @@ -177,7 +177,7 @@ func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, des } func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) diff --git a/outbound/shadowtls.go b/outbound/shadowtls.go index 4427301c40..38de8c0baf 100644 --- a/outbound/shadowtls.go +++ b/outbound/shadowtls.go @@ -92,7 +92,7 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context } func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination switch N.NetworkName(network) { diff --git a/outbound/socks.go b/outbound/socks.go index 063f7b952a..f4757467c5 100644 --- a/outbound/socks.go +++ b/outbound/socks.go @@ -65,7 +65,7 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio } func (h *Socks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination switch N.NetworkName(network) { @@ -91,7 +91,7 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S } func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination if h.uotClient != nil { diff --git a/outbound/trojan.go b/outbound/trojan.go index 52d72757e1..bde251d00a 100644 --- a/outbound/trojan.go +++ b/outbound/trojan.go @@ -124,7 +124,7 @@ func (h *Trojan) Close() error { type trojanDialer Trojan func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination var conn net.Conn diff --git a/outbound/vless.go b/outbound/vless.go index 66080eafca..a81678f015 100644 --- a/outbound/vless.go +++ b/outbound/vless.go @@ -143,7 +143,7 @@ func (h *VLESS) Close() error { type vlessDialer VLESS func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination var conn net.Conn @@ -186,7 +186,7 @@ func (h *vlessDialer) DialContext(ctx context.Context, network string, destinati func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination var conn net.Conn diff --git a/outbound/vmess.go b/outbound/vmess.go index c7d88b9040..3149729c00 100644 --- a/outbound/vmess.go +++ b/outbound/vmess.go @@ -157,7 +157,7 @@ func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, meta type vmessDialer VMess func (h *vmessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination var conn net.Conn @@ -185,7 +185,7 @@ func (h *vmessDialer) DialContext(ctx context.Context, network string, destinati } func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.AppendContext(ctx) + ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination var conn net.Conn From 419058f46639b9fabe27fede9fccd0f5df380a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 28 Oct 2024 22:12:14 +0800 Subject: [PATCH 0079/1272] Update NDK version --- cmd/internal/build_shared/sdk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index a664abee56..9beaa914aa 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -58,7 +58,7 @@ func FindSDK() { } func findNDK() bool { - const fixedVersion = "26.2.11394342" + const fixedVersion = "27.2.12479018" const versionFile = "source.properties" if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { androidNDKPath = fixedPath From 8c143feec8ce11dcc280cd9fa77a827ea6dee9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 30 Oct 2024 13:09:05 +0800 Subject: [PATCH 0080/1272] Increase timeouts --- common/dialer/default.go | 2 +- common/urltest/urltest.go | 2 ++ constant/timeout.go | 3 ++- experimental/clashapi/server_resources.go | 4 ++-- experimental/libbox/http.go | 6 +++--- route/router_geo_resources.go | 5 ++--- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index 488e8000eb..b4bca55e9d 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -81,7 +81,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi if options.ConnectTimeout != 0 { dialer.Timeout = time.Duration(options.ConnectTimeout) } else { - dialer.Timeout = C.TCPTimeout + dialer.Timeout = C.TCPConnectTimeout } // TODO: Add an option to customize the keep alive period dialer.KeepAlive = C.TCPKeepAliveInitial diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index 001f2e15a6..9efd040420 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -8,6 +8,7 @@ import ( "sync" "time" + 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" @@ -113,6 +114,7 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, + Timeout: C.TCPTimeout, } defer client.CloseIdleConnections() resp, err := client.Do(req.WithContext(ctx)) diff --git a/constant/timeout.go b/constant/timeout.go index b270a0500d..67ae6f6644 100644 --- a/constant/timeout.go +++ b/constant/timeout.go @@ -5,7 +5,8 @@ import "time" const ( TCPKeepAliveInitial = 10 * time.Minute TCPKeepAliveInterval = 75 * time.Second - TCPTimeout = 5 * time.Second + TCPConnectTimeout = 5 * time.Second + TCPTimeout = 15 * time.Second ReadPayloadTimeout = 300 * time.Millisecond DNSTimeout = 10 * time.Second QUICTimeout = 30 * time.Second diff --git a/experimental/clashapi/server_resources.go b/experimental/clashapi/server_resources.go index d6d22b5390..a5c79e0cf2 100644 --- a/experimental/clashapi/server_resources.go +++ b/experimental/clashapi/server_resources.go @@ -9,9 +9,9 @@ import ( "os" "path/filepath" "strings" - "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -60,7 +60,7 @@ func (s *Server) downloadExternalUI() error { httpClient := &http.Client{ Transport: &http.Transport{ ForceAttemptHTTP2: true, - TLSHandshakeTimeout: 5 * time.Second, + TLSHandshakeTimeout: C.TCPTimeout, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) }, diff --git a/experimental/libbox/http.go b/experimental/libbox/http.go index 87c5bed626..3e1a04d042 100644 --- a/experimental/libbox/http.go +++ b/experimental/libbox/http.go @@ -17,8 +17,8 @@ import ( "os" "strconv" "sync" - "time" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" @@ -69,8 +69,9 @@ type httpClient struct { func NewHTTPClient() HTTPClient { client := new(httpClient) - client.client.Timeout = 15 * time.Second client.client.Transport = &client.transport + client.transport.ForceAttemptHTTP2 = true + client.transport.TLSHandshakeTimeout = C.TCPTimeout client.transport.TLSClientConfig = &client.tls client.transport.DisableKeepAlives = true return client @@ -127,7 +128,6 @@ func (c *httpClient) TrySocks5(port int32) { } func (c *httpClient) KeepAlive() { - c.transport.ForceAttemptHTTP2 = true c.transport.DisableKeepAlives = false } diff --git a/route/router_geo_resources.go b/route/router_geo_resources.go index 14364d210d..4760bba326 100644 --- a/route/router_geo_resources.go +++ b/route/router_geo_resources.go @@ -7,7 +7,6 @@ import ( "net/http" "os" "path/filepath" - "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/geoip" @@ -158,7 +157,7 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error { httpClient := &http.Client{ Transport: &http.Transport{ ForceAttemptHTTP2: true, - TLSHandshakeTimeout: 5 * time.Second, + TLSHandshakeTimeout: C.TCPTimeout, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) }, @@ -213,7 +212,7 @@ func (r *Router) downloadGeositeDatabase(savePath string) error { httpClient := &http.Client{ Transport: &http.Transport{ ForceAttemptHTTP2: true, - TLSHandshakeTimeout: 5 * time.Second, + TLSHandshakeTimeout: C.TCPTimeout, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) }, From d66d5cd457dab1b6ee308e131d93d06bfc1ca26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 18 Oct 2024 09:58:03 +0800 Subject: [PATCH 0081/1272] Add deprecated warnings --- cmd/sing-box/cmd.go | 3 + experimental/deprecated/constants.go | 86 +++++++++++++++++++ experimental/deprecated/env.go | 30 +++++++ experimental/deprecated/manager.go | 19 ++++ experimental/libbox/command.go | 1 + .../libbox/command_close_connection.go | 4 + .../libbox/command_deprecated_report.go | 46 ++++++++++ experimental/libbox/command_server.go | 2 + experimental/libbox/deprecated.go | 56 ++++++++++++ experimental/libbox/service.go | 2 + go.mod | 2 +- inbound/tun.go | 12 +++ option/rule.go | 18 +--- option/rule_dns.go | 18 +--- route/router.go | 4 +- route/router_geo_resources.go | 5 +- route/rule_default.go | 26 ++++-- route/rule_dns.go | 25 ++++-- 18 files changed, 307 insertions(+), 52 deletions(-) create mode 100644 experimental/deprecated/constants.go create mode 100644 experimental/deprecated/env.go create mode 100644 experimental/deprecated/manager.go create mode 100644 experimental/libbox/command_deprecated_report.go create mode 100644 experimental/libbox/deprecated.go diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go index f6c37ff73e..9ae3a26835 100644 --- a/cmd/sing-box/cmd.go +++ b/cmd/sing-box/cmd.go @@ -7,8 +7,10 @@ import ( "strconv" "time" + "github.com/sagernet/sing-box/experimental/deprecated" _ "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" "github.com/spf13/cobra" @@ -65,4 +67,5 @@ func preRun(cmd *cobra.Command, args []string) { if len(configPaths) == 0 && len(configDirectories) == 0 { configPaths = append(configPaths, "config.json") } + globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger())) } diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go new file mode 100644 index 0000000000..34ea5fc3f3 --- /dev/null +++ b/experimental/deprecated/constants.go @@ -0,0 +1,86 @@ +package deprecated + +import ( + C "github.com/sagernet/sing-box/constant" + F "github.com/sagernet/sing/common/format" + + "golang.org/x/mod/semver" +) + +type Note struct { + Name string + Description string + DeprecatedVersion string + ScheduledVersion string + EnvName string + MigrationLink string +} + +func (n Note) Impending() bool { + if n.ScheduledVersion == "" { + return false + } + if !semver.IsValid("v" + C.Version) { + return false + } + versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion) + if versionMinor < 0 { + panic("invalid deprecated note: " + n.Name) + } + return versionMinor <= 1 +} + +func (n Note) Message() string { + return F.ToString( + n.Description, " is deprecated in sing-box ", n.DeprecatedVersion, + " and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.", + ) +} + +func (n Note) MessageWithLink() string { + return F.ToString( + n.Description, " is deprecated in sing-box ", n.DeprecatedVersion, + " and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink, + ) +} + +var OptionBadMatchSource = Note{ + Name: "bad-match-source", + Description: "legacy match source rule item", + DeprecatedVersion: "1.10.0", + ScheduledVersion: "1.11.0", + 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", + MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged", +} + +var Options = []Note{ + OptionBadMatchSource, + OptionGEOIP, + OptionGEOSITE, + OptionTUNAddressX, +} diff --git a/experimental/deprecated/env.go b/experimental/deprecated/env.go new file mode 100644 index 0000000000..113b871771 --- /dev/null +++ b/experimental/deprecated/env.go @@ -0,0 +1,30 @@ +package deprecated + +import ( + "os" + "strconv" + + "github.com/sagernet/sing/common/logger" +) + +type envManager struct { + logger logger.Logger +} + +func NewEnvManager(logger logger.Logger) Manager { + return &envManager{logger: logger} +} + +func (f *envManager) ReportDeprecated(feature Note) { + if !feature.Impending() { + f.logger.Warn(feature.MessageWithLink()) + return + } + enable, enableErr := strconv.ParseBool(os.Getenv("ENABLE_DEPRECATED_" + feature.EnvName)) + if enableErr == nil && enable { + f.logger.Warn(feature.MessageWithLink()) + return + } + f.logger.Error(feature.MessageWithLink()) + f.logger.Fatal("to continuing using this feature, set ENABLE_DEPRECATED_" + feature.EnvName + "=true") +} diff --git a/experimental/deprecated/manager.go b/experimental/deprecated/manager.go new file mode 100644 index 0000000000..d12acf48fa --- /dev/null +++ b/experimental/deprecated/manager.go @@ -0,0 +1,19 @@ +package deprecated + +import ( + "context" + + "github.com/sagernet/sing/service" +) + +type Manager interface { + ReportDeprecated(feature Note) +} + +func Report(ctx context.Context, feature Note) { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return + } + manager.ReportDeprecated(feature) +} diff --git a/experimental/libbox/command.go b/experimental/libbox/command.go index f9aca13ff9..3eb57dd494 100644 --- a/experimental/libbox/command.go +++ b/experimental/libbox/command.go @@ -16,4 +16,5 @@ const ( CommandSetSystemProxyEnabled CommandConnections CommandCloseConnection + CommandGetDeprecatedNotes ) diff --git a/experimental/libbox/command_close_connection.go b/experimental/libbox/command_close_connection.go index 1edd591122..a2b05e5619 100644 --- a/experimental/libbox/command_close_connection.go +++ b/experimental/libbox/command_close_connection.go @@ -18,6 +18,10 @@ func (c *CommandClient) CloseConnection(connId string) error { 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 { diff --git a/experimental/libbox/command_deprecated_report.go b/experimental/libbox/command_deprecated_report.go new file mode 100644 index 0000000000..5772124c19 --- /dev/null +++ b/experimental/libbox/command_deprecated_report.go @@ -0,0 +1,46 @@ +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_server.go b/experimental/libbox/command_server.go index f913191d6a..26b4aa79b1 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -174,6 +174,8 @@ func (s *CommandServer) handleConnection(conn net.Conn) error { return s.handleConnectionsConn(conn) case CommandCloseConnection: return s.handleCloseConnection(conn) + case CommandGetDeprecatedNotes: + return s.handleGetDeprecatedNotes(conn) default: return E.New("unknown command: ", command) } diff --git a/experimental/libbox/deprecated.go b/experimental/libbox/deprecated.go new file mode 100644 index 0000000000..b953014f94 --- /dev/null +++ b/experimental/libbox/deprecated.go @@ -0,0 +1,56 @@ +package libbox + +import ( + "sync" + + "github.com/sagernet/sing-box/experimental/deprecated" +) + +var _ deprecated.Manager = (*deprecatedManager)(nil) + +type deprecatedManager struct { + access sync.Mutex + features []deprecated.Note +} + +func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) { + m.access.Lock() + defer m.access.Unlock() + m.features = append(m.features, feature) +} + +func (m *deprecatedManager) Get() []deprecated.Note { + m.access.Lock() + defer m.access.Unlock() + features := m.features + m.features = nil + return features +} + +var _ = deprecated.Note(DeprecatedNote{}) + +type DeprecatedNote struct { + Name string + Description string + DeprecatedVersion string + ScheduledVersion string + EnvName string + MigrationLink string +} + +func (n DeprecatedNote) Impending() bool { + return deprecated.Note(n).Impending() +} + +func (n DeprecatedNote) Message() string { + return deprecated.Note(n).Message() +} + +func (n DeprecatedNote) MessageWithLink() string { + return deprecated.Note(n).MessageWithLink() +} + +type DeprecatedNoteIterator interface { + HasNext() bool + Next() *DeprecatedNote +} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index c65090103c..c87c960c99 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -14,6 +14,7 @@ import ( "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" @@ -49,6 +50,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) + ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} instance, err := box.New(box.Options{ Context: ctx, diff --git a/go.mod b/go.mod index 5d10b44d15..d70122831b 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.25.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/mod v0.19.0 golang.org/x/net v0.27.0 golang.org/x/sys v0.25.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 @@ -89,7 +90,6 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.19.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/inbound/tun.go b/inbound/tun.go index 6cb65de249..721112d779 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -13,6 +13,7 @@ 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/deprecated" "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -54,15 +55,18 @@ type Tun struct { func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) { address := options.Address + var deprecatedAddressUsed bool //nolint:staticcheck //goland:noinspection GoDeprecation if len(options.Inet4Address) > 0 { address = append(address, options.Inet4Address...) + deprecatedAddressUsed = true } //nolint:staticcheck //goland:noinspection GoDeprecation 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() @@ -76,11 +80,13 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger //goland:noinspection GoDeprecation if len(options.Inet4RouteAddress) > 0 { routeAddress = append(routeAddress, options.Inet4RouteAddress...) + deprecatedAddressUsed = true } //nolint:staticcheck //goland:noinspection GoDeprecation 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() @@ -94,11 +100,13 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger //goland:noinspection GoDeprecation if len(options.Inet4RouteExcludeAddress) > 0 { routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...) + deprecatedAddressUsed = true } //nolint:staticcheck //goland:noinspection GoDeprecation 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() @@ -107,6 +115,10 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger return it.Addr().Is6() }) + if deprecatedAddressUsed { + deprecated.Report(ctx, deprecated.OptionTUNAddressX) + } + tunMTU := options.MTU if tunMTU == 0 { tunMTU = 9000 diff --git a/option/rule.go b/option/rule.go index 5f15645c9b..0c66ab1d53 100644 --- a/option/rule.go +++ b/option/rule.go @@ -64,7 +64,7 @@ func (r Rule) IsValid() bool { } } -type _DefaultRule struct { +type DefaultRule struct { Inbound Listable[string] `json:"inbound,omitempty"` IPVersion int `json:"ip_version,omitempty"` Network Listable[string] `json:"network,omitempty"` @@ -104,22 +104,6 @@ type _DefaultRule struct { Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` } -type DefaultRule _DefaultRule - -func (r *DefaultRule) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_DefaultRule)(r)) - if err != nil { - return err - } - //nolint:staticcheck - //goland:noinspection GoDeprecation - if r.Deprecated_RulesetIPCIDRMatchSource { - r.Deprecated_RulesetIPCIDRMatchSource = false - r.RuleSetIPCIDRMatchSource = true - } - return nil -} - func (r *DefaultRule) IsValid() bool { var defaultValue DefaultRule defaultValue.Invert = r.Invert diff --git a/option/rule_dns.go b/option/rule_dns.go index 2e03b4252e..2e52d6c561 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -64,7 +64,7 @@ func (r DNSRule) IsValid() bool { } } -type _DefaultDNSRule struct { +type DefaultDNSRule struct { Inbound Listable[string] `json:"inbound,omitempty"` IPVersion int `json:"ip_version,omitempty"` QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` @@ -109,22 +109,6 @@ type _DefaultDNSRule struct { Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` } -type DefaultDNSRule _DefaultDNSRule - -func (r *DefaultDNSRule) UnmarshalJSON(bytes []byte) error { - err := json.UnmarshalDisallowUnknownFields(bytes, (*_DefaultDNSRule)(r)) - if err != nil { - return err - } - //nolint:staticcheck - //goland:noinspection GoDeprecation - if r.Deprecated_RulesetIPCIDRMatchSource { - r.Deprecated_RulesetIPCIDRMatchSource = false - r.RuleSetIPCIDRMatchSource = true - } - return nil -} - func (r *DefaultDNSRule) IsValid() bool { var defaultValue DefaultDNSRule defaultValue.Invert = r.Invert diff --git a/route/router.go b/route/router.go index aac5f603b8..2aaffbbb86 100644 --- a/route/router.go +++ b/route/router.go @@ -153,14 +153,14 @@ func NewRouter( Logger: router.dnsLogger, }) for i, ruleOptions := range options.Rules { - routeRule, err := NewRule(router, router.logger, ruleOptions, true) + routeRule, err := NewRule(ctx, router, router.logger, ruleOptions, true) if err != nil { return nil, E.Cause(err, "parse rule[", i, "]") } router.rules = append(router.rules, routeRule) } for i, dnsRuleOptions := range dnsOptions.Rules { - dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions, true) + dnsRule, err := NewDNSRule(ctx, router, router.logger, dnsRuleOptions, true) if err != nil { return nil, E.Cause(err, "parse dns rule[", i, "]") } diff --git a/route/router_geo_resources.go b/route/router_geo_resources.go index 4760bba326..d4f65cc801 100644 --- a/route/router_geo_resources.go +++ b/route/router_geo_resources.go @@ -12,6 +12,7 @@ import ( "github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geosite" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/rw" @@ -31,7 +32,7 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { if err != nil { return nil, err } - rule, err = NewDefaultRule(r, nil, geosite.Compile(items)) + rule, err = NewDefaultRule(r.ctx, r, nil, geosite.Compile(items)) if err != nil { return nil, err } @@ -40,6 +41,7 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { } func (r *Router) prepareGeoIPDatabase() error { + deprecated.Report(r.ctx, deprecated.OptionGEOIP) var geoPath string if r.geoIPOptions.Path != "" { geoPath = r.geoIPOptions.Path @@ -86,6 +88,7 @@ func (r *Router) prepareGeoIPDatabase() error { } func (r *Router) prepareGeositeDatabase() error { + deprecated.Report(r.ctx, deprecated.OptionGEOSITE) var geoPath string if r.geositeOptions.Path != "" { geoPath = r.geositeOptions.Path diff --git a/route/rule_default.go b/route/rule_default.go index 40b93e5f96..fb4f6d82be 100644 --- a/route/rule_default.go +++ b/route/rule_default.go @@ -1,14 +1,17 @@ package route import ( + "context" + "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" E "github.com/sagernet/sing/common/exceptions" ) -func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) { +func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { @@ -17,7 +20,7 @@ func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rul if options.DefaultOptions.Outbound == "" && checkOutbound { return nil, E.New("missing outbound field") } - return NewDefaultRule(router, logger, options.DefaultOptions) + return NewDefaultRule(ctx, router, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") @@ -25,7 +28,7 @@ func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rul if options.LogicalOptions.Outbound == "" && checkOutbound { return nil, E.New("missing outbound field") } - return NewLogicalRule(router, logger, options.LogicalOptions) + return NewLogicalRule(ctx, router, logger, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -42,7 +45,7 @@ type RuleItem interface { String() string } -func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { +func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { rule := &DefaultRule{ abstractDefaultRule{ invert: options.Invert, @@ -218,7 +221,16 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.allItems = append(rule.allItems, item) } if len(options.RuleSet) > 0 { - item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource, false) + 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) rule.allItems = append(rule.allItems, item) } @@ -231,7 +243,7 @@ type LogicalRule struct { abstractLogicalRule } -func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { +func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { r := &LogicalRule{ abstractLogicalRule{ rules: make([]adapter.HeadlessRule, len(options.Rules)), @@ -248,7 +260,7 @@ func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options opt return nil, E.New("unknown logical mode: ", options.Mode) } for i, subRule := range options.Rules { - rule, err := NewRule(router, logger, subRule, false) + rule, err := NewRule(ctx, router, logger, subRule, false) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } diff --git a/route/rule_dns.go b/route/rule_dns.go index 616f956aaf..4740488e3d 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -1,17 +1,19 @@ package route import ( + "context" "net/netip" "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" E "github.com/sagernet/sing/common/exceptions" ) -func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) { +func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { @@ -20,7 +22,7 @@ func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option. if options.DefaultOptions.Server == "" && checkServer { return nil, E.New("missing server field") } - return NewDefaultDNSRule(router, logger, options.DefaultOptions) + return NewDefaultDNSRule(ctx, router, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") @@ -28,7 +30,7 @@ func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option. if options.LogicalOptions.Server == "" && checkServer { return nil, E.New("missing server field") } - return NewLogicalDNSRule(router, logger, options.LogicalOptions) + return NewLogicalDNSRule(ctx, router, logger, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -43,7 +45,7 @@ type DefaultDNSRule struct { clientSubnet *netip.Prefix } -func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { +func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { rule := &DefaultDNSRule{ abstractDefaultRule: abstractDefaultRule{ invert: options.Invert, @@ -227,7 +229,16 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.allItems = append(rule.allItems, item) } if len(options.RuleSet) > 0 { - item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource, options.RuleSetIPCIDRAcceptEmpty) + 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) rule.allItems = append(rule.allItems, item) } @@ -283,7 +294,7 @@ type LogicalDNSRule struct { clientSubnet *netip.Prefix } -func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { +func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { r := &LogicalDNSRule{ abstractLogicalRule: abstractLogicalRule{ rules: make([]adapter.HeadlessRule, len(options.Rules)), @@ -303,7 +314,7 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options return nil, E.New("unknown logical mode: ", options.Mode) } for i, subRule := range options.Rules { - rule, err := NewDNSRule(router, logger, subRule, false) + rule, err := NewDNSRule(ctx, router, logger, subRule, false) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } From 9585c53e9fae8367c7dd759bb66202aefbdaf002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 30 Oct 2024 11:12:31 +0800 Subject: [PATCH 0082/1272] release: Add upload dSYMs --- Makefile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c945776319..d072085fb7 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,16 @@ upload_macos_dmg: cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \ ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg" -release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg +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" + +release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg upload_macos_dsyms build_tvos: cd ../sing-box-for-apple && \ From 718cffea9a34c9f2d552728ee8b3f978aaf82c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 30 Oct 2024 22:48:10 +0800 Subject: [PATCH 0083/1272] platform: Add openURL event --- experimental/libbox/command_client.go | 8 ++- experimental/libbox/command_event.go | 40 +++++++++++++ experimental/libbox/command_server.go | 8 +++ experimental/libbox/command_status.go | 69 +++++++++++++++++++---- experimental/libbox/config.go | 3 + experimental/libbox/platform/interface.go | 1 + experimental/libbox/service.go | 16 ++++-- 7 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 experimental/libbox/command_event.go diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index bd99511582..a7c21d6873 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -20,6 +20,7 @@ type CommandClient struct { type CommandClientOptions struct { Command int32 StatusInterval int64 + IsMainClient bool } type CommandClientHandler interface { @@ -28,6 +29,7 @@ type CommandClientHandler interface { ClearLogs() WriteLogs(messageList StringIterator) WriteStatus(message *StatusMessage) + OpenURL(url string) WriteGroups(message OutboundGroupIterator) InitializeClashMode(modeList StringIterator, currentMode string) UpdateClashMode(newMode string) @@ -91,9 +93,13 @@ func (c *CommandClient) Connect() error { c.handler.Connected() go c.handleLogConn(conn) case CommandStatus: + err = binary.Write(conn, binary.BigEndian, c.options.IsMainClient) + if err != nil { + return E.Cause(err, "write is main client") + } err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) if err != nil { - return E.Cause(err, "write interval") + return E.Cause(err, "write header") } c.handler.Connected() go c.handleStatusConn(conn) diff --git a/experimental/libbox/command_event.go b/experimental/libbox/command_event.go new file mode 100644 index 0000000000..1be45d1e9f --- /dev/null +++ b/experimental/libbox/command_event.go @@ -0,0 +1,40 @@ +package libbox + +import ( + "encoding/binary" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/varbin" +) + +type myEvent interface { + writeTo(writer varbin.Writer) +} + +func readEvent(reader varbin.Reader) (myEvent, error) { + eventType, err := reader.ReadByte() + if err != nil { + return nil, err + } + switch eventType { + case eventTypeEmpty: + return nil, nil + case eventTypeOpenURL: + url, err := varbin.ReadValue[string](reader, binary.BigEndian) + if err != nil { + return nil, err + } + return &eventOpenURL{URL: url}, nil + default: + return nil, E.New("unknown event type: ", eventType) + } +} + +type eventOpenURL struct { + URL string +} + +func (e *eventOpenURL) writeTo(writer varbin.Writer) { + writer.WriteByte(eventTypeOpenURL) + varbin.Write(writer, binary.BigEndian, e.URL) +} diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 26b4aa79b1..52705a37ec 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -33,6 +33,7 @@ type CommandServer struct { urlTestUpdate chan struct{} modeUpdate chan struct{} logReset chan struct{} + events chan myEvent closedConnections []Connection } @@ -52,6 +53,7 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ urlTestUpdate: make(chan struct{}, 1), modeUpdate: make(chan struct{}, 1), logReset: make(chan struct{}, 1), + events: make(chan myEvent, 8), } server.observer = observable.NewObserver[string](server.subscriber, 64) return server @@ -61,6 +63,12 @@ func (s *CommandServer) SetService(newService *BoxService) { if newService != nil { service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) + newService.platformInterface.openURLFunc = func(url string) { + select { + case s.events <- &eventOpenURL{URL: url}: + default: + } + } } s.service = newService s.notifyURLTestUpdate() diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go index 4ab09d4b28..7591ac2499 100644 --- a/experimental/libbox/command_status.go +++ b/experimental/libbox/command_status.go @@ -1,6 +1,7 @@ package libbox import ( + std_bufio "bufio" "encoding/binary" "net" "runtime" @@ -9,9 +10,15 @@ import ( "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/experimental/clashapi" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/memory" ) +const ( + eventTypeEmpty byte = iota + eventTypeOpenURL +) + type StatusMessage struct { Memory int64 Goroutines int32 @@ -44,31 +51,73 @@ func (s *CommandServer) readStatus() StatusMessage { } func (s *CommandServer) handleStatusConn(conn net.Conn) error { + var isMainClient bool + err := binary.Read(conn, binary.BigEndian, &isMainClient) + if err != nil { + return E.Cause(err, "read is main client") + } var interval int64 - err := binary.Read(conn, binary.BigEndian, &interval) + 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) - for { - err = binary.Write(conn, binary.BigEndian, s.readStatus()) - if err != nil { - return err + writer := std_bufio.NewWriter(conn) + if isMainClient { + for { + writer.WriteByte(eventTypeEmpty) + err = binary.Write(writer, binary.BigEndian, s.readStatus()) + if err != nil { + return err + } + writer.Flush() + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + case event := <-s.events: + event.writeTo(writer) + writer.Flush() + } } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: + } else { + for { + err = binary.Write(writer, binary.BigEndian, s.readStatus()) + if err != nil { + return err + } + writer.Flush() + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + } } } } func (c *CommandClient) handleStatusConn(conn net.Conn) { + reader := std_bufio.NewReader(conn) for { + if c.options.IsMainClient { + rawEvent, err := readEvent(reader) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + switch event := rawEvent.(type) { + case *eventOpenURL: + c.handler.OpenURL(event.URL) + continue + case nil: + default: + panic(F.ToString("unexpected event type: ", event)) + } + } var message StatusMessage - err := binary.Read(conn, binary.BigEndian, &message) + err := binary.Read(reader, binary.BigEndian, &message) if err != nil { c.handler.Disconnected(err.Error()) return diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index b7731143cb..e7ce487e26 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -134,6 +134,9 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { } +func (s *platformInterfaceStub) OpenURL(url string) { +} + func FormatConfig(configContent string) (string, error) { options, err := parseConfig(configContent) if err != nil { diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 3bec13fa5b..08397bb02c 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -25,4 +25,5 @@ type Interface interface { ClearDNSCache() ReadWIFIState() adapter.WIFIState process.Searcher + OpenURL(url string) } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index c87c960c99..7e2e141487 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -34,9 +34,9 @@ type BoxService struct { ctx context.Context cancel context.CancelFunc instance *box.Box + platformInterface *platformInterfaceWrapper pauseManager pause.Manager urlTestHistoryStorage *urltest.HistoryStorage - servicePauseFields } @@ -67,6 +67,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box ctx: ctx, cancel: cancel, instance: instance, + platformInterface: platformWrapper, urlTestHistoryStorage: urlTestHistoryStorage, pauseManager: service.FromContext[pause.Manager](ctx), }, nil @@ -102,9 +103,10 @@ var ( ) type platformInterfaceWrapper struct { - iif PlatformInterface - useProcFS bool - router adapter.Router + iif PlatformInterface + useProcFS bool + router adapter.Router + openURLFunc func(url string) } func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error { @@ -238,3 +240,9 @@ func (w *platformInterfaceWrapper) DisableColors() bool { func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) { w.iif.WriteLog(message) } + +func (w *platformInterfaceWrapper) OpenURL(url string) { + if w.openURLFunc != nil { + w.openURLFunc(url) + } +} From cdb93f0bb217a51ac15353be4aeed119533c5c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 2 Nov 2024 21:37:04 +0800 Subject: [PATCH 0084/1272] Fix "Fix metadata context" --- outbound/builder.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/outbound/builder.go b/outbound/builder.go index b99b49b7b1..d895b56dda 100644 --- a/outbound/builder.go +++ b/outbound/builder.go @@ -11,6 +11,11 @@ import ( ) func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Outbound) (adapter.Outbound, error) { + if tag != "" { + ctx = adapter.WithContext(ctx, &adapter.InboundContext{ + Outbound: tag, + }) + } if options.Type == "" { return nil, E.New("missing outbound type") } From a001e30d8b9fb0fcaf123642f1c84ead0de11e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 3 Nov 2024 20:42:18 +0800 Subject: [PATCH 0085/1272] platform: Remove `SetTraceback("all")` --- experimental/libbox/setup.go | 1 - 1 file changed, 1 deletion(-) diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index ac67db38f2..eb5d7c4e3f 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -24,7 +24,6 @@ var ( func init() { debug.SetPanicOnFault(true) - debug.SetTraceback("all") } func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) { From b702d0b67a0881ef23df6530053b00c2bc21a7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 4 Nov 2024 11:22:55 +0800 Subject: [PATCH 0086/1272] Update dependencies --- go.mod | 28 +++++++++++++-------------- go.sum | 60 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index d70122831b..cd23034f9b 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,14 @@ require ( github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 - github.com/go-chi/chi/v5 v5.0.12 + github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.3.0 github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/libdns/alidns v1.0.3 github.com/libdns/cloudflare v0.1.1 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d + github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.62 github.com/ooni/go-libtor v1.1.8 @@ -25,30 +25,30 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.4 github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f - github.com/sagernet/quic-go v0.47.0-beta.2 + github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.5.0-rc.2 - github.com/sagernet/sing-dns v0.3.0-rc.2 + github.com/sagernet/sing v0.5.0 + github.com/sagernet/sing-dns v0.3.0 github.com/sagernet/sing-mux v0.2.0 - github.com/sagernet/sing-quic v0.3.0-rc.1 + github.com/sagernet/sing-quic v0.3.0-rc.2 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.4.0-rc.4 + github.com/sagernet/sing-tun v0.4.0-rc.5 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.28.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/mod v0.19.0 - golang.org/x/net v0.27.0 - golang.org/x/sys v0.25.0 + golang.org/x/mod v0.20.0 + golang.org/x/net v0.30.0 + golang.org/x/sys v0.26.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 @@ -91,9 +91,9 @@ require ( github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 7afac346c4..5406c74650 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,7 @@ github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4 github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= @@ -17,8 +17,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -70,8 +70,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U= -github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= @@ -110,27 +110,27 @@ 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.47.0-beta.2 h1:1tCGWFOSaXIeuQaHrwOMJIYvlupjTcaVInGQw5ArULU= -github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYigzQ5lQu3Nh4wNh8Q= +github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AEpT94Z0vwo= +github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= 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.5.0-rc.2 h1:tIrs6pRbjJWvI0ITRSg47P1wosY+iSuHpw9t5/hBx+Q= -github.com/sagernet/sing v0.5.0-rc.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.3.0-rc.2 h1:z1yROBxd/6wik5h53Sz5df1DSmbPTaOu/Z0wAmyXGoQ= -github.com/sagernet/sing-dns v0.3.0-rc.2/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= +github.com/sagernet/sing v0.5.0 h1:soo2wVwLcieKWWKIksFNK6CCAojUgAppqQVwyRYGkEM= +github.com/sagernet/sing v0.5.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-dns v0.3.0 h1:uHCIlbCwBxALJwXcEK1d75d7t3vzCSVEQsPfZR1cxQE= +github.com/sagernet/sing-dns v0.3.0/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= -github.com/sagernet/sing-quic v0.3.0-rc.1 h1:SlzL1yfEAKJyRduub8vzOVtbyTLAX7RZEEBZxO5utts= -github.com/sagernet/sing-quic v0.3.0-rc.1/go.mod h1:uX+aUHA0fgIN6U3WViseDpSdTQriViZ7qz0Wbsf1mNQ= +github.com/sagernet/sing-quic v0.3.0-rc.2 h1:7vcC4bdS1GBJzHZhfmJiH0CfzQ4mYLUW51Z2RNHcGwc= +github.com/sagernet/sing-quic v0.3.0-rc.2/go.mod h1:3UOq51WVqzra7eCgod7t4hqnTaOiZzFUci9avMrtOqs= 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.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.4.0-rc.4 h1:EFz+UjLZTm+YS3ob1vpqjOga67wyvnxWcbQKfe5wE3Y= -github.com/sagernet/sing-tun v0.4.0-rc.4/go.mod h1:+lQdWhqD4atzrCgRhoyrxBCg1OBru/hAv2BT3kdgmGM= +github.com/sagernet/sing-tun v0.4.0-rc.5 h1:d4o36GbVZtsucpt5On7CjTFSx4zx82DL8OlGR7rX7vY= +github.com/sagernet/sing-tun v0.4.0-rc.5/go.mod h1:rWu1ZC2lj4+UOOSNiUWjsY0y2fJcOHnLTMGna2n5P2o= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= @@ -141,8 +141,8 @@ github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYASco github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= 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.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -170,16 +170,16 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -190,19 +190,19 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= 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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.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.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= From 1d517b6ca50236057fff090c7803f88dd1f813d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 29 Oct 2024 15:28:58 +0800 Subject: [PATCH 0087/1272] platform: Add link flags --- experimental/libbox/link_flags_linux.go | 30 +++++++++++++++++++++++++ experimental/libbox/link_flags_stub.go | 11 +++++++++ experimental/libbox/platform.go | 1 + experimental/libbox/service.go | 1 + 4 files changed, 43 insertions(+) create mode 100644 experimental/libbox/link_flags_linux.go create mode 100644 experimental/libbox/link_flags_stub.go diff --git a/experimental/libbox/link_flags_linux.go b/experimental/libbox/link_flags_linux.go new file mode 100644 index 0000000000..aa9ad46d20 --- /dev/null +++ b/experimental/libbox/link_flags_linux.go @@ -0,0 +1,30 @@ +package libbox + +import ( + "net" + "syscall" +) + +// copied from net.linkFlags +func linkFlags(rawFlags uint32) net.Flags { + var f net.Flags + if rawFlags&syscall.IFF_UP != 0 { + f |= net.FlagUp + } + if rawFlags&syscall.IFF_RUNNING != 0 { + f |= net.FlagRunning + } + if rawFlags&syscall.IFF_BROADCAST != 0 { + f |= net.FlagBroadcast + } + if rawFlags&syscall.IFF_LOOPBACK != 0 { + f |= net.FlagLoopback + } + if rawFlags&syscall.IFF_POINTOPOINT != 0 { + f |= net.FlagPointToPoint + } + if rawFlags&syscall.IFF_MULTICAST != 0 { + f |= net.FlagMulticast + } + return f +} diff --git a/experimental/libbox/link_flags_stub.go b/experimental/libbox/link_flags_stub.go new file mode 100644 index 0000000000..306754f3f2 --- /dev/null +++ b/experimental/libbox/link_flags_stub.go @@ -0,0 +1,11 @@ +//go:build !linux + +package libbox + +import ( + "net" +) + +func linkFlags(rawFlags uint32) net.Flags { + panic("stub!") +} diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 4078140f8c..e2bf8e8eba 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -38,6 +38,7 @@ type NetworkInterface struct { MTU int32 Name string Addresses StringIterator + Flags int32 } type WIFIState struct { diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 7e2e141487..53ca168cfa 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -181,6 +181,7 @@ func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) { MTU: int(netInterface.MTU), Name: netInterface.Name, Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), + Flags: linkFlags(uint32(netInterface.Flags)), }) } return interfaces, nil From f504fb0d46c527e43f4e41cded766ac6e4480c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 5 Nov 2024 20:03:39 +0800 Subject: [PATCH 0088/1272] Revert "platform: Add openURL event" This reverts commit 718cffea9a34c9f2d552728ee8b3f978aaf82c03. --- experimental/libbox/command_client.go | 8 +-- experimental/libbox/command_event.go | 40 ------------- experimental/libbox/command_server.go | 8 --- experimental/libbox/command_status.go | 69 ++++------------------- experimental/libbox/config.go | 3 - experimental/libbox/platform/interface.go | 1 - experimental/libbox/service.go | 16 ++---- 7 files changed, 15 insertions(+), 130 deletions(-) delete mode 100644 experimental/libbox/command_event.go diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index a7c21d6873..bd99511582 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -20,7 +20,6 @@ type CommandClient struct { type CommandClientOptions struct { Command int32 StatusInterval int64 - IsMainClient bool } type CommandClientHandler interface { @@ -29,7 +28,6 @@ type CommandClientHandler interface { ClearLogs() WriteLogs(messageList StringIterator) WriteStatus(message *StatusMessage) - OpenURL(url string) WriteGroups(message OutboundGroupIterator) InitializeClashMode(modeList StringIterator, currentMode string) UpdateClashMode(newMode string) @@ -93,13 +91,9 @@ func (c *CommandClient) Connect() error { c.handler.Connected() go c.handleLogConn(conn) case CommandStatus: - err = binary.Write(conn, binary.BigEndian, c.options.IsMainClient) - if err != nil { - return E.Cause(err, "write is main client") - } err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) if err != nil { - return E.Cause(err, "write header") + return E.Cause(err, "write interval") } c.handler.Connected() go c.handleStatusConn(conn) diff --git a/experimental/libbox/command_event.go b/experimental/libbox/command_event.go deleted file mode 100644 index 1be45d1e9f..0000000000 --- a/experimental/libbox/command_event.go +++ /dev/null @@ -1,40 +0,0 @@ -package libbox - -import ( - "encoding/binary" - - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" -) - -type myEvent interface { - writeTo(writer varbin.Writer) -} - -func readEvent(reader varbin.Reader) (myEvent, error) { - eventType, err := reader.ReadByte() - if err != nil { - return nil, err - } - switch eventType { - case eventTypeEmpty: - return nil, nil - case eventTypeOpenURL: - url, err := varbin.ReadValue[string](reader, binary.BigEndian) - if err != nil { - return nil, err - } - return &eventOpenURL{URL: url}, nil - default: - return nil, E.New("unknown event type: ", eventType) - } -} - -type eventOpenURL struct { - URL string -} - -func (e *eventOpenURL) writeTo(writer varbin.Writer) { - writer.WriteByte(eventTypeOpenURL) - varbin.Write(writer, binary.BigEndian, e.URL) -} diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 52705a37ec..26b4aa79b1 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -33,7 +33,6 @@ type CommandServer struct { urlTestUpdate chan struct{} modeUpdate chan struct{} logReset chan struct{} - events chan myEvent closedConnections []Connection } @@ -53,7 +52,6 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ urlTestUpdate: make(chan struct{}, 1), modeUpdate: make(chan struct{}, 1), logReset: make(chan struct{}, 1), - events: make(chan myEvent, 8), } server.observer = observable.NewObserver[string](server.subscriber, 64) return server @@ -63,12 +61,6 @@ func (s *CommandServer) SetService(newService *BoxService) { if newService != nil { service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) - newService.platformInterface.openURLFunc = func(url string) { - select { - case s.events <- &eventOpenURL{URL: url}: - default: - } - } } s.service = newService s.notifyURLTestUpdate() diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go index 7591ac2499..4ab09d4b28 100644 --- a/experimental/libbox/command_status.go +++ b/experimental/libbox/command_status.go @@ -1,7 +1,6 @@ package libbox import ( - std_bufio "bufio" "encoding/binary" "net" "runtime" @@ -10,15 +9,9 @@ import ( "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/experimental/clashapi" E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/memory" ) -const ( - eventTypeEmpty byte = iota - eventTypeOpenURL -) - type StatusMessage struct { Memory int64 Goroutines int32 @@ -51,73 +44,31 @@ func (s *CommandServer) readStatus() StatusMessage { } func (s *CommandServer) handleStatusConn(conn net.Conn) error { - var isMainClient bool - err := binary.Read(conn, binary.BigEndian, &isMainClient) - if err != nil { - return E.Cause(err, "read is main client") - } var interval int64 - err = binary.Read(conn, binary.BigEndian, &interval) + 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 := std_bufio.NewWriter(conn) - if isMainClient { - for { - writer.WriteByte(eventTypeEmpty) - err = binary.Write(writer, binary.BigEndian, s.readStatus()) - if err != nil { - return err - } - writer.Flush() - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - case event := <-s.events: - event.writeTo(writer) - writer.Flush() - } + for { + err = binary.Write(conn, binary.BigEndian, s.readStatus()) + if err != nil { + return err } - } else { - for { - err = binary.Write(writer, binary.BigEndian, s.readStatus()) - if err != nil { - return err - } - writer.Flush() - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: } } } func (c *CommandClient) handleStatusConn(conn net.Conn) { - reader := std_bufio.NewReader(conn) for { - if c.options.IsMainClient { - rawEvent, err := readEvent(reader) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - switch event := rawEvent.(type) { - case *eventOpenURL: - c.handler.OpenURL(event.URL) - continue - case nil: - default: - panic(F.ToString("unexpected event type: ", event)) - } - } var message StatusMessage - err := binary.Read(reader, binary.BigEndian, &message) + err := binary.Read(conn, binary.BigEndian, &message) if err != nil { c.handler.Disconnected(err.Error()) return diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index e7ce487e26..b7731143cb 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -134,9 +134,6 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { } -func (s *platformInterfaceStub) OpenURL(url string) { -} - func FormatConfig(configContent string) (string, error) { options, err := parseConfig(configContent) if err != nil { diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 08397bb02c..3bec13fa5b 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -25,5 +25,4 @@ type Interface interface { ClearDNSCache() ReadWIFIState() adapter.WIFIState process.Searcher - OpenURL(url string) } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 53ca168cfa..0525c8bf6b 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -34,9 +34,9 @@ type BoxService struct { ctx context.Context cancel context.CancelFunc instance *box.Box - platformInterface *platformInterfaceWrapper pauseManager pause.Manager urlTestHistoryStorage *urltest.HistoryStorage + servicePauseFields } @@ -67,7 +67,6 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box ctx: ctx, cancel: cancel, instance: instance, - platformInterface: platformWrapper, urlTestHistoryStorage: urlTestHistoryStorage, pauseManager: service.FromContext[pause.Manager](ctx), }, nil @@ -103,10 +102,9 @@ var ( ) type platformInterfaceWrapper struct { - iif PlatformInterface - useProcFS bool - router adapter.Router - openURLFunc func(url string) + iif PlatformInterface + useProcFS bool + router adapter.Router } func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error { @@ -241,9 +239,3 @@ func (w *platformInterfaceWrapper) DisableColors() bool { func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) { w.iif.WriteLog(message) } - -func (w *platformInterfaceWrapper) OpenURL(url string) { - if w.openURLFunc != nil { - w.openURLFunc(url) - } -} From 88099a304a4cc22c094b951df67bab6ac92a3353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 12:51:53 +0800 Subject: [PATCH 0089/1272] platform: Add SendNotification --- experimental/libbox/config.go | 7 ++++++- experimental/libbox/platform.go | 11 +++++++++++ experimental/libbox/platform/interface.go | 13 ++++++++++++- experimental/libbox/service.go | 12 ++++++------ route/router.go | 19 +++++++++++++++++-- 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index b7731143cb..56c5618983 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" + "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" @@ -54,7 +55,7 @@ func (s *platformInterfaceStub) UsePlatformAutoDetectInterfaceControl() bool { return true } -func (s *platformInterfaceStub) AutoDetectInterfaceControl() control.Func { +func (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error { return nil } @@ -134,6 +135,10 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { } +func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error { + return nil +} + func FormatConfig(configContent string) (string, error) { options, err := parseConfig(configContent) if err != nil { diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index e2bf8e8eba..1976ad70fe 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -22,6 +22,7 @@ type PlatformInterface interface { IncludeAllNetworks() bool ReadWIFIState() *WIFIState ClearDNSCache() + SendNotification(notification *Notification) error } type TunInterface interface { @@ -55,6 +56,16 @@ type NetworkInterfaceIterator interface { HasNext() bool } +type Notification struct { + Identifier string + TypeName string + TypeID int32 + Title string + Subtitle string + Body string + OpenURL string +} + type OnDemandRule interface { Target() int32 DNSSearchDomainMatch() StringIterator diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 3bec13fa5b..3dc000814a 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -14,7 +14,7 @@ import ( type Interface interface { Initialize(ctx context.Context, router adapter.Router) error UsePlatformAutoDetectInterfaceControl() bool - AutoDetectInterfaceControl() control.Func + AutoDetectInterfaceControl(fd int) error OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) UsePlatformDefaultInterfaceMonitor() bool CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor @@ -25,4 +25,15 @@ type Interface interface { ClearDNSCache() ReadWIFIState() adapter.WIFIState 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 0525c8bf6b..20395e95d3 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -116,12 +116,8 @@ func (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool return w.iif.UsePlatformAutoDetectInterfaceControl() } -func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func { - return func(network, address string, conn syscall.RawConn) error { - return control.Raw(conn, func(fd uintptr) error { - return w.iif.AutoDetectInterfaceControl(int32(fd)) - }) - } +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) { @@ -239,3 +235,7 @@ func (w *platformInterfaceWrapper) DisableColors() bool { func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) { w.iif.WriteLog(message) } + +func (w *platformInterfaceWrapper) SendNotification(notification *platform.Notification) error { + return w.iif.SendNotification((*Notification)(notification)) +} diff --git a/route/router.go b/route/router.go index 2aaffbbb86..1fc9aa7787 100644 --- a/route/router.go +++ b/route/router.go @@ -10,6 +10,7 @@ import ( "os/user" "runtime" "strings" + "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -211,12 +212,19 @@ func NewRouter( } else { detour = dialer.NewDetour(router, server.Detour) } + var serverProtocol string switch server.Address { case "local": + serverProtocol = "local" default: serverURL, _ := url.Parse(server.Address) var serverAddress string if serverURL != nil { + if serverURL.Scheme == "" { + serverProtocol = "udp" + } else { + serverProtocol = serverURL.Scheme + } serverAddress = serverURL.Hostname() } if serverAddress == "" { @@ -242,9 +250,12 @@ func NewRouter( } else if dnsOptions.ClientSubnet != nil { clientSubnet = dnsOptions.ClientSubnet.Build() } + if serverProtocol == "" { + serverProtocol = "transport" + } transport, err := dns.CreateTransport(dns.TransportOptions{ Context: ctx, - Logger: logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")), + Logger: logFactory.NewLogger(F.ToString("dns/", serverProtocol, "[", tag, "]")), Name: tag, Dialer: detour, Address: server.Address, @@ -1188,7 +1199,11 @@ func (r *Router) AutoDetectInterface() bool { func (r *Router) AutoDetectInterfaceFunc() control.Func { if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { - return r.platformInterface.AutoDetectInterfaceControl() + return func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return r.platformInterface.AutoDetectInterfaceControl(int(fd)) + }) + } } else { if r.interfaceMonitor == nil { return nil From 21faadb9921510f294341c136c68befa12891ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 7 Nov 2024 19:58:46 +0800 Subject: [PATCH 0090/1272] Uniq deprecated notes --- cmd/sing-box/cmd.go | 2 +- cmd/sing-box/cmd_check.go | 2 +- experimental/deprecated/{env.go => stderr.go} | 18 +++++++++++++----- experimental/libbox/deprecated.go | 13 +++++++------ 4 files changed, 22 insertions(+), 13 deletions(-) rename experimental/deprecated/{env.go => stderr.go} (59%) diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go index 9ae3a26835..a2d4a00b35 100644 --- a/cmd/sing-box/cmd.go +++ b/cmd/sing-box/cmd.go @@ -67,5 +67,5 @@ func preRun(cmd *cobra.Command, args []string) { if len(configPaths) == 0 && len(configDirectories) == 0 { configPaths = append(configPaths, "config.json") } - globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger())) + globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())) } diff --git a/cmd/sing-box/cmd_check.go b/cmd/sing-box/cmd_check.go index 1beab9548d..29a3908168 100644 --- a/cmd/sing-box/cmd_check.go +++ b/cmd/sing-box/cmd_check.go @@ -30,7 +30,7 @@ func check() error { if err != nil { return err } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(globalCtx) instance, err := box.New(box.Options{ Context: ctx, Options: options, diff --git a/experimental/deprecated/env.go b/experimental/deprecated/stderr.go similarity index 59% rename from experimental/deprecated/env.go rename to experimental/deprecated/stderr.go index 113b871771..0826baf94b 100644 --- a/experimental/deprecated/env.go +++ b/experimental/deprecated/stderr.go @@ -7,15 +7,23 @@ import ( "github.com/sagernet/sing/common/logger" ) -type envManager struct { - logger logger.Logger +type stderrManager struct { + logger logger.Logger + reported map[string]bool } -func NewEnvManager(logger logger.Logger) Manager { - return &envManager{logger: logger} +func NewStderrManager(logger logger.Logger) Manager { + return &stderrManager{ + logger: logger, + reported: make(map[string]bool), + } } -func (f *envManager) ReportDeprecated(feature Note) { +func (f *stderrManager) ReportDeprecated(feature Note) { + if f.reported[feature.Name] { + return + } + f.reported[feature.Name] = true if !feature.Impending() { f.logger.Warn(feature.MessageWithLink()) return diff --git a/experimental/libbox/deprecated.go b/experimental/libbox/deprecated.go index b953014f94..f85b774712 100644 --- a/experimental/libbox/deprecated.go +++ b/experimental/libbox/deprecated.go @@ -4,27 +4,28 @@ 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 - features []deprecated.Note + access sync.Mutex + notes []deprecated.Note } func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) { m.access.Lock() defer m.access.Unlock() - m.features = append(m.features, feature) + m.notes = common.Uniq(append(m.notes, feature)) } func (m *deprecatedManager) Get() []deprecated.Note { m.access.Lock() defer m.access.Unlock() - features := m.features - m.features = nil - return features + notes := m.notes + m.notes = nil + return notes } var _ = deprecated.Note(DeprecatedNote{}) From 8df0aa5719a448a64139d2b33d200b3c7978920b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 7 Nov 2024 19:55:37 +0800 Subject: [PATCH 0091/1272] Downgrade NDK to r26d --- cmd/internal/build_shared/sdk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index 9beaa914aa..e089a5250b 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -58,7 +58,7 @@ func FindSDK() { } func findNDK() bool { - const fixedVersion = "27.2.12479018" + const fixedVersion = "26.3.11579264" const versionFile = "source.properties" if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { androidNDKPath = fixedPath From f1c76c4dde81b40effe651cf895a58b3decb6be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 8 Nov 2024 20:30:50 +0800 Subject: [PATCH 0092/1272] Fix deprecated version check --- experimental/deprecated/constants.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 34ea5fc3f3..44918857d4 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -24,10 +24,10 @@ func (n Note) Impending() bool { return false } versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion) - if versionMinor < 0 { + if semver.Prerelease("v"+C.Version) == "" && versionMinor > 0 { panic("invalid deprecated note: " + n.Name) } - return versionMinor <= 1 + return versionMinor >= -1 } func (n Note) Message() string { From 84a102a6ef40adaccd0865674024951b73c91961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 9 Nov 2024 12:26:49 +0800 Subject: [PATCH 0093/1272] Fix mux stream accept --- go.mod | 14 +++++++------- go.sum | 30 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index cd23034f9b..99b22b0405 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.5.0 github.com/sagernet/sing-dns v0.3.0 - github.com/sagernet/sing-mux v0.2.0 + github.com/sagernet/sing-mux v0.2.1 github.com/sagernet/sing-quic v0.3.0-rc.2 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 @@ -44,11 +44,11 @@ require ( github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.29.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/mod v0.20.0 - golang.org/x/net v0.30.0 - golang.org/x/sys v0.26.0 + golang.org/x/net v0.31.0 + golang.org/x/sys v0.27.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 @@ -69,7 +69,7 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect - github.com/hashicorp/yamux v0.1.1 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.17.4 // indirect @@ -90,8 +90,8 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect diff --git a/go.sum b/go.sum index 5406c74650..01313fa652 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ 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/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +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/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-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= @@ -119,8 +119,8 @@ github.com/sagernet/sing v0.5.0 h1:soo2wVwLcieKWWKIksFNK6CCAojUgAppqQVwyRYGkEM= github.com/sagernet/sing v0.5.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.3.0 h1:uHCIlbCwBxALJwXcEK1d75d7t3vzCSVEQsPfZR1cxQE= github.com/sagernet/sing-dns v0.3.0/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= -github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= -github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= +github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo= +github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE= github.com/sagernet/sing-quic v0.3.0-rc.2 h1:7vcC4bdS1GBJzHZhfmJiH0CfzQ4mYLUW51Z2RNHcGwc= github.com/sagernet/sing-quic v0.3.0-rc.2/go.mod h1:3UOq51WVqzra7eCgod7t4hqnTaOiZzFUci9avMrtOqs= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= @@ -170,18 +170,18 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/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= @@ -190,14 +190,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= 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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From e3e203844e7e50ca3e7c1bab7b5b56afc1e2ebd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:27:28 +0800 Subject: [PATCH 0094/1272] Fix decompile rule-set --- cmd/sing-box/cmd_rule_set_compile.go | 7 +---- cmd/sing-box/cmd_rule_set_convert.go | 3 +- cmd/sing-box/cmd_rule_set_decompile.go | 8 +---- cmd/sing-box/cmd_rule_set_match.go | 15 +++++---- common/srs/binary.go | 42 ++++++++++++-------------- constant/rule.go | 1 + option/rule_set.go | 2 +- route/rule_set_local.go | 15 ++++----- route/rule_set_remote.go | 17 +++++------ 9 files changed, 49 insertions(+), 61 deletions(-) diff --git a/cmd/sing-box/cmd_rule_set_compile.go b/cmd/sing-box/cmd_rule_set_compile.go index 4fae4d99e5..0c44a2a1ed 100644 --- a/cmd/sing-box/cmd_rule_set_compile.go +++ b/cmd/sing-box/cmd_rule_set_compile.go @@ -6,7 +6,6 @@ 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/common/json" @@ -56,10 +55,6 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - ruleSet, err := plainRuleSet.Upgrade() - if err != nil { - return err - } var outputPath string if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput { if strings.HasSuffix(sourcePath, ".json") { @@ -74,7 +69,7 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - err = srs.Write(outputFile, ruleSet, plainRuleSet.Version == C.RuleSetVersion2) + err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version) if err != nil { outputFile.Close() os.Remove(outputPath) diff --git a/cmd/sing-box/cmd_rule_set_convert.go b/cmd/sing-box/cmd_rule_set_convert.go index 8d2092ea28..320149cf0d 100644 --- a/cmd/sing-box/cmd_rule_set_convert.go +++ b/cmd/sing-box/cmd_rule_set_convert.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard" "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" E "github.com/sagernet/sing/common/exceptions" @@ -77,7 +78,7 @@ func convertRuleSet(sourcePath string) error { return err } defer outputFile.Close() - err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, true) + err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2) if err != nil { outputFile.Close() os.Remove(outputPath) diff --git a/cmd/sing-box/cmd_rule_set_decompile.go b/cmd/sing-box/cmd_rule_set_decompile.go index 02af03dd67..10a67bcf19 100644 --- a/cmd/sing-box/cmd_rule_set_decompile.go +++ b/cmd/sing-box/cmd_rule_set_decompile.go @@ -6,9 +6,7 @@ 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/common/json" "github.com/spf13/cobra" @@ -48,14 +46,10 @@ func decompileRuleSet(sourcePath string) error { return err } } - plainRuleSet, err := srs.Read(reader, true) + ruleSet, err := srs.Read(reader, true) if err != nil { return err } - ruleSet := option.PlainRuleSetCompat{ - Version: C.RuleSetVersion1, - Options: plainRuleSet, - } var outputPath string if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput { if strings.HasSuffix(sourcePath, ".srs") { diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index 937458f2c6..7cded86ecf 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -55,26 +55,25 @@ func ruleSetMatch(sourcePath string, domain string) error { if err != nil { return E.Cause(err, "read rule-set") } - var plainRuleSet option.PlainRuleSet + var ruleSet option.PlainRuleSetCompat switch flagRuleSetMatchFormat { case C.RuleSetFormatSource: - var compat option.PlainRuleSetCompat - compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) - if err != nil { - return err - } - plainRuleSet, err = compat.Upgrade() + ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) if err != nil { return err } case C.RuleSetFormatBinary: - plainRuleSet, err = srs.Read(bytes.NewReader(content), false) + ruleSet, err = srs.Read(bytes.NewReader(content), false) if err != nil { return err } default: return E.New("unknown rule-set format: ", flagRuleSetMatchFormat) } + plainRuleSet, err := ruleSet.Upgrade() + if err != nil { + return err + } ipAddress := M.ParseAddr(domain) var metadata adapter.InboundContext if ipAddress.IsValid() { diff --git a/common/srs/binary.go b/common/srs/binary.go index 489dc22e2f..dd670fc83e 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -41,7 +41,7 @@ const ( ruleItemFinal uint8 = 0xFF ) -func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) { +func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetCompat, err error) { var magicBytes [3]byte _, err = io.ReadFull(reader, magicBytes[:]) if err != nil { @@ -54,10 +54,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro var version uint8 err = binary.Read(reader, binary.BigEndian, &version) if err != nil { - return ruleSet, err + return ruleSetCompat, err } - if version > C.RuleSetVersion2 { - return ruleSet, E.New("unsupported version: ", version) + if version > C.RuleSetVersionCurrent { + return ruleSetCompat, E.New("unsupported version: ", version) } compressReader, err := zlib.NewReader(reader) if err != nil { @@ -68,9 +68,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro if err != nil { return } - ruleSet.Rules = make([]option.HeadlessRule, length) + ruleSetCompat.Version = version + ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length) for i := uint64(0); i < length; i++ { - ruleSet.Rules[i], err = readRule(bReader, recover) + ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover) if err != nil { err = E.Cause(err, "read rule[", i, "]") return @@ -79,18 +80,12 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro return } -func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) error { +func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8) error { _, err := writer.Write(MagicBytes[:]) if err != nil { return err } - var version uint8 - if generateUnstable { - version = C.RuleSetVersion2 - } else { - version = C.RuleSetVersion1 - } - err = binary.Write(writer, binary.BigEndian, version) + err = binary.Write(writer, binary.BigEndian, generateVersion) if err != nil { return err } @@ -104,7 +99,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) return err } for _, rule := range ruleSet.Rules { - err = writeRule(bWriter, rule, generateUnstable) + err = writeRule(bWriter, rule, generateVersion) if err != nil { return err } @@ -135,12 +130,12 @@ func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err return } -func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateUnstable bool) error { +func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateVersion uint8) error { switch rule.Type { case C.RuleTypeDefault: - return writeDefaultRule(writer, rule.DefaultOptions, generateUnstable) + return writeDefaultRule(writer, rule.DefaultOptions, generateVersion) case C.RuleTypeLogical: - return writeLogicalRule(writer, rule.LogicalOptions, generateUnstable) + return writeLogicalRule(writer, rule.LogicalOptions, generateVersion) default: panic("unknown rule type: " + rule.Type) } @@ -240,7 +235,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea } } -func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateUnstable bool) error { +func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateVersion uint8) error { err := binary.Write(writer, binary.BigEndian, uint8(0)) if err != nil { return err @@ -264,7 +259,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen if err != nil { return err } - err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, !generateUnstable).Write(writer) + err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, generateVersion == C.RuleSetVersion1).Write(writer) if err != nil { return err } @@ -354,6 +349,9 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen } } if len(rule.AdGuardDomain) > 0 { + if generateVersion < C.RuleSetVersion2 { + return E.New("AdGuard rule items is only supported in version 2 or later") + } err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain) if err != nil { return err @@ -457,7 +455,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo return } -func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateUnstable bool) error { +func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateVersion uint8) error { err := binary.Write(writer, binary.BigEndian, uint8(1)) if err != nil { return err @@ -478,7 +476,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu return err } for _, rule := range logicalRule.Rules { - err = writeRule(writer, rule, generateUnstable) + err = writeRule(writer, rule, generateVersion) if err != nil { return err } diff --git a/constant/rule.go b/constant/rule.go index 718e79a5e6..086b95a015 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -21,4 +21,5 @@ const ( const ( RuleSetVersion1 = 1 + iota RuleSetVersion2 + RuleSetVersionCurrent = RuleSetVersion2 ) diff --git a/option/rule_set.go b/option/rule_set.go index d4368de38a..e0f10bf180 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -189,7 +189,7 @@ func (r LogicalHeadlessRule) IsValid() bool { } type _PlainRuleSetCompat struct { - Version int `json:"version"` + Version uint8 `json:"version"` Options PlainRuleSet `json:"-"` } diff --git a/route/rule_set_local.go b/route/rule_set_local.go index 4485fbad41..c3ecf9ca28 100644 --- a/route/rule_set_local.go +++ b/route/rule_set_local.go @@ -95,33 +95,34 @@ func (s *LocalRuleSet) StartContext(ctx context.Context, startContext *adapter.H } func (s *LocalRuleSet) reloadFile(path string) error { - var plainRuleSet option.PlainRuleSet + var ruleSet option.PlainRuleSetCompat switch s.fileFormat { case C.RuleSetFormatSource, "": content, err := os.ReadFile(path) if err != nil { return err } - compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content) - if err != nil { - return err - } - plainRuleSet, err = compat.Upgrade() + ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) if err != nil { return err } + case C.RuleSetFormatBinary: setFile, err := os.Open(path) if err != nil { return err } - plainRuleSet, err = srs.Read(setFile, false) + ruleSet, err = srs.Read(setFile, false) if err != nil { return err } default: return E.New("unknown rule-set format: ", s.fileFormat) } + plainRuleSet, err := ruleSet.Upgrade() + if err != nil { + return err + } return s.reloadRules(plainRuleSet.Rules) } diff --git a/route/rule_set_remote.go b/route/rule_set_remote.go index 115416092d..5045bf2dab 100644 --- a/route/rule_set_remote.go +++ b/route/rule_set_remote.go @@ -159,28 +159,27 @@ func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSet func (s *RemoteRuleSet) loadBytes(content []byte) error { var ( - plainRuleSet option.PlainRuleSet - err error + ruleSet option.PlainRuleSetCompat + err error ) switch s.options.Format { case C.RuleSetFormatSource: - var compat option.PlainRuleSetCompat - compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) - if err != nil { - return err - } - plainRuleSet, err = compat.Upgrade() + ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) if err != nil { return err } case C.RuleSetFormatBinary: - plainRuleSet, err = srs.Read(bytes.NewReader(content), false) + ruleSet, err = srs.Read(bytes.NewReader(content), false) if err != nil { return err } default: return E.New("unknown rule-set format: ", s.options.Format) } + plainRuleSet, err := ruleSet.Upgrade() + if err != nil { + return err + } rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) for i, ruleOptions := range plainRuleSet.Rules { rules[i], err = NewHeadlessRule(s.router, ruleOptions) From 1e076339141a599fe41210114d9f029515933993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 12:56:31 +0800 Subject: [PATCH 0095/1272] Downgrade NDK to 26.2.11394342 --- cmd/internal/build_shared/sdk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index e089a5250b..a664abee56 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -58,7 +58,7 @@ func FindSDK() { } func findNDK() bool { - const fixedVersion = "26.3.11579264" + const fixedVersion = "26.2.11394342" const versionFile = "source.properties" if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { androidNDKPath = fixedPath From de14337b4bcf91ab4233c8488f8c7295f4dc8ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Nov 2024 14:06:25 +0800 Subject: [PATCH 0096/1272] Fix deprecated check --- experimental/deprecated/constants.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 44918857d4..fd785dbc93 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -1,6 +1,7 @@ package deprecated import ( + "github.com/sagernet/sing-box/common/badversion" C "github.com/sagernet/sing-box/constant" F "github.com/sagernet/sing/common/format" @@ -23,11 +24,12 @@ func (n Note) Impending() bool { if !semver.IsValid("v" + C.Version) { return false } - versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion) - if semver.Prerelease("v"+C.Version) == "" && versionMinor > 0 { + versionCurrent := badversion.Parse(C.Version) + versionMinor := badversion.Parse(n.ScheduledVersion).Minor - versionCurrent.Minor + if versionCurrent.PreReleaseIdentifier == "" && versionMinor < 0 { panic("invalid deprecated note: " + n.Name) } - return versionMinor >= -1 + return versionMinor <= 1 } func (n Note) Message() string { @@ -49,6 +51,7 @@ var OptionBadMatchSource = Note{ 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", } @@ -75,6 +78,7 @@ var OptionTUNAddressX = Note{ 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", } From 0bb113203493ed4ab39b02cbabbe39f538c58651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 Nov 2024 13:54:31 +0800 Subject: [PATCH 0097/1272] selector: Fix crash before start --- outbound/selector.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/outbound/selector.go b/outbound/selector.go index e801daeadc..7326f58f0f 100644 --- a/outbound/selector.go +++ b/outbound/selector.go @@ -97,7 +97,11 @@ func (s *Selector) Start() error { } func (s *Selector) Now() string { - return s.selected.Tag() + selected := s.selected + if selected == nil { + return s.tags[0] + } + return selected.Tag() } func (s *Selector) All() []string { From 122be275b0a62528a983fd17deb65e3ba8816b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 14 Nov 2024 14:11:32 +0800 Subject: [PATCH 0098/1272] release: Notarize macos standalone manually with --no-s3-acceleration --- Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index d072085fb7..ae8e0b24cf 100644 --- a/Makefile +++ b/Makefile @@ -96,11 +96,7 @@ upload_android: release_android: lib_android update_android_version build_android upload_android publish_android: - cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle - -publish_android_appcenter: - cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease - + cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop # TODO: find why and remove `-destination 'generic/platform=iOS'` build_ios: @@ -147,9 +143,13 @@ build_macos_dmg: --hide-extension "SFM.app" \ --app-drop-link 0 0 \ --skip-jenkins \ - --notarize "notarytool-password" \ "../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app" +notarize_macos_dmg: + xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \ + --keychain-profile "notarytool-password" \ + --no-s3-acceleration + upload_macos_dmg: cd dist/SFM && \ cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \ @@ -164,7 +164,7 @@ upload_macos_dsyms: cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \ ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip" -release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg upload_macos_dsyms +release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms build_tvos: cd ../sing-box-for-apple && \ From 97c47e72c4dd334ee92ea6d247d3b02043644efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Nov 2024 12:51:58 +0800 Subject: [PATCH 0099/1272] Update dependencies --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- transport/wireguard/device_stack.go | 12 ++++++++++++ 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 99b22b0405..29d2733139 100644 --- a/go.mod +++ b/go.mod @@ -24,17 +24,17 @@ require ( github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.4 - github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f + github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.5.0 + github.com/sagernet/sing v0.5.1 github.com/sagernet/sing-dns v0.3.0 github.com/sagernet/sing-mux v0.2.1 - github.com/sagernet/sing-quic v0.3.0-rc.2 + github.com/sagernet/sing-quic v0.3.1 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 - github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.4.0-rc.5 + github.com/sagernet/sing-shadowtls v0.1.5 + github.com/sagernet/sing-tun v0.4.1 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 @@ -66,7 +66,7 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect - github.com/google/btree v1.1.2 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/hashicorp/yamux v0.1.2 // indirect @@ -92,7 +92,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/text v0.20.0 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index 01313fa652..b2d1b8c9ef 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +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/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= @@ -104,8 +104,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.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY= github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= +github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 h1:RxEz7LhPNiF/gX/Hg+OXr5lqsM9iVAgmaK1L1vzlDRM= +github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= 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= @@ -115,22 +115,22 @@ github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/ 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.5.0 h1:soo2wVwLcieKWWKIksFNK6CCAojUgAppqQVwyRYGkEM= -github.com/sagernet/sing v0.5.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y= +github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.3.0 h1:uHCIlbCwBxALJwXcEK1d75d7t3vzCSVEQsPfZR1cxQE= github.com/sagernet/sing-dns v0.3.0/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo= github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE= -github.com/sagernet/sing-quic v0.3.0-rc.2 h1:7vcC4bdS1GBJzHZhfmJiH0CfzQ4mYLUW51Z2RNHcGwc= -github.com/sagernet/sing-quic v0.3.0-rc.2/go.mod h1:3UOq51WVqzra7eCgod7t4hqnTaOiZzFUci9avMrtOqs= +github.com/sagernet/sing-quic v0.3.1 h1:kLg2n4JPnuzUPg7myJGbfGVJGeXiccXfV+PhXIlkSEc= +github.com/sagernet/sing-quic v0.3.1/go.mod h1:g8b5Fj88KRM0H9lpKAxJj0EpkL/Yk06qXJAG7FuZd2I= 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.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= -github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.4.0-rc.5 h1:d4o36GbVZtsucpt5On7CjTFSx4zx82DL8OlGR7rX7vY= -github.com/sagernet/sing-tun v0.4.0-rc.5/go.mod h1:rWu1ZC2lj4+UOOSNiUWjsY0y2fJcOHnLTMGna2n5P2o= +github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo= +github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4= +github.com/sagernet/sing-tun v0.4.1 h1:VKjKX93fUlEbYiabX3OhnqEylYzrcYcQthnaO0u2cI0= +github.com/sagernet/sing-tun v0.4.1/go.mod h1:hHNVxjL7X0vNjkfN0GTkMk2CrGdgk0zZMEinkmz8XeM= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= @@ -198,8 +198,8 @@ 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.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index d5770419e2..61286e6a9a 100644 --- a/transport/wireguard/device_stack.go +++ b/transport/wireguard/device_stack.go @@ -252,6 +252,9 @@ func (ep *wireEndpoint) MTU() uint32 { return ep.mtu } +func (ep *wireEndpoint) SetMTU(mtu uint32) { +} + func (ep *wireEndpoint) MaxHeaderLength() uint16 { return 0 } @@ -260,6 +263,9 @@ func (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress { return "" } +func (ep *wireEndpoint) SetLinkAddress(addr tcpip.LinkAddress) { +} + func (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities { return stack.CapabilityRXChecksumOffload } @@ -297,3 +303,9 @@ func (ep *wireEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Er } return list.Len(), nil } + +func (ep *wireEndpoint) Close() { +} + +func (ep *wireEndpoint) SetOnCloseAction(f func()) { +} From 1d81996ceb75dc072015028882d77abdfc35cd26 Mon Sep 17 00:00:00 2001 From: zeetex Date: Mon, 18 Nov 2024 13:24:14 +0800 Subject: [PATCH 0100/1272] Fix reloading of tls.certificate_path, tls.key_path and tls.ech.key_path --- common/tls/ech_server.go | 4 ++-- common/tls/std_server.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/tls/ech_server.go b/common/tls/ech_server.go index ac3e6279c3..00b24fd5d5 100644 --- a/common/tls/ech_server.go +++ b/common/tls/ech_server.go @@ -98,7 +98,7 @@ func (c *echServerConfig) startWatcher() error { return err } c.watcher = watcher - return nil + return c.watcher.Start() } func (c *echServerConfig) credentialsUpdated(path string) error { @@ -232,7 +232,7 @@ func NewECHServer(ctx context.Context, logger log.Logger, options option.Inbound var echKey []byte if len(options.ECH.Key) > 0 { echKey = []byte(strings.Join(options.ECH.Key, "\n")) - } else if options.KeyPath != "" { + } else if options.ECH.KeyPath != "" { content, err := os.ReadFile(options.ECH.KeyPath) if err != nil { return nil, E.Cause(err, "read ECH key") diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 7001bd3a04..82ea9134f7 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -107,7 +107,7 @@ func (c *STDServerConfig) startWatcher() error { return err } c.watcher = watcher - return nil + return c.watcher.Start() } func (c *STDServerConfig) certificateUpdated(path string) error { From e58b549d0f63a343b917f9930638a44b54e5ef1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Nov 2024 19:02:53 +0800 Subject: [PATCH 0101/1272] Fix "Fix reloading of tls.certificate_path, tls.key_path and tls.ech.key_path" --- common/tls/ech_server.go | 6 +++++- common/tls/std_server.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/common/tls/ech_server.go b/common/tls/ech_server.go index 00b24fd5d5..c38fe673d8 100644 --- a/common/tls/ech_server.go +++ b/common/tls/ech_server.go @@ -97,8 +97,12 @@ func (c *echServerConfig) startWatcher() error { if err != nil { return err } + err = c.watcher.Start() + if err != nil { + return err + } c.watcher = watcher - return c.watcher.Start() + return nil } func (c *echServerConfig) credentialsUpdated(path string) error { diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 82ea9134f7..e894dadee1 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -106,8 +106,12 @@ func (c *STDServerConfig) startWatcher() error { if err != nil { return err } + err = c.watcher.Start() + if err != nil { + return err + } c.watcher = watcher - return c.watcher.Start() + return nil } func (c *STDServerConfig) certificateUpdated(path string) error { From af58e3bec0cb586d5ea64e9a8ebbeacd198d7607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 20 Nov 2024 10:52:26 +0800 Subject: [PATCH 0102/1272] Fix debug listener --- debug_http.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug_http.go b/debug_http.go index df356c2ed4..3215977821 100644 --- a/debug_http.go +++ b/debug_http.go @@ -46,7 +46,7 @@ func applyDebugListenOption(options option.DebugOptions) { encoder := json.NewEncoder(writer) encoder.SetIndent("", " ") - encoder.Encode(memObject) + encoder.Encode(&memObject) }) r.Route("/pprof", func(r chi.Router) { r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { From 6be07ed51f4be1f51caa95d2543d379f28c480ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 20 Nov 2024 11:32:43 +0800 Subject: [PATCH 0103/1272] Fix start watcher --- common/tls/ech_server.go | 2 +- common/tls/std_server.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/tls/ech_server.go b/common/tls/ech_server.go index c38fe673d8..d41783fe31 100644 --- a/common/tls/ech_server.go +++ b/common/tls/ech_server.go @@ -97,7 +97,7 @@ func (c *echServerConfig) startWatcher() error { if err != nil { return err } - err = c.watcher.Start() + err = watcher.Start() if err != nil { return err } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index e894dadee1..8eab87da0f 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -106,7 +106,7 @@ func (c *STDServerConfig) startWatcher() error { if err != nil { return err } - err = c.watcher.Start() + err = watcher.Start() if err != nil { return err } From 17019f172910cb32bc56826602a376874c1b9f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Nov 2024 12:56:52 +0800 Subject: [PATCH 0104/1272] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/clients/android b/clients/android index 45a1f5f0aa..ea460ea5d1 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 45a1f5f0aa2b5cf7cb74720a2fa407fdaa591bbf +Subproject commit ea460ea5d11ac88b3f9b1a7c1b6bd80a08b87f84 diff --git a/clients/apple b/clients/apple index c7d9b49de7..286f9717cb 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit c7d9b49de799fe31e256bcdb68f80c50e3aee41d +Subproject commit 286f9717cbde78a8102c20a27bb089faf4ef4fc5 diff --git a/docs/changelog.md b/docs/changelog.md index e70ec08b96..0c66a89e01 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,12 @@ icon: material/alert-decagram --- +### 1.10.2 + +* Add deprecated warnings +* Fix proxying websocket connections in HTTP/mixed inbounds +* Fixes and improvements + ### 1.10.1 * Fixes and improvements @@ -33,7 +39,7 @@ Important changes since 1.9: The new auto-redirect feature allows TUN to automatically configure connection redirection to improve proxy performance. -When auto-redirect is enabled, new route address set options will allow you to +When auto-redirect is enabled, new route address set options will allow you to automatically configure destination IP CIDR rules from a specified rule set to the firewall. Specified or unspecified destinations will bypass the sing-box routes to get better performance From d9102ba5999e28b8d1f9b0b6dc3d9c9b6a24b6d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 23 Nov 2024 12:16:46 +0800 Subject: [PATCH 0105/1272] Fix build on bsd systems --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 29d2733139..ff0caf6e52 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.4 - github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 + github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.5.1 @@ -34,7 +34,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.5 - github.com/sagernet/sing-tun v0.4.1 + github.com/sagernet/sing-tun v0.4.2 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index b2d1b8c9ef..b0e0f95d0f 100644 --- a/go.sum +++ b/go.sum @@ -104,8 +104,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.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY= github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= -github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 h1:RxEz7LhPNiF/gX/Hg+OXr5lqsM9iVAgmaK1L1vzlDRM= -github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= +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/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= @@ -129,8 +129,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo= github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4= -github.com/sagernet/sing-tun v0.4.1 h1:VKjKX93fUlEbYiabX3OhnqEylYzrcYcQthnaO0u2cI0= -github.com/sagernet/sing-tun v0.4.1/go.mod h1:hHNVxjL7X0vNjkfN0GTkMk2CrGdgk0zZMEinkmz8XeM= +github.com/sagernet/sing-tun v0.4.2 h1:GCP7TI/gwDH/iFIugYS3WcVhCcbDE6qwAbjYQ5W/m+E= +github.com/sagernet/sing-tun v0.4.2/go.mod h1:1WQVMelJQjrtlzhzHwwPTSa7n41b3zSWP2DeJqWxruk= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= From 1086d5e6658ec524e82b9dd89143a7cbe7d1b987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 23 Nov 2024 12:04:08 +0800 Subject: [PATCH 0106/1272] Fix test tags --- .github/workflows/debug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml index 804eced92b..09499732ba 100644 --- a/.github/workflows/debug.yml +++ b/.github/workflows/debug.yml @@ -204,7 +204,7 @@ jobs: GOARM: ${{ matrix.goarm }} GOMIPS: ${{ matrix.gomips }} CGO_ENABLED: 0 - TAGS: with_clash_api,with_quic + TAGS: with_gvisor,with_dhcp,with_wireguard,with_clash_api,with_quic,with_utls,with_ech steps: - name: Checkout uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 From 0e9129ee3f0ca8e08f6d59c3af4a71340c0449a3 Mon Sep 17 00:00:00 2001 From: Zephyruso <176294927+Zephyruso@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:37:03 +0800 Subject: [PATCH 0107/1272] clashapi: Add mode list --- experimental/clashapi/configs.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/experimental/clashapi/configs.go b/experimental/clashapi/configs.go index 9d1e6109d2..8ae1d258bb 100644 --- a/experimental/clashapi/configs.go +++ b/experimental/clashapi/configs.go @@ -18,17 +18,19 @@ func configRouter(server *Server, logFactory log.Factory) http.Handler { } type configSchema struct { - Port int `json:"port"` - SocksPort int `json:"socks-port"` - RedirPort int `json:"redir-port"` - TProxyPort int `json:"tproxy-port"` - MixedPort int `json:"mixed-port"` - AllowLan bool `json:"allow-lan"` - BindAddress string `json:"bind-address"` - Mode string `json:"mode"` - LogLevel string `json:"log-level"` - IPv6 bool `json:"ipv6"` - Tun map[string]any `json:"tun"` + Port int `json:"port"` + SocksPort int `json:"socks-port"` + RedirPort int `json:"redir-port"` + TProxyPort int `json:"tproxy-port"` + MixedPort int `json:"mixed-port"` + AllowLan bool `json:"allow-lan"` + BindAddress string `json:"bind-address"` + Mode string `json:"mode"` + // sing-box added + ModeList []string `json:"mode-list"` + LogLevel string `json:"log-level"` + IPv6 bool `json:"ipv6"` + Tun map[string]any `json:"tun"` } func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWriter, r *http.Request) { @@ -41,6 +43,7 @@ func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWrit } render.JSON(w, r, &configSchema{ Mode: server.mode, + ModeList: server.modeList, BindAddress: "*", LogLevel: log.FormatLevel(logLevel), }) From 7f168c5ec672512768315fe1902d9f1e243e7021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 27 Nov 2024 10:34:20 +0800 Subject: [PATCH 0108/1272] release: Clean before iOS build --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index ae8e0b24cf..e35afa096c 100644 --- a/Makefile +++ b/Makefile @@ -99,9 +99,11 @@ publish_android: cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop # TODO: find why and remove `-destination 'generic/platform=iOS'` +# TODO: remove xcode clean when fix control widget fixed 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 upload_ios_app_store: From effea5a2b3736e5d850e8ca4d97f0ea7062c5e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 26 Nov 2024 12:44:24 +0800 Subject: [PATCH 0109/1272] Update quic-go to v0.48.2 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ff0caf6e52..42d0b8f304 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.4 github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff - github.com/sagernet/quic-go v0.48.1-beta.1 + github.com/sagernet/quic-go v0.48.2-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.5.1 github.com/sagernet/sing-dns v0.3.0 diff --git a/go.sum b/go.sum index b0e0f95d0f..303654a1de 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,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.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AEpT94Z0vwo= -github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= +github.com/sagernet/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg= +github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= 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= From 3cc0e87cfbcce02362c14544ca63d378b2957482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 27 Nov 2024 10:37:55 +0800 Subject: [PATCH 0110/1272] 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 ea460ea5d1..cff12c57dd 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit ea460ea5d11ac88b3f9b1a7c1b6bd80a08b87f84 +Subproject commit cff12c57dd3ace2d2776ad607fa0c6bb93873649 diff --git a/clients/apple b/clients/apple index 286f9717cb..fa107e3b7c 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 286f9717cbde78a8102c20a27bb089faf4ef4fc5 +Subproject commit fa107e3b7ca03e1acc5d250aabf82da6ddff997c diff --git a/docs/changelog.md b/docs/changelog.md index 0c66a89e01..3a440ccfa3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +### 1.10.3 + +* Fixes and improvements + ### 1.10.2 * Add deprecated warnings From 5a661cde678ecd51f70abb713d6291460c0468c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 27 Nov 2024 18:05:30 +0800 Subject: [PATCH 0111/1272] clashapi: Remove traffic loop --- experimental/clashapi/server.go | 16 ++++--- .../clashapi/trafficontrol/manager.go | 46 +------------------ experimental/libbox/command_status.go | 15 +++++- 3 files changed, 24 insertions(+), 53 deletions(-) diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 889d191e0e..f47e08cf26 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -316,27 +316,31 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, tick := time.NewTicker(time.Second) defer tick.Stop() buf := &bytes.Buffer{} - var err error + var ( + uploadTotal int64 + doanloadTotal int64 + err error + ) for range tick.C { buf.Reset() - up, down := trafficManager.Now() + uploadTotalNew, downloadTotalNew := trafficManager.Total() if err := json.NewEncoder(buf).Encode(Traffic{ - Up: up, - Down: down, + Up: uploadTotalNew - uploadTotal, + Down: downloadTotalNew - doanloadTotal, }); err != nil { break } - if conn == nil { _, err = w.Write(buf.Bytes()) w.(http.Flusher).Flush() } else { err = wsutil.WriteServerText(conn, buf.Bytes()) } - if err != nil { break } + uploadTotal = uploadTotalNew + doanloadTotal = downloadTotalNew } } } diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index 9b22f1e3d9..757ffdf917 100644 --- a/experimental/clashapi/trafficontrol/manager.go +++ b/experimental/clashapi/trafficontrol/manager.go @@ -16,30 +16,18 @@ import ( ) type Manager struct { - uploadTemp atomic.Int64 - downloadTemp atomic.Int64 - uploadBlip atomic.Int64 - downloadBlip atomic.Int64 uploadTotal atomic.Int64 downloadTotal atomic.Int64 connections compatible.Map[uuid.UUID, Tracker] closedConnectionsAccess sync.Mutex closedConnections list.List[TrackerMetadata] - ticker *time.Ticker - done chan struct{} // process *process.Process memory uint64 } func NewManager() *Manager { - manager := &Manager{ - ticker: time.NewTicker(time.Second), - done: make(chan struct{}), - // process: &process.Process{Pid: int32(os.Getpid())}, - } - go manager.handle() - return manager + return &Manager{} } func (m *Manager) Join(c Tracker) { @@ -61,19 +49,13 @@ func (m *Manager) Leave(c Tracker) { } func (m *Manager) PushUploaded(size int64) { - m.uploadTemp.Add(size) m.uploadTotal.Add(size) } func (m *Manager) PushDownloaded(size int64) { - m.downloadTemp.Add(size) m.downloadTotal.Add(size) } -func (m *Manager) Now() (up int64, down int64) { - return m.uploadBlip.Load(), m.downloadBlip.Load() -} - func (m *Manager) Total() (up int64, down int64) { return m.uploadTotal.Load(), m.downloadTotal.Load() } @@ -127,36 +109,10 @@ func (m *Manager) Snapshot() *Snapshot { } func (m *Manager) ResetStatistic() { - m.uploadTemp.Store(0) - m.uploadBlip.Store(0) m.uploadTotal.Store(0) - m.downloadTemp.Store(0) - m.downloadBlip.Store(0) m.downloadTotal.Store(0) } -func (m *Manager) handle() { - var uploadTemp int64 - var downloadTemp int64 - for { - select { - case <-m.done: - return - case <-m.ticker.C: - } - uploadTemp = m.uploadTemp.Swap(0) - downloadTemp = m.downloadTemp.Swap(0) - m.uploadBlip.Store(uploadTemp) - m.downloadBlip.Store(downloadTemp) - } -} - -func (m *Manager) Close() error { - m.ticker.Stop() - close(m.done) - return nil -} - type Snapshot struct { Download int64 Upload int64 diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go index 4ab09d4b28..07e6693499 100644 --- a/experimental/libbox/command_status.go +++ b/experimental/libbox/command_status.go @@ -34,7 +34,6 @@ func (s *CommandServer) readStatus() StatusMessage { if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil { message.TrafficAvailable = true trafficManager := clashServer.(*clashapi.Server).TrafficManager() - message.Uplink, message.Downlink = trafficManager.Now() message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() message.ConnectionsIn = int32(trafficManager.ConnectionsLen()) } @@ -52,8 +51,20 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error { ticker := time.NewTicker(time.Duration(interval)) defer ticker.Stop() ctx := connKeepAlive(conn) + var ( + status StatusMessage + uploadTotal int64 + downloadTotal int64 + ) for { - err = binary.Write(conn, binary.BigEndian, s.readStatus()) + status = s.readStatus() + upload := status.UplinkTotal - uploadTotal + download := status.DownlinkTotal - downloadTotal + uploadTotal = status.UplinkTotal + downloadTotal = status.DownlinkTotal + status.Uplink = upload + status.Downlink = download + err = binary.Write(conn, binary.BigEndian, status) if err != nil { return err } From 6e7ecbd4f53fd8dbdc9d7f43a76a9ef209bef51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 30 Nov 2024 12:20:37 +0800 Subject: [PATCH 0112/1272] Fix wireguard listen --- 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 b4bca55e9d..f4fd99fbca 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -179,7 +179,7 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address)) + return d.udpListener.ListenPacket(context.Background(), network, address) } func trackConn(conn net.Conn, err error) (net.Conn, error) { From 558585b01dc225b6ebd244a64d229233037d9103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Dec 2024 14:10:33 +0800 Subject: [PATCH 0113/1272] release: Set upload threads to 5 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e35afa096c..70627aead0 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ release: dist/*_amd64.pkg.tar.zst \ dist/*_arm64.pkg.tar.zst \ dist/release - ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release + ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release rm -r dist/release release_repo: @@ -90,7 +90,7 @@ 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 - ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android + ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android rm -rf dist/release_android release_android: lib_android update_android_version build_android upload_android From 1e787cb60723e9fe848aa6d89d5e5f1350de4fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Dec 2024 21:43:11 +0800 Subject: [PATCH 0114/1272] Fix initial traffic value --- experimental/clashapi/server.go | 16 +++++++--------- experimental/libbox/command_status.go | 22 ++++++++++------------ 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index f47e08cf26..909583b361 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -316,18 +316,15 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, tick := time.NewTicker(time.Second) defer tick.Stop() buf := &bytes.Buffer{} - var ( - uploadTotal int64 - doanloadTotal int64 - err error - ) + uploadTotal, downloadTotal := trafficManager.Total() for range tick.C { buf.Reset() uploadTotalNew, downloadTotalNew := trafficManager.Total() - if err := json.NewEncoder(buf).Encode(Traffic{ + err := json.NewEncoder(buf).Encode(Traffic{ Up: uploadTotalNew - uploadTotal, - Down: downloadTotalNew - doanloadTotal, - }); err != nil { + Down: downloadTotalNew - downloadTotal, + }) + if err != nil { break } if conn == nil { @@ -339,8 +336,9 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, if err != nil { break } + uploadTotal = uploadTotalNew - doanloadTotal = downloadTotalNew + downloadTotal = downloadTotalNew } } } diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go index 07e6693499..a6280d0f7c 100644 --- a/experimental/libbox/command_status.go +++ b/experimental/libbox/command_status.go @@ -51,19 +51,10 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error { ticker := time.NewTicker(time.Duration(interval)) defer ticker.Stop() ctx := connKeepAlive(conn) - var ( - status StatusMessage - uploadTotal int64 - downloadTotal int64 - ) + status := s.readStatus() + uploadTotal := status.UplinkTotal + downloadTotal := status.DownlinkTotal for { - status = s.readStatus() - upload := status.UplinkTotal - uploadTotal - download := status.DownlinkTotal - downloadTotal - uploadTotal = status.UplinkTotal - downloadTotal = status.DownlinkTotal - status.Uplink = upload - status.Downlink = download err = binary.Write(conn, binary.BigEndian, status) if err != nil { return err @@ -73,6 +64,13 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error { 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 } } From 8c3a98faa236d1f6e9daeb48fcb9e7f86f9fd4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 5 Dec 2024 17:40:15 +0800 Subject: [PATCH 0115/1272] wireguard: Fix set reserved --- outbound/wireguard.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/outbound/wireguard.go b/outbound/wireguard.go index 3ae3f63b36..6287684923 100644 --- a/outbound/wireguard.go +++ b/outbound/wireguard.go @@ -152,6 +152,13 @@ func (w *WireGuard) start() error { } bind = wireguard.NewClientBind(w.ctx, w, w.listener, isConnect, connectAddr, reserved) } + if w.useStdNetBind || len(w.peers) > 1 { + for _, peer := range w.peers { + if peer.Reserved != [3]uint8{} { + bind.SetReservedForEndpoint(peer.Endpoint, peer.Reserved) + } + } + } err = w.tunDevice.Start() if err != nil { return err From db22f61846ea81296f7b9c392204f65111050578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 5 Dec 2024 19:49:45 +0800 Subject: [PATCH 0116/1272] Update NDK to r28-rc1 --- cmd/internal/build_shared/sdk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index a664abee56..fae57b5370 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -58,7 +58,7 @@ func FindSDK() { } func findNDK() bool { - const fixedVersion = "26.2.11394342" + const fixedVersion = "28.0.12674087" const versionFile = "source.properties" if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { androidNDKPath = fixedPath From 3032317918a21497ab21daed9fecaa1bed23a8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 4 Dec 2024 21:25:16 +0800 Subject: [PATCH 0117/1272] release: Add workflow build --- .github/workflows/build.yml | 613 ++++++++++++++++++++ .github/workflows/debug.yml | 219 ------- .github/workflows/linux.yml | 1 - .goreleaser.yaml | 4 +- cmd/internal/build_libbox/main.go | 20 +- cmd/internal/build_shared/sdk.go | 14 +- cmd/internal/build_shared/tag.go | 13 + cmd/internal/read_tag/main.go | 65 ++- cmd/internal/update_android_version/main.go | 24 +- cmd/internal/update_apple_version/main.go | 16 +- 10 files changed, 745 insertions(+), 244 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/debug.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..55583aa19e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,613 @@ +name: Build + +on: + workflow_dispatch: + inputs: + version: + description: "Version name" + required: true + type: string + prerelease: + description: "Is prerelease" + required: true + type: boolean + default: true + build: + description: "Build type" + required: true + type: choice + default: "All" + options: + - All + - Binary + - Android + - Apple + - app-store + - iOS + - macOS + - tvOS + - macOS-standalone + - publish-android + macos_project_version: + description: "macOS project version" + required: false + type: string + push: + branches: + - main-next + - dev-next + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }} + cancel-in-progress: true + +jobs: + calculate_version: + name: Calculate version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.outputs.outputs.version }} + prerelease: ${{ steps.outputs.outputs.prerelease }} + steps: + - name: Checkout + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + with: + fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ^1.23 + - name: Check input version + if: github.event_name == 'workflow_dispatch' + run: |- + echo "version=${{ inputs.version }}" + echo "prerelease=${{ inputs.prerelease }}" + echo "version=${{ inputs.version }}" >> "$GITHUB_ENV" + echo "prerelease=${{ inputs.prerelease }}" >> "$GITHUB_ENV" + - name: Calculate version + if: github.event_name != 'workflow_dispatch' + run: |- + go run -v ./cmd/internal/read_tag --nightly + - name: Set outputs + id: outputs + run: |- + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT" + build: + name: Build binary + if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' + runs-on: ubuntu-latest + needs: + - calculate_version + strategy: + matrix: + include: + - name: linux_386 + goos: linux + goarch: 386 + - name: linux_amd64 + goos: linux + goarch: amd64 + - name: linux_arm64 + goos: linux + goarch: arm64 + - name: linux_arm + goos: linux + goarch: arm + goarm: 6 + - name: linux_arm_v7 + goos: linux + goarch: arm + goarm: 7 + - name: linux_s390x + goos: linux + goarch: s390x + - name: linux_riscv64 + goos: linux + goarch: riscv64 + - name: linux_mips64le + goos: linux + goarch: mips64le + - name: windows_amd64 + goos: windows + goarch: amd64 + require_legacy_go: true + - name: windows_386 + goos: windows + goarch: 386 + require_legacy_go: true + - name: windows_arm64 + goos: windows + goarch: arm64 + - name: darwin_arm64 + goos: darwin + goarch: arm64 + - name: darwin_amd64 + goos: darwin + goarch: amd64 + require_legacy_go: true + - name: android_arm64 + goos: android + goarch: arm64 + - name: android_arm + goos: android + goarch: arm + goarm: 7 + - name: android_amd64 + goos: android + goarch: amd64 + - name: android_386 + goos: android + goarch: 386 + steps: + - name: Checkout + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + with: + fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ^1.23 + - name: Cache legacy Go + if: matrix.require_legacy_go + id: cache-legacy-go + uses: actions/cache@v4 + with: + path: | + ~/go/go1.20.14 + key: go120 + - name: Setup legacy Go + if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true' + run: |- + wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz + tar -xzf go1.20.14.linux-amd64.tar.gz + mv go $HOME/go/go1.20.14 + - name: Setup Android NDK + if: matrix.goos == 'android' + uses: nttld/setup-ndk@v1 + with: + ndk-version: r28-beta2 + local-cache: true + - name: Setup Goreleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser-pro + version: latest + install-only: true + - name: Extract signing key + run: |- + mkdir -p $HOME/.gnupg + cat > $HOME/.gnupg/sagernet.key <> "$GITHUB_ENV" + - name: Set tag + run: |- + git tag v${{ needs.calculate_version.outputs.version }} + - name: Build + if: matrix.goos != 'android' + run: |- + goreleaser release --clean --split + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + GOPATH: ${{ env.HOME }}/go + GOARM: ${{ matrix.goarm }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key + NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: Build Android + if: matrix.goos == 'android' + run: |- + go install -v ./cmd/internal/build + GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split + env: + BUILD_GOOS: ${{ matrix.goos }} + BUILD_GOARCH: ${{ matrix.goarch }} + GOARM: ${{ matrix.goarm }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key + NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: Upload artifact + if: github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v4 + with: + name: binary-${{ matrix.name }} + path: 'dist' + build_android: + name: Build Android + if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android' + runs-on: ubuntu-latest + needs: + - calculate_version + steps: + - name: Checkout + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + with: + fetch-depth: 0 + submodules: 'recursive' + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ^1.23 + - name: Setup Android NDK + id: setup-ndk + uses: nttld/setup-ndk@v1 + with: + ndk-version: r28-beta2 + - name: Setup OpenJDK + run: |- + sudo apt update && sudo apt install -y openjdk-17-jdk-headless + /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version + - name: Set tag + run: |- + git tag v${{ needs.calculate_version.outputs.version }} + - name: Build library + run: |- + make lib_install + export PATH="$PATH:$(go env GOPATH)/bin" + make lib_android + env: + JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + - name: Checkout main branch + if: needs.calculate_version.outputs.prerelease == 'false' + run: |- + cd clients/android + git checkout main + - name: Checkout dev branch + if: needs.calculate_version.outputs.prerelease == 'true' + run: |- + cd clients/android + git checkout dev + - name: Gradle cache + uses: actions/cache@v4 + with: + path: ~/.gradle + key: gradle-${{ hashFiles('**/*.gradle') }} + - name: Build + run: |- + go run -v ./cmd/internal/update_android_version --ci + mkdir clients/android/app/libs + cp libbox.aar clients/android/app/libs + cd clients/android + ./gradlew :app:assemblePlayRelease :app:assembleOtherRelease + env: + JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} + - name: Prepare upload + if: github.event_name == 'workflow_dispatch' + run: |- + mkdir -p dist/release + cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release + cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release + - name: Upload artifact + if: github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v4 + with: + name: binary-android-apks + path: 'dist' + publish_android: + name: Publish Android + if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' + runs-on: ubuntu-latest + needs: + - calculate_version + steps: + - name: Checkout + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + with: + fetch-depth: 0 + submodules: 'recursive' + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ^1.23 + - name: Setup Android NDK + id: setup-ndk + uses: nttld/setup-ndk@v1 + with: + ndk-version: r28-beta2 + - name: Setup OpenJDK + run: |- + sudo apt update && sudo apt install -y openjdk-17-jdk-headless + /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version + - name: Set tag + run: |- + git tag v${{ needs.calculate_version.outputs.version }} + - name: Build library + run: |- + make lib_install + export PATH="$PATH:$(go env GOPATH)/bin" + make lib_android + env: + JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + - name: Checkout main branch + if: needs.calculate_version.outputs.prerelease == 'false' + run: |- + cd clients/android + git checkout main + - name: Checkout dev branch + if: needs.calculate_version.outputs.prerelease == 'true' + run: |- + cd clients/android + git checkout dev + - name: Gradle cache + uses: actions/cache@v4 + with: + path: ~/.gradle + key: gradle-${{ hashFiles('**/*.gradle') }} + - name: Build + run: |- + go run -v ./cmd/internal/update_android_version --ci + mkdir clients/android/app/libs + cp libbox.aar clients/android/app/libs + cd clients/android + ./gradlew :app:publishPlayReleaseBundle + env: + JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} + build_apple_library: + name: Build Apple library + if: 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' + runs-on: macos-15 + needs: + - calculate_version + steps: + - name: Checkout + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + with: + fetch-depth: 0 + submodules: 'recursive' + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ^1.23 + - name: Setup Xcode + run: |- + sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app + - name: Set tag + run: |- + git tag v${{ needs.calculate_version.outputs.version }} + - name: Build library + run: |- + make lib_install + export PATH="$PATH:$(go env GOPATH)/bin" + make lib_ios + - name: Upload library + uses: actions/upload-artifact@v4 + with: + name: library-apple + path: 'Libbox.xcframework' + build_apple: + name: Build Apple clients + runs-on: macos-15 + needs: + - calculate_version + - build_apple_library + strategy: + matrix: + include: + - name: iOS + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }} + scheme: SFI + destination: 'generic/platform=iOS' + archive: build/SFI.xcarchive + upload: SFI/Upload.plist + - name: macOS + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }} + scheme: SFM + destination: 'generic/platform=macOS' + archive: build/SFM.xcarchive + upload: SFI/Upload.plist + - name: tvOS + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }} + scheme: SFT + destination: 'generic/platform=tvOS' + archive: build/SFT.xcarchive + upload: SFI/Upload.plist + - name: macOS-standalone + if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }} + scheme: SFM.System + destination: 'generic/platform=macOS' + archive: build/SFM.System.xcarchive + export: SFM.System/Export.plist + export_path: build/SFM.System + steps: + - name: Checkout + if: matrix.if + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + with: + fetch-depth: 0 + submodules: 'recursive' + - name: Setup Go + if: matrix.if + uses: actions/setup-go@v5 + with: + go-version: ^1.23 + - name: Setup Xcode + if: matrix.if + run: |- + sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app + - name: Set tag + if: matrix.if + run: |- + git tag v${{ needs.calculate_version.outputs.version }} + echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" + - name: Checkout main branch + if: matrix.if && needs.calculate_version.outputs.prerelease == 'false' + run: |- + cd clients/apple + git checkout main + - name: Checkout dev branch + if: matrix.if && needs.calculate_version.outputs.prerelease == 'true' + run: |- + cd clients/apple + git checkout dev + - name: Setup certificates + if: matrix.if + run: |- + CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db + echo -n "$CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip + echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH + + PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles" + mkdir -p "$PROFILES_PATH" + unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH" + + ASC_KEY_PATH=$RUNNER_TEMP/Key.p12 + echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH + + xcrun notarytool store-credentials "notarytool-password" \ + --key $ASC_KEY_PATH \ + --key-id $ASC_KEY_ID \ + --issuer $ASC_KEY_ISSUER_ID + env: + CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }} + PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }} + ASC_KEY: ${{ secrets.ASC_KEY }} + ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} + ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }} + - name: Download library + if: matrix.if + uses: actions/download-artifact@v4 + with: + name: library-apple + path: clients/apple/Libbox.xcframework + - name: Build + if: matrix.if + run: |- + go run -v ./cmd/internal/update_apple_version --ci + cd clients/apple + xcodebuild archive \ + -scheme "${{ matrix.scheme }}" \ + -configuration Release \ + -destination "${{ matrix.destination }}" \ + -archivePath "${{ matrix.archive }}" \ + -allowProvisioningUpdates \ + -authenticationKeyPath $RUNNER_TEMP/Key.p12 \ + -authenticationKeyID $ASC_KEY_ID \ + -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID + env: + MACOS_PROJECT_VERSION: ${{ inputs.macos_project_version }} + ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} + ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }} + - name: Upload to App Store Connect + if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' + run: |- + cd clients/apple + xcodebuild -exportArchive \ + -archivePath "${{ matrix.archive }}" \ + -exportOptionsPlist ${{ matrix.upload }} \ + -allowProvisioningUpdates \ + -authenticationKeyPath $RUNNER_TEMP/Key.p12 \ + -authenticationKeyID $ASC_KEY_ID \ + -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID + env: + ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} + ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }} + - name: Build image + if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch' + run: |- + pushd clients/apple + xcodebuild -exportArchive \ + -archivePath "${{ matrix.archive }}" \ + -exportOptionsPlist ${{ matrix.export }} \ + -exportPath "${{ matrix.export_path }}" + brew install create-dmg + create-dmg \ + --volname "sing-box" \ + --volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \ + --icon "SFM.app" 0 0 \ + --hide-extension "SFM.app" \ + --app-drop-link 0 0 \ + --skip-jenkins \ + SFM.dmg "${{ matrix.export_path }}/SFM.app" + xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password" + cd "${{ matrix.archive }}" + zip -r SFM.dSYMs.zip dSYMs + popd + + mkdir -p dist/release + cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg" + cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip" + - name: Upload image + if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v4 + with: + name: binary-macos-dmg + path: 'dist' + upload: + name: Upload builds + if: always() && github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + needs: + - calculate_version + - build + - build_android + - build_apple + steps: + - name: Checkout + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + with: + fetch-depth: 0 + - name: Setup Goreleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser-pro + version: latest + install-only: true + - name: Cache ghr + uses: actions/cache@v4 + id: cache-ghr + with: + path: | + ~/go/bin/ghr + key: ghr + - name: Setup ghr + if: steps.cache-ghr.outputs.cache-hit != 'true' + run: |- + cd $HOME + git clone https://github.com/nekohasekai/ghr ghr + cd ghr + go install -v . + - name: Set tag + run: |- + git tag v${{ needs.calculate_version.outputs.version }} + echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" + - name: Download builds + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - name: Merge builds + if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' + run: |- + goreleaser continue --merge --skip publish + mkdir -p dist/release + mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + - name: Upload builds + run: |- + export PATH="$PATH:$HOME/go/bin" + ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml deleted file mode 100644 index 09499732ba..0000000000 --- a/.github/workflows/debug.yml +++ /dev/null @@ -1,219 +0,0 @@ -name: Debug build - -on: - push: - branches: - - stable-next - - main-next - - dev-next - paths-ignore: - - '**.md' - - '.github/**' - - '!.github/workflows/debug.yml' - pull_request: - branches: - - stable-next - - main-next - - dev-next - -jobs: - build: - name: Debug build - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ^1.23 - - name: Run Test - run: | - go test -v ./... - build_go120: - name: Debug build (Go 1.20) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ~1.20 - - name: Cache go module - uses: actions/cache@v4 - with: - path: | - ~/go/pkg/mod - key: go120-${{ hashFiles('**/go.sum') }} - - name: Run Test - run: make ci_build_go120 - build_go121: - name: Debug build (Go 1.21) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ~1.21 - - name: Cache go module - uses: actions/cache@v4 - with: - path: | - ~/go/pkg/mod - key: go121-${{ hashFiles('**/go.sum') }} - - name: Run Test - run: make ci_build - build_go122: - name: Debug build (Go 1.22) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ~1.22 - - name: Cache go module - uses: actions/cache@v4 - with: - path: | - ~/go/pkg/mod - key: go122-${{ hashFiles('**/go.sum') }} - - name: Run Test - run: make ci_build - cross: - strategy: - matrix: - include: - # windows - - name: windows-amd64 - goos: windows - goarch: amd64 - goamd64: v1 - - name: windows-amd64-v3 - goos: windows - goarch: amd64 - goamd64: v3 - - name: windows-386 - goos: windows - goarch: 386 - - name: windows-arm64 - goos: windows - goarch: arm64 - - name: windows-arm32v7 - goos: windows - goarch: arm - goarm: 7 - - # linux - - name: linux-amd64 - goos: linux - goarch: amd64 - goamd64: v1 - - name: linux-amd64-v3 - goos: linux - goarch: amd64 - goamd64: v3 - - name: linux-386 - goos: linux - goarch: 386 - - name: linux-arm64 - goos: linux - goarch: arm64 - - name: linux-armv5 - goos: linux - goarch: arm - goarm: 5 - - name: linux-armv6 - goos: linux - goarch: arm - goarm: 6 - - name: linux-armv7 - goos: linux - goarch: arm - goarm: 7 - - name: linux-mips-softfloat - goos: linux - goarch: mips - gomips: softfloat - - name: linux-mips-hardfloat - goos: linux - goarch: mips - gomips: hardfloat - - name: linux-mipsel-softfloat - goos: linux - goarch: mipsle - gomips: softfloat - - name: linux-mipsel-hardfloat - goos: linux - goarch: mipsle - gomips: hardfloat - - name: linux-mips64 - goos: linux - goarch: mips64 - - name: linux-mips64el - goos: linux - goarch: mips64le - - name: linux-s390x - goos: linux - goarch: s390x - # darwin - - name: darwin-amd64 - goos: darwin - goarch: amd64 - goamd64: v1 - - name: darwin-amd64-v3 - goos: darwin - goarch: amd64 - goamd64: v3 - - name: darwin-arm64 - goos: darwin - goarch: arm64 - # freebsd - - name: freebsd-amd64 - goos: freebsd - goarch: amd64 - goamd64: v1 - - name: freebsd-amd64-v3 - goos: freebsd - goarch: amd64 - goamd64: v3 - - name: freebsd-386 - goos: freebsd - goarch: 386 - - name: freebsd-arm64 - goos: freebsd - goarch: arm64 - fail-fast: true - runs-on: ubuntu-latest - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - GOAMD64: ${{ matrix.goamd64 }} - GOARM: ${{ matrix.goarm }} - GOMIPS: ${{ matrix.gomips }} - CGO_ENABLED: 0 - TAGS: with_gvisor,with_dhcp,with_wireguard,with_clash_api,with_quic,with_utls,with_ech - steps: - - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ^1.21 - - name: Build - id: build - run: make \ No newline at end of file diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 521477ed6f..613c057a27 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -22,7 +22,6 @@ jobs: mkdir -p $HOME/.gnupg cat > $HOME/.gnupg/sagernet.key <> "$GITHUB_ENV" EOF echo "HOME=$HOME" >> "$GITHUB_ENV" - name: Publish release diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 2d0b234f81..62136cfc96 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -200,4 +200,6 @@ release: ids: - archive - package - skip_upload: true \ No newline at end of file + skip_upload: true +partial: + by: target \ No newline at end of file diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index fc9308ff40..e6b9bbd729 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -10,7 +10,9 @@ import ( _ "github.com/sagernet/gomobile" "github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/shell" ) var ( @@ -62,6 +64,22 @@ func init() { func buildAndroid() { build_shared.FindSDK() + var javaPath string + javaHome := os.Getenv("JAVA_HOME") + if javaHome == "" { + javaPath = "java" + } else { + javaPath = filepath.Join(javaHome, "bin", "java") + } + + javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput() + if err != nil { + log.Fatal(E.Cause(err, "check java version")) + } + if !strings.Contains(javaVersion, "openjdk 17") { + log.Fatal("java version should be openjdk 17") + } + args := []string{ "bind", "-v", @@ -86,7 +104,7 @@ func buildAndroid() { 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) } diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index fae57b5370..5dbefa4ad3 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -11,9 +11,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/common/shell" ) var ( @@ -42,14 +40,6 @@ func FindSDK() { log.Fatal("android NDK not found") } - javaVersion, err := shell.Exec("java", "--version").ReadOutput() - if err != nil { - log.Fatal(E.Cause(err, "check java version")) - } - if !strings.Contains(javaVersion, "openjdk 17") { - log.Fatal("java version should be openjdk 17") - } - os.Setenv("ANDROID_HOME", androidSDKPath) os.Setenv("ANDROID_SDK_HOME", androidSDKPath) os.Setenv("ANDROID_NDK_HOME", androidNDKPath) @@ -64,6 +54,10 @@ func findNDK() bool { androidNDKPath = fixedPath return true } + if ndkHomeEnv := os.Getenv("ANDROID_NDK_HOME"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) { + androidNDKPath = ndkHomeEnv + return true + } ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk")) if err != nil { return false diff --git a/cmd/internal/build_shared/tag.go b/cmd/internal/build_shared/tag.go index 608b5877db..16a97f3e1d 100644 --- a/cmd/internal/build_shared/tag.go +++ b/cmd/internal/build_shared/tag.go @@ -20,6 +20,11 @@ func ReadTag() (string, error) { return version.String() + "-" + shortCommit, nil } +func ReadTagVersionRev() (badversion.Version, error) { + currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput()) + return badversion.Parse(currentTagRev[1:]), nil +} + func ReadTagVersion() (badversion.Version, error) { currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput()) currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput()) @@ -31,3 +36,11 @@ func ReadTagVersion() (badversion.Version, error) { } return version, nil } + +func IsDevBranch() bool { + branch, err := shell.Exec("git", "branch", "--show-current").ReadOutput() + if err != nil { + return false + } + return branch == "dev-next" +} diff --git a/cmd/internal/read_tag/main.go b/cmd/internal/read_tag/main.go index d319f771a0..ae496ec67f 100644 --- a/cmd/internal/read_tag/main.go +++ b/cmd/internal/read_tag/main.go @@ -1,21 +1,74 @@ package main import ( + "flag" "os" "github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/log" + F "github.com/sagernet/sing/common/format" ) +var nightly bool + +func init() { + flag.BoolVar(&nightly, "nightly", false, "Print nightly tag") +} + func main() { - currentTag, err := build_shared.ReadTag() - if err != nil { - log.Error(err) - _, err = os.Stdout.WriteString("unknown\n") + flag.Parse() + if nightly { + version, err := build_shared.ReadTagVersionRev() + if err != nil { + log.Fatal(err) + } + var ( + versionStr string + isPrerelease bool + ) + if version.PreReleaseIdentifier != "" { + isPrerelease = true + versionStr = version.VersionString() + "-nightly" + } else { + version.Patch++ + versionStr = version.VersionString() + "-nightly" + } + if build_shared.IsDevBranch() { + isPrerelease = true + } + err = setGitHubOutput("version", versionStr) + if err != nil { + log.Fatal(err) + } + err = setGitHubOutput("prerelease", F.ToString(isPrerelease)) + if err != nil { + log.Fatal(err) + } } else { - _, err = os.Stdout.WriteString(currentTag + "\n") + tag, err := build_shared.ReadTag() + if err != nil { + log.Error(err) + os.Stdout.WriteString("unknown\n") + } else { + os.Stdout.WriteString(tag + "\n") + } + } +} + +func setGitHubOutput(name string, value string) error { + outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + return err + } + _, err = outputFile.WriteString(name + "=" + value + "\n") + if err != nil { + outputFile.Close() + return err } + err = outputFile.Close() if err != nil { - log.Error(err) + return err } + os.Stderr.WriteString(name + "=" + value + "\n") + return nil } diff --git a/cmd/internal/update_android_version/main.go b/cmd/internal/update_android_version/main.go index 040ba5d2d3..0c6528c65d 100644 --- a/cmd/internal/update_android_version/main.go +++ b/cmd/internal/update_android_version/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "os" "path/filepath" "runtime" @@ -12,9 +13,22 @@ import ( "github.com/sagernet/sing/common" ) +var flagRunInCI bool + +func init() { + flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI") +} + func main() { - newVersion := common.Must1(build_shared.ReadTagVersion()) - androidPath, err := filepath.Abs("../sing-box-for-android") + flag.Parse() + newVersion := common.Must1(build_shared.ReadTag()) + var androidPath string + if flagRunInCI { + androidPath = "clients/android" + } else { + androidPath = "../sing-box-for-android" + } + androidPath, err := filepath.Abs(androidPath) if err != nil { log.Fatal(err) } @@ -31,10 +45,10 @@ func main() { for _, propPair := range propsList { switch propPair[0] { case "VERSION_NAME": - if propPair[1] != newVersion.String() { + if propPair[1] != newVersion { versionUpdated = true - propPair[1] = newVersion.String() - log.Info("updated version to ", newVersion.String()) + propPair[1] = newVersion + log.Info("updated version to ", newVersion) } case "GO_VERSION": if propPair[1] != runtime.Version() { diff --git a/cmd/internal/update_apple_version/main.go b/cmd/internal/update_apple_version/main.go index 0da0f64976..93388e493e 100644 --- a/cmd/internal/update_apple_version/main.go +++ b/cmd/internal/update_apple_version/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "os" "path/filepath" "regexp" @@ -13,9 +14,22 @@ import ( "howett.net/plist" ) +var flagRunInCI bool + +func init() { + flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI") +} + func main() { + flag.Parse() newVersion := common.Must1(build_shared.ReadTagVersion()) - applePath, err := filepath.Abs("../sing-box-for-apple") + var applePath string + if flagRunInCI { + applePath = "clients/apple" + } else { + applePath = "../sing-box-for-apple" + } + applePath, err := filepath.Abs(applePath) if err != nil { log.Fatal(err) } From f0b6818b4c06417f83a5b4bc52b18e0040d7c40c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 10 Dec 2024 09:58:55 +0800 Subject: [PATCH 0118/1272] Add workaround for golang/go#68760 --- Makefile | 6 +++++ cmd/internal/build_libbox/main.go | 8 ++++++ constant/cgo_android_fix.go | 8 ++++++ constant/cgo_android_fix_stub.go | 5 ++++ experimental/libbox/command_client.go | 22 ++++++++++++++--- experimental/libbox/monitor.go | 9 +++++++ experimental/libbox/service.go | 35 ++++++++++++++++++--------- 7 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 constant/cgo_android_fix.go create mode 100644 constant/cgo_android_fix_stub.go diff --git a/Makefile b/Makefile index 70627aead0..2bc149892a 100644 --- a/Makefile +++ b/Makefile @@ -201,9 +201,15 @@ 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_ios: go run ./cmd/internal/build_libbox -target ios +lib_ios_debug: + go run ./cmd/internal/build_libbox -target ios -debug + lib: go run ./cmd/internal/build_libbox -target android go run ./cmd/internal/build_libbox -target ios diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index e6b9bbd729..9d9151bd55 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -80,9 +80,17 @@ func buildAndroid() { log.Fatal("java version should be openjdk 17") } + var bindTarget string + if debugEnabled { + bindTarget = "android/arm64" + } else { + bindTarget = "android" + } + args := []string{ "bind", "-v", + "-target", bindTarget, "-androidapi", "21", "-javapkg=io.nekohasekai", "-libname=box", diff --git a/constant/cgo_android_fix.go b/constant/cgo_android_fix.go new file mode 100644 index 0000000000..cb4023c4d1 --- /dev/null +++ b/constant/cgo_android_fix.go @@ -0,0 +1,8 @@ +//go:build android && debug + +package constant + +// TODO: remove after fixed +// https://github.com/golang/go/issues/68760 + +const FixAndroidStack = true diff --git a/constant/cgo_android_fix_stub.go b/constant/cgo_android_fix_stub.go new file mode 100644 index 0000000000..6145f9f566 --- /dev/null +++ b/constant/cgo_android_fix_stub.go @@ -0,0 +1,5 @@ +//go:build !(android && debug) + +package constant + +const FixAndroidStack = false diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index bd99511582..a9b1c131cd 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -7,6 +7,7 @@ import ( "path/filepath" "time" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" ) @@ -113,11 +114,24 @@ func (c *CommandClient) Connect() error { if err != nil { return err } - c.handler.Connected() - c.handler.InitializeClashMode(newIterator(modeList), currentMode) + if C.FixAndroidStack { + 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 { - conn.Close() - c.handler.Disconnected(os.ErrInvalid.Error()) return nil } go c.handleModeConn(conn) diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 60fc2b8e75..d0517973ce 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -5,6 +5,7 @@ import ( "net/netip" "sync" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -97,6 +98,14 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme } func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) { + if C.FixAndroidStack { + go m.updateDefaultInterface(interfaceName, interfaceIndex32) + } else { + m.updateDefaultInterface(interfaceName, interfaceIndex32) + } +} + +func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName string, interfaceIndex32 int32) { if interfaceName == "" || interfaceIndex32 == -1 { m.defaultInterfaceName = "" m.defaultInterfaceIndex = -1 diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 20395e95d3..a08d5e070a 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -73,23 +73,36 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box } func (s *BoxService) Start() error { - return s.instance.Start() + if C.FixAndroidStack { + 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{}) - defer close(done) go func() { - select { - case <-done: - return - case <-time.After(C.FatalStopTimeout): - os.Exit(1) - } + err = s.instance.Close() + close(done) }() - s.cancel() - s.urlTestHistoryStorage.Close() - return s.instance.Close() + select { + case <-done: + return err + case <-time.After(C.FatalStopTimeout): + os.Exit(1) + return nil + } } func (s *BoxService) NeedWIFIState() bool { From 1e6a3f1f0b22a022f99dd1fcc57906df1d7dea9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 10 Dec 2024 13:13:32 +0800 Subject: [PATCH 0119/1272] release: Update debug iOS library build --- cmd/internal/build_libbox/main.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 9d9151bd55..cb493299c1 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -130,10 +130,17 @@ func buildAndroid() { } func buildiOS() { + var bindTarget string + if debugEnabled { + bindTarget = "ios" + } else { + bindTarget = "ios,iossimulator,tvos,tvossimulator,macos" + } + args := []string{ "bind", "-v", - "-target", "ios,iossimulator,tvos,tvossimulator,macos", + "-target", bindTarget, "-libname=box", } if !debugEnabled { From cec7e47086bb782337a7fc0f350714a6ecf6ecbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 10 Dec 2024 13:04:55 +0800 Subject: [PATCH 0120/1272] Add workaround for `bulkBarrierPreWrite: unaligned arguments` panic --- experimental/libbox/config.go | 8 ++++---- experimental/libbox/http.go | 18 ++++++------------ experimental/libbox/panic.go | 12 ++++++++++++ experimental/libbox/service_error.go | 4 ++-- experimental/libbox/tun.go | 8 ++++---- 5 files changed, 28 insertions(+), 22 deletions(-) create mode 100644 experimental/libbox/panic.go diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 56c5618983..c8d0693acb 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -139,17 +139,17 @@ func (s *platformInterfaceStub) SendNotification(notification *platform.Notifica return nil } -func FormatConfig(configContent string) (string, error) { +func FormatConfig(configContent string) (*StringBox, error) { options, err := parseConfig(configContent) if err != nil { - return "", err + return nil, err } var buffer bytes.Buffer encoder := json.NewEncoder(&buffer) encoder.SetIndent("", " ") err = encoder.Encode(options) if err != nil { - return "", err + return nil, err } - return buffer.String(), nil + return wrapString(buffer.String()), nil } diff --git a/experimental/libbox/http.go b/experimental/libbox/http.go index 3e1a04d042..e037de009b 100644 --- a/experimental/libbox/http.go +++ b/experimental/libbox/http.go @@ -50,8 +50,7 @@ type HTTPRequest interface { } type HTTPResponse interface { - GetContent() ([]byte, error) - GetContentString() (string, error) + GetContent() (*StringBox, error) WriteTo(path string) error } @@ -210,27 +209,22 @@ type httpResponse struct { } func (h *httpResponse) errorString() string { - content, err := h.GetContentString() + content, err := h.GetContent() if err != nil { return fmt.Sprint("HTTP ", h.Status) } return fmt.Sprint("HTTP ", h.Status, ": ", content) } -func (h *httpResponse) GetContent() ([]byte, error) { +func (h *httpResponse) GetContent() (*StringBox, error) { h.getContentOnce.Do(func() { defer h.Body.Close() h.content, h.contentError = io.ReadAll(h.Body) }) - return h.content, h.contentError -} - -func (h *httpResponse) GetContentString() (string, error) { - content, err := h.GetContent() - if err != nil { - return "", err + if h.contentError != nil { + return nil, h.contentError } - return string(content), nil + return wrapString(string(h.content)), nil } func (h *httpResponse) WriteTo(path string) error { diff --git a/experimental/libbox/panic.go b/experimental/libbox/panic.go new file mode 100644 index 0000000000..62fe132657 --- /dev/null +++ b/experimental/libbox/panic.go @@ -0,0 +1,12 @@ +package libbox + +// https://github.com/golang/go/issues/46893 +// TODO: remove after `bulkBarrierPreWrite: unaligned arguments` fixed + +type StringBox struct { + Value string +} + +func wrapString(value string) *StringBox { + return &StringBox{Value: value} +} diff --git a/experimental/libbox/service_error.go b/experimental/libbox/service_error.go index 2f22574a7b..bb0593bf69 100644 --- a/experimental/libbox/service_error.go +++ b/experimental/libbox/service_error.go @@ -13,12 +13,12 @@ func ClearServiceError() { os.Remove(serviceErrorPath()) } -func ReadServiceError() (string, error) { +func ReadServiceError() (*StringBox, error) { data, err := os.ReadFile(serviceErrorPath()) if err == nil { os.Remove(serviceErrorPath()) } - return string(data), err + return wrapString(string(data)), err } func WriteServiceError(message string) error { diff --git a/experimental/libbox/tun.go b/experimental/libbox/tun.go index 5c6e3370c2..18b7910ef5 100644 --- a/experimental/libbox/tun.go +++ b/experimental/libbox/tun.go @@ -13,7 +13,7 @@ import ( type TunOptions interface { GetInet4Address() RoutePrefixIterator GetInet6Address() RoutePrefixIterator - GetDNSServerAddress() (string, error) + GetDNSServerAddress() (*StringBox, error) GetMTU() int32 GetAutoRoute() bool GetStrictRoute() bool @@ -89,11 +89,11 @@ func (o *tunOptions) GetInet6Address() RoutePrefixIterator { return mapRoutePrefix(o.Inet6Address) } -func (o *tunOptions) GetDNSServerAddress() (string, error) { +func (o *tunOptions) GetDNSServerAddress() (*StringBox, error) { if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 { - return "", E.New("need one more IPv4 address for DNS hijacking") + return nil, E.New("need one more IPv4 address for DNS hijacking") } - return o.Inet4Address[0].Addr().Next().String(), nil + return wrapString(o.Inet4Address[0].Addr().Next().String()), nil } func (o *tunOptions) GetMTU() int32 { From 5cdf5c1d9e3e5c57a8c36fa22f7376b02e4f0145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 10 Dec 2024 12:44:02 +0800 Subject: [PATCH 0121/1272] release: Fix play release --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 55583aa19e..b55a331a83 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -347,11 +347,13 @@ jobs: mkdir clients/android/app/libs cp libbox.aar clients/android/app/libs cd clients/android + echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json ./gradlew :app:publishPlayReleaseBundle env: JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} + SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }} build_apple_library: name: Build Apple library if: 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' @@ -555,7 +557,7 @@ jobs: path: 'dist' upload: name: Upload builds - if: always() && github.event_name == 'workflow_dispatch' + if: always() && github.event_name == 'workflow_dispatch' && inputs.build != 'publish-android' runs-on: ubuntu-latest needs: - calculate_version From dd9de694f81fa1c8384ca7c8b538cba932c94f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Dec 2024 09:34:57 +0800 Subject: [PATCH 0122/1272] release: Update macOS project version atomically --- .github/workflows/build.yml | 75 ++++++----------- Makefile | 14 +++- cmd/internal/app_store_connect/main.go | 108 +++++++++++++++++++++++++ cmd/internal/build_libbox/main.go | 18 +++-- go.mod | 4 + go.sum | 10 +++ 6 files changed, 167 insertions(+), 62 deletions(-) create mode 100644 cmd/internal/app_store_connect/main.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b55a331a83..403f7e1c8f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,10 +28,6 @@ on: - tvOS - macOS-standalone - publish-android - macos_project_version: - description: "macOS project version" - required: false - type: string push: branches: - main-next @@ -354,67 +350,38 @@ jobs: ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }} - build_apple_library: - name: Build Apple library - if: 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' - runs-on: macos-15 - needs: - - calculate_version - steps: - - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 - with: - fetch-depth: 0 - submodules: 'recursive' - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ^1.23 - - name: Setup Xcode - run: |- - sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app - - name: Set tag - run: |- - git tag v${{ needs.calculate_version.outputs.version }} - - name: Build library - run: |- - make lib_install - export PATH="$PATH:$(go env GOPATH)/bin" - make lib_ios - - name: Upload library - uses: actions/upload-artifact@v4 - with: - name: library-apple - path: 'Libbox.xcframework' build_apple: name: Build Apple clients runs-on: macos-15 needs: - calculate_version - - build_apple_library strategy: matrix: include: - name: iOS if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }} + platform: ios scheme: SFI destination: 'generic/platform=iOS' archive: build/SFI.xcarchive upload: SFI/Upload.plist - name: macOS if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }} + platform: macos scheme: SFM destination: 'generic/platform=macOS' archive: build/SFM.xcarchive upload: SFI/Upload.plist - name: tvOS if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }} + platform: tvos scheme: SFT destination: 'generic/platform=tvOS' archive: build/SFT.xcarchive upload: SFI/Upload.plist - name: macOS-standalone if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }} + platform: macos scheme: SFM.System destination: 'generic/platform=macOS' archive: build/SFM.System.xcarchive @@ -435,7 +402,7 @@ jobs: - name: Setup Xcode if: matrix.if run: |- - sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app + sudo xcode-select -s /Applications/Xcode_16.2.app - name: Set tag if: matrix.if run: |- @@ -478,6 +445,10 @@ jobs: --key $ASC_KEY_PATH \ --key-id $ASC_KEY_ID \ --issuer $ASC_KEY_ISSUER_ID + + echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV" + echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV" + echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV" env: CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }} P12_PASSWORD: ${{ secrets.P12_PASSWORD }} @@ -486,12 +457,19 @@ jobs: ASC_KEY: ${{ secrets.ASC_KEY }} ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }} - - name: Download library + - name: Build library if: matrix.if - uses: actions/download-artifact@v4 - with: - name: library-apple - path: clients/apple/Libbox.xcframework + run: |- + make lib_install + export PATH="$PATH:$(go env GOPATH)/bin" + go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }} + mv Libbox.xcframework clients/apple + - name: Update macOS version + if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch' + run: |- + MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version) + echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" + echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV" - name: Build if: matrix.if run: |- @@ -503,13 +481,9 @@ jobs: -destination "${{ matrix.destination }}" \ -archivePath "${{ matrix.archive }}" \ -allowProvisioningUpdates \ - -authenticationKeyPath $RUNNER_TEMP/Key.p12 \ + -authenticationKeyPath $ASC_KEY_PATH \ -authenticationKeyID $ASC_KEY_ID \ -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID - env: - MACOS_PROJECT_VERSION: ${{ inputs.macos_project_version }} - ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} - ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }} - name: Upload to App Store Connect if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' run: |- @@ -518,12 +492,9 @@ jobs: -archivePath "${{ matrix.archive }}" \ -exportOptionsPlist ${{ matrix.upload }} \ -allowProvisioningUpdates \ - -authenticationKeyPath $RUNNER_TEMP/Key.p12 \ + -authenticationKeyPath $ASC_KEY_PATH \ -authenticationKeyID $ASC_KEY_ID \ -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID - env: - ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} - ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }} - name: Build image if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch' run: |- diff --git a/Makefile b/Makefile index 2bc149892a..50db1082be 100644 --- a/Makefile +++ b/Makefile @@ -182,10 +182,16 @@ release_tvos: build_tvos upload_tvos_app_store update_apple_version: go run ./cmd/internal/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_beta: update_apple_version release_ios release_macos release_tvos +publish_testflight: + go run -v ./cmd/internal/app_store_connect publish_testflight + test: @go test -v ./... && \ cd test && \ @@ -204,11 +210,11 @@ lib_android: lib_android_debug: go run ./cmd/internal/build_libbox -target android -debug -lib_ios: - go run ./cmd/internal/build_libbox -target ios +lib_apple: + go run ./cmd/internal/build_libbox -target apple -lib_ios_debug: - go run ./cmd/internal/build_libbox -target ios -debug +lib_ios: + go run ./cmd/internal/build_libbox -target apple -platform ios -debug lib: go run ./cmd/internal/build_libbox -target android diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go new file mode 100644 index 0000000000..1304b768bb --- /dev/null +++ b/cmd/internal/app_store_connect/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "os" + "strconv" + "time" + + "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/cidertool/asc-go/asc" +) + +func main() { + switch os.Args[1] { + case "publish_testflight": + err := publishTestflight(context.Background()) + if err != nil { + log.Fatal(err) + } + case "next_macos_project_version": + err := fetchMacOSVersion(context.Background()) + if err != nil { + log.Fatal(err) + } + default: + log.Fatal("unknown action: ", os.Args[1]) + } +} + +const ( + appID = "6673731168" + groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda" +) + +func createClient() *asc.Client { + privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH")) + if err != nil { + log.Fatal(err) + } + tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), time.Minute, privateKey) + if err != nil { + log.Fatal(err) + } + return asc.NewClient(tokenConfig.Client()) +} + +func publishTestflight(ctx context.Context) error { + client := createClient() + var buildsToPublish []asc.Build + for _, platform := range []string{ + "IOS", + "MAC_OS", + "TV_OS", + } { + builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ + FilterApp: []string{appID}, + FilterPreReleaseVersionPlatform: []string{platform}, + }) + if err != nil { + return err + } + buildsToPublish = append(buildsToPublish, builds.Data[0]) + } + _, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, common.Map(buildsToPublish, func(it asc.Build) string { + return it.ID + })) + if err != nil { + return err + } + return nil +} + +func fetchMacOSVersion(ctx context.Context) error { + client := createClient() + versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{"MAC_OS"}, + }) + if err != nil { + return err + } + var versionID string +findVersion: + for _, version := range versions.Data { + switch *version.Attributes.AppStoreState { + case asc.AppStoreVersionStateReadyForSale, + asc.AppStoreVersionStatePendingDeveloperRelease: + versionID = version.ID + break findVersion + } + } + if versionID == "" { + return E.New("no version found") + } + latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{}) + if err != nil { + return err + } + versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version) + if err != nil { + return E.Cause(err, "parse version code") + } + os.Stdout.WriteString(F.ToString(versionInt+1, "\n")) + return nil +} diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index cb493299c1..e37a9ff4dc 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -18,11 +18,13 @@ import ( var ( debugEnabled bool target string + platform string ) func init() { flag.BoolVar(&debugEnabled, "debug", false, "enable debug") flag.StringVar(&target, "target", "android", "target platform") + flag.StringVar(&platform, "platform", "", "specify platform") } func main() { @@ -33,8 +35,8 @@ func main() { switch target { case "android": buildAndroid() - case "ios": - buildiOS() + case "apple": + buildApple() } } @@ -81,7 +83,9 @@ func buildAndroid() { } var bindTarget string - if debugEnabled { + if platform != "" { + bindTarget = platform + } else if debugEnabled { bindTarget = "android/arm64" } else { bindTarget = "android" @@ -129,12 +133,14 @@ func buildAndroid() { } } -func buildiOS() { +func buildApple() { var bindTarget string - if debugEnabled { + if platform != "" { + bindTarget = platform + } else if debugEnabled { bindTarget = "ios" } else { - bindTarget = "ios,iossimulator,tvos,tvossimulator,macos" + bindTarget = "ios,tvos,macos" } args := []string{ diff --git a/go.mod b/go.mod index 42d0b8f304..7683bd5d12 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( berty.tech/go-libtor v1.0.385 github.com/caddyserver/certmagic v0.20.0 + github.com/cidertool/asc-go v0.5.1 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.1.0 @@ -60,7 +61,9 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect + github.com/cenkalti/backoff/v4 v4.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect @@ -68,6 +71,7 @@ require ( github.com/gobwas/pool v0.2.1 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-querystring v1.0.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 303654a1de..2f96653f36 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,10 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= +github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc= +github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cidertool/asc-go v0.5.1 h1:KYki2Y8IXJMOkOXy9y1sdr8tz6IdW2ti770K4bk7WY0= +github.com/cidertool/asc-go v0.5.1/go.mod h1:LyrZWU7DeCh8cWrFwXcpl93ixRUUL2aEZV7/0h07FxA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -15,6 +19,8 @@ github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbe github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= @@ -37,6 +43,8 @@ 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-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= @@ -203,6 +211,8 @@ golang.org/x/time v0.7.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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= From 483d9fa503aa89d329951b3d8bcd22e011ce0069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Dec 2024 14:46:03 +0800 Subject: [PATCH 0123/1272] release: Fix check prerelease --- .github/workflows/build.yml | 21 ++++++--------------- cmd/internal/build_shared/tag.go | 8 -------- cmd/internal/read_tag/main.go | 18 +++--------------- 3 files changed, 9 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 403f7e1c8f..256d9ede04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,11 +7,6 @@ on: description: "Version name" required: true type: string - prerelease: - description: "Is prerelease" - required: true - type: boolean - default: true build: description: "Build type" required: true @@ -43,7 +38,6 @@ jobs: runs-on: ubuntu-latest outputs: version: ${{ steps.outputs.outputs.version }} - prerelease: ${{ steps.outputs.outputs.prerelease }} steps: - name: Checkout uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 @@ -57,9 +51,7 @@ jobs: if: github.event_name == 'workflow_dispatch' run: |- echo "version=${{ inputs.version }}" - echo "prerelease=${{ inputs.prerelease }}" echo "version=${{ inputs.version }}" >> "$GITHUB_ENV" - echo "prerelease=${{ inputs.prerelease }}" >> "$GITHUB_ENV" - name: Calculate version if: github.event_name != 'workflow_dispatch' run: |- @@ -68,7 +60,6 @@ jobs: id: outputs run: |- echo "version=$version" >> "$GITHUB_OUTPUT" - echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT" build: name: Build binary if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' @@ -249,12 +240,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: needs.calculate_version.outputs.prerelease == 'false' + if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch' run: |- cd clients/android git checkout main - name: Checkout dev branch - if: needs.calculate_version.outputs.prerelease == 'true' + if: github.ref == 'refs/heads/dev-next' && github.event_name != 'workflow_dispatch' run: |- cd clients/android git checkout dev @@ -323,12 +314,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: needs.calculate_version.outputs.prerelease == 'false' + if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch' run: |- cd clients/android git checkout main - name: Checkout dev branch - if: needs.calculate_version.outputs.prerelease == 'true' + if: github.ref == 'refs/heads/dev-next' && github.event_name != 'workflow_dispatch' run: |- cd clients/android git checkout dev @@ -409,12 +400,12 @@ jobs: git tag v${{ needs.calculate_version.outputs.version }} echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" - name: Checkout main branch - if: matrix.if && needs.calculate_version.outputs.prerelease == 'false' + if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch' run: |- cd clients/apple git checkout main - name: Checkout dev branch - if: matrix.if && needs.calculate_version.outputs.prerelease == 'true' + if: matrix.if && github.ref == 'refs/heads/dev-next' && github.event_name != 'workflow_dispatch' run: |- cd clients/apple git checkout dev diff --git a/cmd/internal/build_shared/tag.go b/cmd/internal/build_shared/tag.go index 16a97f3e1d..15f050907d 100644 --- a/cmd/internal/build_shared/tag.go +++ b/cmd/internal/build_shared/tag.go @@ -36,11 +36,3 @@ func ReadTagVersion() (badversion.Version, error) { } return version, nil } - -func IsDevBranch() bool { - branch, err := shell.Exec("git", "branch", "--show-current").ReadOutput() - if err != nil { - return false - } - return branch == "dev-next" -} diff --git a/cmd/internal/read_tag/main.go b/cmd/internal/read_tag/main.go index ae496ec67f..2cebaf7075 100644 --- a/cmd/internal/read_tag/main.go +++ b/cmd/internal/read_tag/main.go @@ -6,7 +6,6 @@ import ( "github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/log" - F "github.com/sagernet/sing/common/format" ) var nightly bool @@ -22,25 +21,14 @@ func main() { if err != nil { log.Fatal(err) } - var ( - versionStr string - isPrerelease bool - ) + var versionStr string if version.PreReleaseIdentifier != "" { - isPrerelease = true versionStr = version.VersionString() + "-nightly" } else { version.Patch++ versionStr = version.VersionString() + "-nightly" } - if build_shared.IsDevBranch() { - isPrerelease = true - } - err = setGitHubOutput("version", versionStr) - if err != nil { - log.Fatal(err) - } - err = setGitHubOutput("prerelease", F.ToString(isPrerelease)) + err = setGitHubEnv("version", versionStr) if err != nil { log.Fatal(err) } @@ -55,7 +43,7 @@ func main() { } } -func setGitHubOutput(name string, value string) error { +func setGitHubEnv(name string, value string) error { outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) if err != nil { return err From 52f3a4226c8e40049493abd05477074bb6368945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Dec 2024 17:54:05 +0800 Subject: [PATCH 0124/1272] release: Add app store connect actions --- .github/workflows/build.yml | 1 + Makefile | 6 + cmd/internal/app_store_connect/main.go | 279 +++++++++++++++++++++++-- 3 files changed, 266 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 256d9ede04..a21f962f81 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -478,6 +478,7 @@ jobs: - name: Upload to App Store Connect if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' run: |- + go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }} cd clients/apple xcodebuild -exportArchive \ -archivePath "${{ matrix.archive }}" \ diff --git a/Makefile b/Makefile index 50db1082be..11cecd17f6 100644 --- a/Makefile +++ b/Makefile @@ -192,6 +192,12 @@ release_apple_beta: update_apple_version release_ios release_macos release_tvos publish_testflight: go run -v ./cmd/internal/app_store_connect publish_testflight +prepare_app_store: + go run -v ./cmd/internal/app_store_connect prepare_app_store + +publish_app_store: + go run -v ./cmd/internal/app_store_connect publish_app_store + test: @go test -v ./... && \ cd test && \ diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go index 1304b768bb..32e6dcd7a3 100644 --- a/cmd/internal/app_store_connect/main.go +++ b/cmd/internal/app_store_connect/main.go @@ -2,10 +2,12 @@ package main import ( "context" + "net/http" "os" "strconv" "time" + "github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -15,14 +17,30 @@ import ( ) func main() { + ctx := context.Background() switch os.Args[1] { + case "next_macos_project_version": + err := fetchMacOSVersion(ctx) + if err != nil { + log.Fatal(err) + } case "publish_testflight": - err := publishTestflight(context.Background()) + err := publishTestflight(ctx) if err != nil { log.Fatal(err) } - case "next_macos_project_version": - err := fetchMacOSVersion(context.Background()) + case "cancel_app_store": + err := cancelAppStore(ctx, os.Args[2]) + if err != nil { + log.Fatal(err) + } + case "prepare_app_store": + err := prepareAppStore(ctx) + if err != nil { + log.Fatal(err) + } + case "publish_app_store": + err := publishAppStore(ctx) if err != nil { log.Fatal(err) } @@ -48,6 +66,39 @@ func createClient() *asc.Client { return asc.NewClient(tokenConfig.Client()) } +func fetchMacOSVersion(ctx context.Context) error { + client := createClient() + versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{"MAC_OS"}, + }) + if err != nil { + return err + } + var versionID string +findVersion: + for _, version := range versions.Data { + switch *version.Attributes.AppStoreState { + case asc.AppStoreVersionStateReadyForSale, + asc.AppStoreVersionStatePendingDeveloperRelease: + versionID = version.ID + break findVersion + } + } + if versionID == "" { + return E.New("no version found") + } + latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{}) + if err != nil { + return err + } + versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version) + if err != nil { + return E.Cause(err, "parse version code") + } + os.Stdout.WriteString(F.ToString(versionInt+1, "\n")) + return nil +} + func publishTestflight(ctx context.Context) error { client := createClient() var buildsToPublish []asc.Build @@ -74,35 +125,223 @@ func publishTestflight(ctx context.Context) error { return nil } -func fetchMacOSVersion(ctx context.Context) error { +func cancelAppStore(ctx context.Context, platform string) error { + switch platform { + case "ios": + platform = string(asc.PlatformIOS) + case "macos": + platform = string(asc.PlatformMACOS) + case "tvos": + platform = string(asc.PlatformTVOS) + } + tag, err := build_shared.ReadTag() + if err != nil { + return err + } client := createClient() + log.Info(platform, " list versions") versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ - FilterPlatform: []string{"MAC_OS"}, + FilterPlatform: []string{string(platform)}, }) if err != nil { return err } - var versionID string -findVersion: - for _, version := range versions.Data { - switch *version.Attributes.AppStoreState { - case asc.AppStoreVersionStateReadyForSale, - asc.AppStoreVersionStatePendingDeveloperRelease: - versionID = version.ID - break findVersion - } + version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { + return *it.Attributes.VersionString == tag + }) + if version.ID == "" { + return nil } - if versionID == "" { - return E.New("no version found") + log.Info(string(platform), " ", tag, " get submission") + submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil) + if response != nil && response.StatusCode == http.StatusNotFound { + return nil } - latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{}) if err != nil { return err } - versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version) + log.Info(platform, " ", tag, " delete submission") + _, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID) if err != nil { - return E.Cause(err, "parse version code") + return err + } + return nil +} + +func prepareAppStore(ctx context.Context) error { + tag, err := build_shared.ReadTag() + if err != nil { + return err + } + client := createClient() + for _, platform := range []asc.Platform{ + asc.PlatformIOS, + asc.PlatformMACOS, + asc.PlatformTVOS, + } { + log.Info(string(platform), " list versions") + versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{string(platform)}, + }) + if err != nil { + return err + } + version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { + return *it.Attributes.VersionString == tag + }) + log.Info(string(platform), " ", tag, " list builds") + builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ + FilterApp: []string{appID}, + FilterPreReleaseVersionPlatform: []string{string(platform)}, + }) + if err != nil { + return err + } + if len(builds.Data) == 0 { + log.Fatal(platform, " ", tag, " no build found") + } + buildID := common.Ptr(builds.Data[0].ID) + if version.ID == "" { + log.Info(string(platform), " ", tag, " create version") + newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{ + Platform: platform, + VersionString: tag, + }, appID, buildID) + if err != nil { + return err + } + version = newVersion.Data + + } else { + log.Info(string(platform), " ", tag, " check build") + currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID { + switch *version.Attributes.AppStoreState { + case asc.AppStoreVersionStatePrepareForSubmission, + asc.AppStoreVersionStateRejected, + asc.AppStoreVersionStateDeveloperRejected: + case asc.AppStoreVersionStateWaitingForReview, + asc.AppStoreVersionStateInReview, + asc.AppStoreVersionStatePendingDeveloperRelease: + submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil) + if err != nil { + return err + } + if submission != nil { + log.Info(string(platform), " ", tag, " delete submission") + _, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID) + if err != nil { + return err + } + time.Sleep(5 * time.Second) + } + default: + log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState)) + } + log.Info(string(platform), " ", tag, " update build") + _, _, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID) + if err != nil { + return err + } + } else { + switch *version.Attributes.AppStoreState { + case asc.AppStoreVersionStatePrepareForSubmission, + asc.AppStoreVersionStateRejected, + asc.AppStoreVersionStateDeveloperRejected: + case asc.AppStoreVersionStateWaitingForReview, + asc.AppStoreVersionStateInReview, + asc.AppStoreVersionStatePendingDeveloperRelease: + continue + default: + log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState)) + } + } + } + log.Info(string(platform), " ", tag, " list localization") + localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil) + if err != nil { + return err + } + localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool { + return *it.Attributes.Locale == "en-US" + }) + if localization.ID == "" { + log.Info(string(platform), " ", tag, " no en-US localization found") + } + if localization.Attributes.WhatsNew == nil && *localization.Attributes.WhatsNew == "" { + log.Info(string(platform), " ", tag, " update localization") + _, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{ + PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."), + WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")), + }) + if err != nil { + return err + } + } + log.Info(string(platform), " ", tag, " create submission") + fixSubmit: + for { + _, response, err := client.Submission.CreateSubmission(ctx, version.ID) + if err != nil { + switch response.StatusCode { + case http.StatusInternalServerError: + continue + default: + response.Write(os.Stderr) + log.Info(string(platform), " ", tag, " unexpected response: ", response.Status) + } + } + switch response.StatusCode { + case http.StatusCreated: + break fixSubmit + default: + response.Write(os.Stderr) + log.Info(string(platform), " ", tag, " unexpected response: ", response.Status) + } + } + } + return nil +} + +func publishAppStore(ctx context.Context) error { + tag, err := build_shared.ReadTag() + if err != nil { + return err + } + client := createClient() + for _, platform := range []asc.Platform{ + asc.PlatformIOS, + asc.PlatformMACOS, + asc.PlatformTVOS, + } { + log.Info(string(platform), " list versions") + versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{string(platform)}, + }) + if err != nil { + return err + } + version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { + return *it.Attributes.VersionString == tag + }) + switch *version.Attributes.AppStoreState { + case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected: + log.Fatal(string(platform), " ", tag, " not submitted") + case asc.AppStoreVersionStateWaitingForReview, + asc.AppStoreVersionStateInReview: + log.Warn(string(platform), " ", tag, " waiting for review") + continue + case asc.AppStoreVersionStatePendingDeveloperRelease: + default: + log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState)) + } + _, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID) + if err != nil { + return err + } } - os.Stdout.WriteString(F.ToString(versionInt+1, "\n")) return nil } From 0a922c6fe3ac504e8347f0a49dea633ac40df4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Dec 2024 17:55:54 +0800 Subject: [PATCH 0125/1272] release: Fix Xcode version --- .github/workflows/build.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a21f962f81..f67c7a2d1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -390,8 +390,12 @@ jobs: uses: actions/setup-go@v5 with: go-version: ^1.23 - - name: Setup Xcode - if: matrix.if + - name: Setup Xcode stable + if: matrix.if && github.ref == 'refs/heads/main-next' + run: |- + sudo xcode-select -s /Applications/Xcode_16.1.app + - name: Setup Xcode beta + if: matrix.if && github.ref == 'refs/heads/dev-next' run: |- sudo xcode-select -s /Applications/Xcode_16.2.app - name: Set tag From 3ed8a5c5d111759dc9cfb80b52fb67496535e5de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Dec 2024 21:05:19 +0800 Subject: [PATCH 0126/1272] wireguard: Fix windows tun read --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7683bd5d12..cd07387567 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.5 - github.com/sagernet/sing-tun v0.4.2 + github.com/sagernet/sing-tun v0.4.5 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 2f96653f36..fc2c239238 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo= github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4= -github.com/sagernet/sing-tun v0.4.2 h1:GCP7TI/gwDH/iFIugYS3WcVhCcbDE6qwAbjYQ5W/m+E= -github.com/sagernet/sing-tun v0.4.2/go.mod h1:1WQVMelJQjrtlzhzHwwPTSa7n41b3zSWP2DeJqWxruk= +github.com/sagernet/sing-tun v0.4.5 h1:GgZR9pNRem0TigZw1AgdrCE/v2oRexjmyAytvp89kW0= +github.com/sagernet/sing-tun v0.4.5/go.mod h1:1WQVMelJQjrtlzhzHwwPTSa7n41b3zSWP2DeJqWxruk= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= From 64a94e8144cd1771cfda6e142693cfded3b34770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 13 Dec 2024 00:34:25 +0800 Subject: [PATCH 0127/1272] release: Fix UpdateBuildForAppStoreVersion --- cmd/internal/app_store_connect/client.go | 30 ++++ .../app_store_connect/client_linkname.go | 140 ++++++++++++++++++ cmd/internal/app_store_connect/main.go | 10 +- 3 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 cmd/internal/app_store_connect/client.go create mode 100644 cmd/internal/app_store_connect/client_linkname.go diff --git a/cmd/internal/app_store_connect/client.go b/cmd/internal/app_store_connect/client.go new file mode 100644 index 0000000000..bdc7607633 --- /dev/null +++ b/cmd/internal/app_store_connect/client.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "fmt" + _ "unsafe" + + "github.com/cidertool/asc-go/asc" +) + +type Client struct { + *asc.Client +} + +func (c *Client) UpdateBuildForAppStoreVersion(ctx context.Context, id string, buildID *string) (*asc.Response, error) { + linkage := newRelationshipDeclaration(buildID, "builds") + url := fmt.Sprintf("appStoreVersions/%s/relationships/build", id) + return c.patch(ctx, url, newRequestBody(linkage), nil) +} + +func newRelationshipDeclaration(id *string, relationshipType string) *asc.RelationshipData { + if id == nil { + return nil + } + + return &asc.RelationshipData{ + ID: *id, + Type: relationshipType, + } +} diff --git a/cmd/internal/app_store_connect/client_linkname.go b/cmd/internal/app_store_connect/client_linkname.go new file mode 100644 index 0000000000..7ccfc4fe41 --- /dev/null +++ b/cmd/internal/app_store_connect/client_linkname.go @@ -0,0 +1,140 @@ +package main + +import ( + "context" + "net/http" + "net/url" + "reflect" + _ "unsafe" + + "github.com/cidertool/asc-go/asc" + "github.com/google/go-querystring/query" +) + +func (c *Client) newRequest(ctx context.Context, method string, path string, body *requestBody, options ...requestOption) (*http.Request, error) { + return clientNewRequest(c.Client, ctx, method, path, body, options...) +} + +//go:linkname clientNewRequest github.com/cidertool/asc-go/asc.(*Client).newRequest +func clientNewRequest(c *asc.Client, ctx context.Context, method string, path string, body *requestBody, options ...requestOption) (*http.Request, error) + +func (c *Client) do(ctx context.Context, req *http.Request, v interface{}) (*asc.Response, error) { + return clientDo(c.Client, ctx, req, v) +} + +//go:linkname clientDo github.com/cidertool/asc-go/asc.(*Client).do +func clientDo(c *asc.Client, ctx context.Context, req *http.Request, v interface{}) (*asc.Response, error) + +// get sends a GET request to the API as configured. +func (c *Client) get(ctx context.Context, url string, query interface{}, v interface{}, options ...requestOption) (*asc.Response, error) { + var err error + if query != nil { + url, err = appendingQueryOptions(url, query) + if err != nil { + return nil, err + } + } + + req, err := c.newRequest(ctx, "GET", url, nil, options...) + if err != nil { + return nil, err + } + + resp, err := c.do(ctx, req, v) + if err != nil { + return resp, err + } + + return resp, err +} + +// post sends a POST request to the API as configured. +func (c *Client) post(ctx context.Context, url string, body *requestBody, v interface{}) (*asc.Response, error) { + req, err := c.newRequest(ctx, "POST", url, body, withContentType("application/json")) + if err != nil { + return nil, err + } + + resp, err := c.do(ctx, req, v) + if err != nil { + return resp, err + } + + return resp, err +} + +// patch sends a PATCH request to the API as configured. +func (c *Client) patch(ctx context.Context, url string, body *requestBody, v interface{}) (*asc.Response, error) { + req, err := c.newRequest(ctx, "PATCH", url, body, withContentType("application/json")) + if err != nil { + return nil, err + } + + resp, err := c.do(ctx, req, v) + if err != nil { + return resp, err + } + + return resp, err +} + +// delete sends a DELETE request to the API as configured. +func (c *Client) delete(ctx context.Context, url string, body *requestBody) (*asc.Response, error) { + req, err := c.newRequest(ctx, "DELETE", url, body, withContentType("application/json")) + if err != nil { + return nil, err + } + + return c.do(ctx, req, nil) +} + +// request is a common structure for a request body sent to the API. +type requestBody struct { + Data interface{} `json:"data"` + Included interface{} `json:"included,omitempty"` +} + +func newRequestBody(data interface{}) *requestBody { + return newRequestBodyWithIncluded(data, nil) +} + +func newRequestBodyWithIncluded(data interface{}, included interface{}) *requestBody { + return &requestBody{Data: data, Included: included} +} + +type requestOption func(*http.Request) + +func withAccept(typ string) requestOption { + return func(req *http.Request) { + req.Header.Set("Accept", typ) + } +} + +func withContentType(typ string) requestOption { + return func(req *http.Request) { + req.Header.Set("Content-Type", typ) + } +} + +// AddOptions adds the parameters in opt as URL query parameters to s. opt +// must be a struct whose fields may contain "url" tags. +func appendingQueryOptions(s string, opt interface{}) (string, error) { + v := reflect.ValueOf(opt) + if v.Kind() == reflect.Ptr && v.IsNil() { + return s, nil + } + + u, err := url.Parse(s) + if err != nil { + return s, err + } + + qs, err := query.Values(opt) + if err != nil { + return s, err + } + + u.RawQuery = qs.Encode() + + return u.String(), nil +} diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go index 32e6dcd7a3..08ff1e115a 100644 --- a/cmd/internal/app_store_connect/main.go +++ b/cmd/internal/app_store_connect/main.go @@ -54,7 +54,7 @@ const ( groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda" ) -func createClient() *asc.Client { +func createClient() *Client { privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH")) if err != nil { log.Fatal(err) @@ -63,7 +63,7 @@ func createClient() *asc.Client { if err != nil { log.Fatal(err) } - return asc.NewClient(tokenConfig.Client()) + return &Client{asc.NewClient(tokenConfig.Client())} } func fetchMacOSVersion(ctx context.Context) error { @@ -242,10 +242,14 @@ func prepareAppStore(ctx context.Context) error { log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState)) } log.Info(string(platform), " ", tag, " update build") - _, _, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID) + response, err = client.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID) if err != nil { return err } + if response.StatusCode != http.StatusNoContent { + response.Write(os.Stderr) + log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status) + } } else { switch *version.Attributes.AppStoreState { case asc.AppStoreVersionStatePrepareForSubmission, From 2ac2589d141e5d074b4de333bd10cc972bbe000f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 13 Dec 2024 21:14:42 +0800 Subject: [PATCH 0128/1272] release: Fix publish testflight --- .github/workflows/build.yml | 8 +-- cmd/internal/app_store_connect/main.go | 75 +++++++++++++++++++++----- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f67c7a2d1a..a1cdee2cab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -245,7 +245,7 @@ jobs: cd clients/android git checkout main - name: Checkout dev branch - if: github.ref == 'refs/heads/dev-next' && github.event_name != 'workflow_dispatch' + if: github.ref == 'refs/heads/dev-next' run: |- cd clients/android git checkout dev @@ -319,7 +319,7 @@ jobs: cd clients/android git checkout main - name: Checkout dev branch - if: github.ref == 'refs/heads/dev-next' && github.event_name != 'workflow_dispatch' + if: github.ref == 'refs/heads/dev-next' run: |- cd clients/android git checkout dev @@ -409,7 +409,7 @@ jobs: cd clients/apple git checkout main - name: Checkout dev branch - if: matrix.if && github.ref == 'refs/heads/dev-next' && github.event_name != 'workflow_dispatch' + if: matrix.if && github.ref == 'refs/heads/dev-next' run: |- cd clients/apple git checkout dev @@ -524,7 +524,7 @@ jobs: path: 'dist' upload: name: Upload builds - if: always() && github.event_name == 'workflow_dispatch' && inputs.build != 'publish-android' + if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone') runs-on: ubuntu-latest needs: - calculate_version diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go index 08ff1e115a..75e00475d9 100644 --- a/cmd/internal/app_store_connect/main.go +++ b/cmd/internal/app_store_connect/main.go @@ -100,27 +100,76 @@ findVersion: } func publishTestflight(ctx context.Context) error { + tagVersion, err := build_shared.ReadTagVersion() + if err != nil { + return err + } + tag := tagVersion.VersionString() client := createClient() - var buildsToPublish []asc.Build - for _, platform := range []string{ - "IOS", - "MAC_OS", - "TV_OS", + + buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil) + if err != nil { + return err + } + buildIDS := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string { + return it.ID + }) + for _, platform := range []asc.Platform{ + asc.PlatformIOS, + asc.PlatformMACOS, + asc.PlatformTVOS, } { + log.Info(string(platform), " list builds") builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ FilterApp: []string{appID}, - FilterPreReleaseVersionPlatform: []string{platform}, + FilterPreReleaseVersionPlatform: []string{string(platform)}, }) if err != nil { return err } - buildsToPublish = append(buildsToPublish, builds.Data[0]) - } - _, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, common.Map(buildsToPublish, func(it asc.Build) string { - return it.ID - })) - if err != nil { - return err + log.Info(string(platform), " ", tag, " list localizations") + localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, builds.Data[0].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 ", tag), + )) + if err != nil { + return err + } + } + if !common.Contains(buildIDS, builds.Data[0].ID) { + log.Info(string(platform), " ", tag, " publish") + _, err = client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{builds.Data[0].ID}) + if err != nil { + return err + } + } + log.Info(string(platform), " ", tag, " list submissions") + betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{ + FilterBuild: []string{builds.Data[0].ID}, + }) + if err != nil { + return err + } + if len(betaSubmissions.Data) == 0 { + log.Info(string(platform), " ", tag, " create submission") + _, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, builds.Data[0].ID) + if err != nil { + return err + } + continue + } + } return nil } From e6847ff50e70551448e63cc6018033a2b72b1143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 14 Dec 2024 00:18:58 +0800 Subject: [PATCH 0129/1272] Add locale support ford deprecated messages --- experimental/deprecated/constants.go | 12 +++++++---- experimental/libbox/setup.go | 5 +++++ experimental/locale/locale.go | 30 ++++++++++++++++++++++++++++ experimental/locale/locale_zh_CN.go | 10 ++++++++++ 4 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 experimental/locale/locale.go create mode 100644 experimental/locale/locale_zh_CN.go diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index fd785dbc93..f5b67119f2 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -1,8 +1,11 @@ package deprecated import ( + "fmt" + "github.com/sagernet/sing-box/common/badversion" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/locale" F "github.com/sagernet/sing/common/format" "golang.org/x/mod/semver" @@ -33,10 +36,11 @@ func (n Note) Impending() bool { } func (n Note) Message() string { - return F.ToString( - n.Description, " is deprecated in sing-box ", n.DeprecatedVersion, - " and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.", - ) + if n.MigrationLink != "" { + return fmt.Sprintf(locale.Current().DeprecatedMessage, n.Description, n.DeprecatedVersion, n.ScheduledVersion) + } else { + return fmt.Sprintf(locale.Current().DeprecatedMessageNoLink, n.Description, n.DeprecatedVersion, n.ScheduledVersion) + } } func (n Note) MessageWithLink() string { diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index eb5d7c4e3f..ea941ecf13 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-box/common/humanize" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/locale" _ "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" ) @@ -54,6 +55,10 @@ func SetupWithUsername(basePath string, workingPath string, tempPath string, use return nil } +func SetLocale(localeId string) { + locale.Set(localeId) +} + func Version() string { return C.Version } diff --git a/experimental/locale/locale.go b/experimental/locale/locale.go new file mode 100644 index 0000000000..b17367802a --- /dev/null +++ b/experimental/locale/locale.go @@ -0,0 +1,30 @@ +package locale + +var ( + localeRegistry = make(map[string]*Locale) + current = defaultLocal +) + +type Locale struct { + // deprecated messages for graphical clients + DeprecatedMessage string + DeprecatedMessageNoLink string +} + +var defaultLocal = &Locale{ + DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.", + DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.", +} + +func Current() *Locale { + return current +} + +func Set(localeId string) bool { + locale, loaded := localeRegistry[localeId] + if !loaded { + return false + } + current = locale + return true +} diff --git a/experimental/locale/locale_zh_CN.go b/experimental/locale/locale_zh_CN.go new file mode 100644 index 0000000000..5db2f27406 --- /dev/null +++ b/experimental/locale/locale_zh_CN.go @@ -0,0 +1,10 @@ +package locale + +var warningMessageForEndUsers = "\n\n如果您不明白此消息意味着什么:您的配置文件已过时,且将很快不可用。请联系您的配置提供者以更新配置。" + +func init() { + localeRegistry["zh_CN"] = &Locale{ + DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers, + DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers, + } +} From 9f69f41f68fda38be831a36ff339d1d0a27557ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Dec 2024 13:13:25 +0800 Subject: [PATCH 0130/1272] Update http file server usage --- experimental/clashapi/server.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 909583b361..462f76b57d 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -124,11 +124,8 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ if options.ExternalUI != "" { server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) chiRouter.Group(func(r chi.Router) { - fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI))) - r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP) - r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) { - fs.ServeHTTP(w, r) - }) + r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP) + r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(server.externalUI)))) }) } return server, nil From 3a94e792a2d502b2b464c4736e767cc5a645379a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 14 Dec 2024 22:45:38 +0800 Subject: [PATCH 0131/1272] documentation: Fix uid range example --- docs/configuration/inbound/tun.md | 4 ++-- docs/configuration/inbound/tun.zh.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 420f77a900..cc2f5603c8 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -110,13 +110,13 @@ icon: material/new-box 0 ], "include_uid_range": [ - "1000-99999" + "1000:99999" ], "exclude_uid": [ 1000 ], "exclude_uid_range": [ - "1000-99999" + "1000:99999" ], "include_android_user": [ 0, diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index e7f69483ae..6bc1eb8793 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -110,13 +110,13 @@ icon: material/new-box 0 ], "include_uid_range": [ - "1000-99999" + "1000:99999" ], "exclude_uid": [ 1000 ], "exclude_uid_range": [ - "1000-99999" + "1000:99999" ], "include_android_user": [ 0, From 50576084c613773d0ba4a826685132a245f74fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Dec 2024 21:56:23 +0800 Subject: [PATCH 0132/1272] release: Add publish testflight --- .github/workflows/build.yml | 6 +- cmd/internal/app_store_connect/client.go | 30 --- .../app_store_connect/client_linkname.go | 140 ------------ cmd/internal/app_store_connect/main.go | 209 +++++++++++------- go.mod | 6 +- go.sum | 13 +- 6 files changed, 143 insertions(+), 261 deletions(-) delete mode 100644 cmd/internal/app_store_connect/client.go delete mode 100644 cmd/internal/app_store_connect/client_linkname.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1cdee2cab..c37179ee6d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -393,7 +393,7 @@ jobs: - name: Setup Xcode stable if: matrix.if && github.ref == 'refs/heads/main-next' run: |- - sudo xcode-select -s /Applications/Xcode_16.1.app + sudo xcode-select -s /Applications/Xcode_16.2.app - name: Setup Xcode beta if: matrix.if && github.ref == 'refs/heads/dev-next' run: |- @@ -491,6 +491,10 @@ jobs: -authenticationKeyPath $ASC_KEY_PATH \ -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' + run: |- + go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }} - name: Build image if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch' run: |- diff --git a/cmd/internal/app_store_connect/client.go b/cmd/internal/app_store_connect/client.go deleted file mode 100644 index bdc7607633..0000000000 --- a/cmd/internal/app_store_connect/client.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "context" - "fmt" - _ "unsafe" - - "github.com/cidertool/asc-go/asc" -) - -type Client struct { - *asc.Client -} - -func (c *Client) UpdateBuildForAppStoreVersion(ctx context.Context, id string, buildID *string) (*asc.Response, error) { - linkage := newRelationshipDeclaration(buildID, "builds") - url := fmt.Sprintf("appStoreVersions/%s/relationships/build", id) - return c.patch(ctx, url, newRequestBody(linkage), nil) -} - -func newRelationshipDeclaration(id *string, relationshipType string) *asc.RelationshipData { - if id == nil { - return nil - } - - return &asc.RelationshipData{ - ID: *id, - Type: relationshipType, - } -} diff --git a/cmd/internal/app_store_connect/client_linkname.go b/cmd/internal/app_store_connect/client_linkname.go deleted file mode 100644 index 7ccfc4fe41..0000000000 --- a/cmd/internal/app_store_connect/client_linkname.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "context" - "net/http" - "net/url" - "reflect" - _ "unsafe" - - "github.com/cidertool/asc-go/asc" - "github.com/google/go-querystring/query" -) - -func (c *Client) newRequest(ctx context.Context, method string, path string, body *requestBody, options ...requestOption) (*http.Request, error) { - return clientNewRequest(c.Client, ctx, method, path, body, options...) -} - -//go:linkname clientNewRequest github.com/cidertool/asc-go/asc.(*Client).newRequest -func clientNewRequest(c *asc.Client, ctx context.Context, method string, path string, body *requestBody, options ...requestOption) (*http.Request, error) - -func (c *Client) do(ctx context.Context, req *http.Request, v interface{}) (*asc.Response, error) { - return clientDo(c.Client, ctx, req, v) -} - -//go:linkname clientDo github.com/cidertool/asc-go/asc.(*Client).do -func clientDo(c *asc.Client, ctx context.Context, req *http.Request, v interface{}) (*asc.Response, error) - -// get sends a GET request to the API as configured. -func (c *Client) get(ctx context.Context, url string, query interface{}, v interface{}, options ...requestOption) (*asc.Response, error) { - var err error - if query != nil { - url, err = appendingQueryOptions(url, query) - if err != nil { - return nil, err - } - } - - req, err := c.newRequest(ctx, "GET", url, nil, options...) - if err != nil { - return nil, err - } - - resp, err := c.do(ctx, req, v) - if err != nil { - return resp, err - } - - return resp, err -} - -// post sends a POST request to the API as configured. -func (c *Client) post(ctx context.Context, url string, body *requestBody, v interface{}) (*asc.Response, error) { - req, err := c.newRequest(ctx, "POST", url, body, withContentType("application/json")) - if err != nil { - return nil, err - } - - resp, err := c.do(ctx, req, v) - if err != nil { - return resp, err - } - - return resp, err -} - -// patch sends a PATCH request to the API as configured. -func (c *Client) patch(ctx context.Context, url string, body *requestBody, v interface{}) (*asc.Response, error) { - req, err := c.newRequest(ctx, "PATCH", url, body, withContentType("application/json")) - if err != nil { - return nil, err - } - - resp, err := c.do(ctx, req, v) - if err != nil { - return resp, err - } - - return resp, err -} - -// delete sends a DELETE request to the API as configured. -func (c *Client) delete(ctx context.Context, url string, body *requestBody) (*asc.Response, error) { - req, err := c.newRequest(ctx, "DELETE", url, body, withContentType("application/json")) - if err != nil { - return nil, err - } - - return c.do(ctx, req, nil) -} - -// request is a common structure for a request body sent to the API. -type requestBody struct { - Data interface{} `json:"data"` - Included interface{} `json:"included,omitempty"` -} - -func newRequestBody(data interface{}) *requestBody { - return newRequestBodyWithIncluded(data, nil) -} - -func newRequestBodyWithIncluded(data interface{}, included interface{}) *requestBody { - return &requestBody{Data: data, Included: included} -} - -type requestOption func(*http.Request) - -func withAccept(typ string) requestOption { - return func(req *http.Request) { - req.Header.Set("Accept", typ) - } -} - -func withContentType(typ string) requestOption { - return func(req *http.Request) { - req.Header.Set("Content-Type", typ) - } -} - -// AddOptions adds the parameters in opt as URL query parameters to s. opt -// must be a struct whose fields may contain "url" tags. -func appendingQueryOptions(s string, opt interface{}) (string, error) { - v := reflect.ValueOf(opt) - if v.Kind() == reflect.Ptr && v.IsNil() { - return s, nil - } - - u, err := url.Parse(s) - if err != nil { - return s, err - } - - qs, err := query.Values(opt) - if err != nil { - return s, err - } - - u.RawQuery = qs.Encode() - - return u.String(), nil -} diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go index 75e00475d9..1fe347424b 100644 --- a/cmd/internal/app_store_connect/main.go +++ b/cmd/internal/app_store_connect/main.go @@ -7,13 +7,12 @@ import ( "strconv" "time" + "github.com/sagernet/asc-go/asc" "github.com/sagernet/sing-box/cmd/internal/build_shared" "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/cidertool/asc-go/asc" ) func main() { @@ -54,20 +53,20 @@ const ( groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda" ) -func createClient() *Client { +func createClient(expireDuration time.Duration) *asc.Client { privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH")) if err != nil { log.Fatal(err) } - tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), time.Minute, privateKey) + tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey) if err != nil { log.Fatal(err) } - return &Client{asc.NewClient(tokenConfig.Client())} + return asc.NewClient(tokenConfig.Client()) } func fetchMacOSVersion(ctx context.Context) error { - client := createClient() + client := createClient(time.Minute) versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ FilterPlatform: []string{"MAC_OS"}, }) @@ -105,71 +104,101 @@ func publishTestflight(ctx context.Context) error { return err } tag := tagVersion.VersionString() - client := createClient() + client := createClient(10 * time.Minute) + log.Info(tag, " list build IDs") buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil) if err != nil { return err } - buildIDS := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string { + buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string { return it.ID }) - for _, platform := range []asc.Platform{ - asc.PlatformIOS, - asc.PlatformMACOS, - asc.PlatformTVOS, - } { - log.Info(string(platform), " list builds") - builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ - FilterApp: []string{appID}, - FilterPreReleaseVersionPlatform: []string{string(platform)}, - }) - if err != nil { - return err + 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]) } - log.Info(string(platform), " ", tag, " list localizations") - localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, builds.Data[0].ID, nil) - if err != nil { - return err + } else { + platforms = []asc.Platform{ + asc.PlatformIOS, + asc.PlatformMACOS, + asc.PlatformTVOS, } - 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 ", tag), - )) + } + 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 } - } - if !common.Contains(buildIDS, builds.Data[0].ID) { - log.Info(string(platform), " ", tag, " publish") - _, err = client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{builds.Data[0].ID}) + build := builds.Data[0] + if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute { + log.Info(string(platform), " ", tag, " waiting for process") + time.Sleep(15 * time.Second) + continue + } + if *build.Attributes.ProcessingState != "VALID" { + 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 } - } - log.Info(string(platform), " ", tag, " list submissions") - betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{ - FilterBuild: []string{builds.Data[0].ID}, - }) - if err != nil { - return err - } - if len(betaSubmissions.Data) == 0 { - log.Info(string(platform), " ", tag, " create submission") - _, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, builds.Data[0].ID) + 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 { + 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 } - continue + if len(betaSubmissions.Data) == 0 { + log.Info(string(platform), " ", tag, " create submission") + _, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID) + if err != nil { + return err + } + } + break } - } return nil } @@ -187,34 +216,40 @@ func cancelAppStore(ctx context.Context, platform string) error { if err != nil { return err } - client := createClient() - log.Info(platform, " list versions") - versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ - FilterPlatform: []string{string(platform)}, - }) - if err != nil { - return err - } - version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { - return *it.Attributes.VersionString == tag - }) - if version.ID == "" { - return nil - } - log.Info(string(platform), " ", tag, " get submission") - submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil) - if response != nil && response.StatusCode == http.StatusNotFound { + client := createClient(time.Minute) + for { + log.Info(platform, " list versions") + versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{string(platform)}, + }) + if isRetryable(response) { + continue + } else if err != nil { + return err + } + version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { + return *it.Attributes.VersionString == tag + }) + if version.ID == "" { + return nil + } + log.Info(platform, " ", tag, " get submission") + submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil) + if response != nil && response.StatusCode == http.StatusNotFound { + return nil + } + if isRetryable(response) { + continue + } else if err != nil { + return err + } + log.Info(platform, " ", tag, " delete submission") + _, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID) + if err != nil { + return err + } return nil } - if err != nil { - return err - } - log.Info(platform, " ", tag, " delete submission") - _, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID) - if err != nil { - return err - } - return nil } func prepareAppStore(ctx context.Context) error { @@ -222,7 +257,7 @@ func prepareAppStore(ctx context.Context) error { if err != nil { return err } - client := createClient() + client := createClient(time.Minute) for _, platform := range []asc.Platform{ asc.PlatformIOS, asc.PlatformMACOS, @@ -291,7 +326,7 @@ func prepareAppStore(ctx context.Context) error { log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState)) } log.Info(string(platform), " ", tag, " update build") - response, err = client.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID) + response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID) if err != nil { return err } @@ -364,7 +399,7 @@ func publishAppStore(ctx context.Context) error { if err != nil { return err } - client := createClient() + client := createClient(time.Minute) for _, platform := range []asc.Platform{ asc.PlatformIOS, asc.PlatformMACOS, @@ -398,3 +433,15 @@ func publishAppStore(ctx context.Context) error { } return nil } + +func isRetryable(response *asc.Response) bool { + if response == nil { + return false + } + switch response.StatusCode { + case http.StatusInternalServerError, http.StatusUnprocessableEntity: + return true + default: + return false + } +} diff --git a/go.mod b/go.mod index cd07387567..eab4e0bec9 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.20 require ( berty.tech/go-libtor v1.0.385 github.com/caddyserver/certmagic v0.20.0 - github.com/cidertool/asc-go v0.5.1 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.1.0 @@ -20,6 +19,7 @@ require ( github.com/miekg/dns v1.1.62 github.com/ooni/go-libtor v1.1.8 github.com/oschwald/maxminddb-golang v1.12.0 + github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/cors v1.2.1 @@ -61,7 +61,7 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect - github.com/cenkalti/backoff/v4 v4.1.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -71,7 +71,7 @@ require ( github.com/gobwas/pool v0.2.1 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/go-querystring v1.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index fc2c239238..60a048de34 100644 --- a/go.sum +++ b/go.sum @@ -6,10 +6,8 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= -github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc= -github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cidertool/asc-go v0.5.1 h1:KYki2Y8IXJMOkOXy9y1sdr8tz6IdW2ti770K4bk7WY0= -github.com/cidertool/asc-go v0.5.1/go.mod h1:LyrZWU7DeCh8cWrFwXcpl93ixRUUL2aEZV7/0h07FxA= +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/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -41,10 +39,11 @@ github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 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.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +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/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= @@ -102,6 +101,8 @@ github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1 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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4= +github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU= 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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= From 1676e13d3e566c5c84dbebac560ceba7a8801d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Dec 2024 17:55:58 +0800 Subject: [PATCH 0133/1272] 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 cff12c57dd..db039bde63 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit cff12c57dd3ace2d2776ad607fa0c6bb93873649 +Subproject commit db039bde6354375e7bc22c793d6b40b6a75241d0 diff --git a/clients/apple b/clients/apple index fa107e3b7c..eca82794a5 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit fa107e3b7ca03e1acc5d250aabf82da6ddff997c +Subproject commit eca82794a5cdc8c4b6ef838f98be5e27dae4bf86 diff --git a/docs/changelog.md b/docs/changelog.md index 3a440ccfa3..e905810185 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -### 1.10.3 +### 1.10.4 * Fixes and improvements From d4cd564dbe808f22dac3e6c9938c4165aeae898b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Dec 2024 21:14:25 +0800 Subject: [PATCH 0134/1272] release: Fix check tag --- .github/workflows/build.yml | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c37179ee6d..b813a66c11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -170,7 +170,8 @@ jobs: echo "HOME=$HOME" >> "$GITHUB_ENV" - name: Set tag run: |- - git tag v${{ needs.calculate_version.outputs.version }} + 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: Build if: matrix.goos != 'android' run: |- @@ -230,7 +231,8 @@ jobs: /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version - name: Set tag run: |- - git tag v${{ needs.calculate_version.outputs.version }} + 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: Build library run: |- make lib_install @@ -304,7 +306,8 @@ jobs: /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version - name: Set tag run: |- - git tag v${{ needs.calculate_version.outputs.version }} + 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: Build library run: |- make lib_install @@ -401,7 +404,8 @@ jobs: - name: Set tag if: matrix.if run: |- - git tag v${{ needs.calculate_version.outputs.version }} + 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 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' @@ -562,7 +566,8 @@ jobs: go install -v . - name: Set tag run: |- - git tag v${{ needs.calculate_version.outputs.version }} + 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 echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" - name: Download builds uses: actions/download-artifact@v4 @@ -579,8 +584,16 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} - name: Upload builds + if: ${{ env.PUBLISHED == 'false' }} run: |- export PATH="$PATH:$HOME/go/bin" ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Replace builds + if: ${{ env.PUBLISHED != 'false' }} + run: |- + export PATH="$PATH:$HOME/go/bin" + ghr --replace -p 5 "v${VERSION}" dist/release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 4795ed712b2c70c4f131bcbd8a8d56ae4dbcf85b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Dec 2024 22:30:54 +0800 Subject: [PATCH 0135/1272] release: Fix android version --- clients/android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/android b/clients/android index db039bde63..d53c4db800 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit db039bde6354375e7bc22c793d6b40b6a75241d0 +Subproject commit d53c4db8005c2f86e399c357f926938e0aac8ff5 From 2babf07f9a13a24e916924bba19341530cae68a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 21 Dec 2024 14:23:51 +0800 Subject: [PATCH 0136/1272] Fix wireguard --- go.mod | 3 ++- go.sum | 6 ++++-- outbound/wireguard.go | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index eab4e0bec9..f66decfe1c 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 - github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 + github.com/sagernet/wireguard-go v0.0.1-beta.5 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 @@ -98,6 +98,7 @@ require ( golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.24.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 60a048de34..f5999a7c72 100644 --- a/go.sum +++ b/go.sum @@ -146,8 +146,8 @@ github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxe github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= 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.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= -github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= +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/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.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -214,6 +214,8 @@ golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= diff --git a/outbound/wireguard.go b/outbound/wireguard.go index 6287684923..6ecf536dc1 100644 --- a/outbound/wireguard.go +++ b/outbound/wireguard.go @@ -163,7 +163,7 @@ func (w *WireGuard) start() error { if err != nil { return err } - wgDevice := device.NewDevice(w.tunDevice, bind, &device.Logger{ + wgDevice := device.NewDevice(w.ctx, w.tunDevice, bind, &device.Logger{ Verbosef: func(format string, args ...interface{}) { w.logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) }, From 3c00099ed423531ee8471326344812b34bf4bbe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Dec 2024 19:35:37 +0800 Subject: [PATCH 0137/1272] Fix process rule check --- route/router_rule.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/route/router_rule.go b/route/router_rule.go index 4a99a31cc3..4c345b59a3 100644 --- a/route/router_rule.go +++ b/route/router_rule.go @@ -71,15 +71,15 @@ func isGeositeDNSRule(rule option.DefaultDNSRule) bool { } func isProcessRule(rule option.DefaultRule) bool { - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 } func isProcessDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 } func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool { - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 } func notPrivateNode(code string) bool { From 27bdef34c7bf2753e339047b92528bbe9b69a4bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 21 Dec 2024 17:11:09 +0800 Subject: [PATCH 0138/1272] release: Fix create app store version --- cmd/internal/app_store_connect/main.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go index 1fe347424b..ef00673835 100644 --- a/cmd/internal/app_store_connect/main.go +++ b/cmd/internal/app_store_connect/main.go @@ -359,7 +359,7 @@ func prepareAppStore(ctx context.Context) error { if localization.ID == "" { log.Info(string(platform), " ", tag, " no en-US localization found") } - if localization.Attributes.WhatsNew == nil && *localization.Attributes.WhatsNew == "" { + if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" { log.Info(string(platform), " ", tag, " update localization") _, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{ PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."), @@ -378,16 +378,14 @@ func prepareAppStore(ctx context.Context) error { case http.StatusInternalServerError: continue default: - response.Write(os.Stderr) - log.Info(string(platform), " ", tag, " unexpected response: ", response.Status) + return err } } switch response.StatusCode { case http.StatusCreated: break fixSubmit default: - response.Write(os.Stderr) - log.Info(string(platform), " ", tag, " unexpected response: ", response.Status) + return err } } } From 9c4ab0bf336fe3ff41033686817015eee452e45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 21 Dec 2024 15:49:00 +0800 Subject: [PATCH 0139/1272] 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 d53c4db800..6533b62fa3 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit d53c4db8005c2f86e399c357f926938e0aac8ff5 +Subproject commit 6533b62fa3af7aa9cf13a94af3e66ad6703f2be8 diff --git a/clients/apple b/clients/apple index eca82794a5..1ecaff4c90 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit eca82794a5cdc8c4b6ef838f98be5e27dae4bf86 +Subproject commit 1ecaff4c902fb1dec2a93db1edf6d48e4fe148e1 diff --git a/docs/changelog.md b/docs/changelog.md index e905810185..39bc6c8f63 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -### 1.10.4 +### 1.10.5 * Fixes and improvements From bc2e3960e4c90f4713efded55cc77d6a2598363c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 26 Dec 2024 11:12:48 +0800 Subject: [PATCH 0140/1272] Enable fix stack for Android 7 and 9 --- constant/cgo_android_fix.go | 8 ---- constant/cgo_android_fix_stub.go | 5 --- experimental/libbox/command_client.go | 3 +- experimental/libbox/monitor.go | 3 +- experimental/libbox/service.go | 2 +- experimental/libbox/setup.go | 64 ++++++++++++++++----------- 6 files changed, 41 insertions(+), 44 deletions(-) delete mode 100644 constant/cgo_android_fix.go delete mode 100644 constant/cgo_android_fix_stub.go diff --git a/constant/cgo_android_fix.go b/constant/cgo_android_fix.go deleted file mode 100644 index cb4023c4d1..0000000000 --- a/constant/cgo_android_fix.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build android && debug - -package constant - -// TODO: remove after fixed -// https://github.com/golang/go/issues/68760 - -const FixAndroidStack = true diff --git a/constant/cgo_android_fix_stub.go b/constant/cgo_android_fix_stub.go deleted file mode 100644 index 6145f9f566..0000000000 --- a/constant/cgo_android_fix_stub.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build !(android && debug) - -package constant - -const FixAndroidStack = false diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index a9b1c131cd..fff2dbe2e6 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -7,7 +7,6 @@ import ( "path/filepath" "time" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" ) @@ -114,7 +113,7 @@ func (c *CommandClient) Connect() error { if err != nil { return err } - if C.FixAndroidStack { + if sFixAndroidStack { go func() { c.handler.Connected() c.handler.InitializeClashMode(newIterator(modeList), currentMode) diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index d0517973ce..eb56a314af 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -5,7 +5,6 @@ import ( "net/netip" "sync" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -98,7 +97,7 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme } func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) { - if C.FixAndroidStack { + if sFixAndroidStack { go m.updateDefaultInterface(interfaceName, interfaceIndex32) } else { m.updateDefaultInterface(interfaceName, interfaceIndex32) diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index a08d5e070a..9bfa8a70d7 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -73,7 +73,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box } func (s *BoxService) Start() error { - if C.FixAndroidStack { + if sFixAndroidStack { var err error done := make(chan struct{}) go func() { diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index ea941ecf13..950cbe4edc 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -15,43 +15,55 @@ import ( ) var ( - sBasePath string - sWorkingPath string - sTempPath string - sUserID int - sGroupID int - sTVOS bool + sBasePath string + sWorkingPath string + sTempPath string + sUserID int + sGroupID int + sTVOS bool + sFixAndroidStack bool ) func init() { debug.SetPanicOnFault(true) } -func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) { - sBasePath = basePath - sWorkingPath = workingPath - sTempPath = tempPath - sUserID = os.Getuid() - sGroupID = os.Getgid() - sTVOS = isTVOS - os.MkdirAll(sWorkingPath, 0o777) - os.MkdirAll(sTempPath, 0o777) +type SetupOptions struct { + BasePath string + WorkingPath string + TempPath string + Username string + IsTVOS bool + FixAndroidStack bool } -func SetupWithUsername(basePath string, workingPath string, tempPath string, username string) error { - sBasePath = basePath - sWorkingPath = workingPath - sTempPath = tempPath - sUser, err := user.Lookup(username) - if err != nil { - return err +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() } - sUserID, _ = strconv.Atoi(sUser.Uid) - sGroupID, _ = strconv.Atoi(sUser.Gid) + sTVOS = options.IsTVOS + + // TODO: remove after fixed + // https://github.com/golang/go/issues/68760 + sFixAndroidStack = options.FixAndroidStack + os.MkdirAll(sWorkingPath, 0o777) os.MkdirAll(sTempPath, 0o777) - os.Chown(sWorkingPath, sUserID, sGroupID) - os.Chown(sTempPath, sUserID, sGroupID) + if options.Username != "" { + os.Chown(sWorkingPath, sUserID, sGroupID) + os.Chown(sTempPath, sUserID, sGroupID) + } return nil } From 1bc27a32c212fd7d092bc6dbcb3a76eb7a6bf273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 29 Dec 2024 18:39:22 +0800 Subject: [PATCH 0141/1272] Fix linter configuration --- .golangci.yml | 10 ++++++++++ cmd/sing-box/cmd_tools_fetch_http3.go | 2 +- common/tls/ech_client.go | 1 + common/tls/ech_keygen.go | 3 +++ common/tls/ech_quic.go | 2 +- common/tls/reality_server.go | 1 + common/tls/utls_client.go | 1 + inbound/hysteria.go | 4 ++-- outbound/hysteria.go | 4 ++-- 9 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6cf053f580..cdd53eca51 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -22,6 +22,16 @@ linters-settings: run: go: "1.23" + build-tags: + - with_gvisor + - with_quic + - with_dhcp + - with_wireguard + - with_ech + - with_utls + - with_reality_server + - with_acme + - with_clash_api issues: exclude-dirs: diff --git a/cmd/sing-box/cmd_tools_fetch_http3.go b/cmd/sing-box/cmd_tools_fetch_http3.go index 5dc3d9157f..9a7e440272 100644 --- a/cmd/sing-box/cmd_tools_fetch_http3.go +++ b/cmd/sing-box/cmd_tools_fetch_http3.go @@ -21,7 +21,7 @@ func initializeHTTP3Client(instance *box.Box) error { return err } http3Client = &http.Client{ - Transport: &http3.RoundTripper{ + Transport: &http3.Transport{ Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { destination := M.ParseSocksaddr(addr) udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination) diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go index 7f72b4d8aa..19022d410c 100644 --- a/common/tls/ech_client.go +++ b/common/tls/ech_client.go @@ -63,6 +63,7 @@ type echConnWrapper struct { func (c *echConnWrapper) ConnectionState() tls.ConnectionState { state := c.Conn.ConnectionState() + //nolint:staticcheck return tls.ConnectionState{ Version: state.Version, HandshakeComplete: state.HandshakeComplete, diff --git a/common/tls/ech_keygen.go b/common/tls/ech_keygen.go index 1fea131c96..5053981f1a 100644 --- a/common/tls/ech_keygen.go +++ b/common/tls/ech_keygen.go @@ -147,6 +147,9 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [ pair.rawConf = b secBuf, err := sec.MarshalBinary() + if err != nil { + return nil, E.Cause(err, "serialize ECH private key") + } sk := []byte{} sk = be.AppendUint16(sk, uint16(len(secBuf))) sk = append(sk, secBuf...) diff --git a/common/tls/ech_quic.go b/common/tls/ech_quic.go index fef506dbb1..4606090a7a 100644 --- a/common/tls/ech_quic.go +++ b/common/tls/ech_quic.go @@ -28,7 +28,7 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad } func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper { - return &http3.RoundTripper{ + return &http3.Transport{ TLSClientConfig: c.config, QUICConfig: quicConfig, Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index a931879835..6395f65201 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -175,6 +175,7 @@ type realityConnWrapper struct { func (c *realityConnWrapper) ConnectionState() ConnectionState { state := c.Conn.ConnectionState() + //nolint:staticcheck return tls.ConnectionState{ Version: state.Version, HandshakeComplete: state.HandshakeComplete, diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index 6364740f68..15103f0522 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -69,6 +69,7 @@ type utlsConnWrapper struct { func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState { state := c.Conn.ConnectionState() + //nolint:staticcheck return tls.ConnectionState{ Version: state.Version, HandshakeComplete: state.HandshakeComplete, diff --git a/inbound/hysteria.go b/inbound/hysteria.go index e415d57019..427d7df430 100644 --- a/inbound/hysteria.go +++ b/inbound/hysteria.go @@ -61,8 +61,8 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL } if len(options.Down) > 0 { receiveBps, err = humanize.ParseBytes(options.Down) - if receiveBps == 0 { - return nil, E.New("invalid down speed format: ", options.Down) + if err != nil { + return nil, E.Cause(err, "invalid down speed format: ", options.Down) } } else { receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps diff --git a/outbound/hysteria.go b/outbound/hysteria.go index cf7f7fed11..50383af5d4 100644 --- a/outbound/hysteria.go +++ b/outbound/hysteria.go @@ -63,8 +63,8 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL } if len(options.Down) > 0 { receiveBps, err = humanize.ParseBytes(options.Down) - if receiveBps == 0 { - return nil, E.New("invalid down speed format: ", options.Down) + if err != nil { + return nil, E.Cause(err, "invalid down speed format: ", options.Down) } } else { receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps From 3e246f1173c7d9382331e35ec38041df0e575df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 1 Jan 2025 12:18:47 +0800 Subject: [PATCH 0142/1272] Fix vmess mux leak --- go.mod | 10 +++++----- go.sum | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index f66decfe1c..febd338ce4 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.5 github.com/sagernet/sing-tun v0.4.5 - github.com/sagernet/sing-vmess v0.1.12 + github.com/sagernet/sing-vmess v0.1.13 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.1-beta.5 @@ -45,11 +45,11 @@ require ( github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.29.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/mod v0.20.0 golang.org/x/net v0.31.0 - golang.org/x/sys v0.27.0 + golang.org/x/sys v0.28.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 @@ -94,8 +94,8 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect diff --git a/go.sum b/go.sum index f5999a7c72..e4f1b71956 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,8 @@ github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiT github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4= github.com/sagernet/sing-tun v0.4.5 h1:GgZR9pNRem0TigZw1AgdrCE/v2oRexjmyAytvp89kW0= github.com/sagernet/sing-tun v0.4.5/go.mod h1:1WQVMelJQjrtlzhzHwwPTSa7n41b3zSWP2DeJqWxruk= -github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= -github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= +github.com/sagernet/sing-vmess v0.1.13 h1:/GSfD1Rt6/mVfE80WFHNBykNT7KJNWWmvcMP9DoElEs= +github.com/sagernet/sing-vmess v0.1.13/go.mod h1:D+g+lhv4iOk1Pn08pd3MtUd72khL5+wgEE3xVH8J+ow= 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/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= @@ -179,8 +179,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= @@ -189,8 +189,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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/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= @@ -199,14 +199,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= 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.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 935beca45d926e6ee9b9612f6d7d09f855b002a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 1 Jan 2025 12:19:49 +0800 Subject: [PATCH 0143/1272] Fix check interface --- route/router.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/route/router.go b/route/router.go index 1fc9aa7787..110809f0d2 100644 --- a/route/router.go +++ b/route/router.go @@ -345,9 +345,6 @@ func NewRouter( return nil, err } router.networkMonitor = networkMonitor - networkMonitor.RegisterCallback(func() { - _ = router.interfaceFinder.Update() - }) interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ InterfaceFinder: router.interfaceFinder, OverrideAndroidVPN: options.OverrideAndroidVPN, From 578571b97291bd60e1083b006f12dfb82efb0603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 28 Dec 2024 15:12:22 +0800 Subject: [PATCH 0144/1272] 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 6533b62fa3..e1049099a0 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 6533b62fa3af7aa9cf13a94af3e66ad6703f2be8 +Subproject commit e1049099a04dbb14a0e69a1970f51daa97ee5f7b diff --git a/clients/apple b/clients/apple index 1ecaff4c90..3d889ae017 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 1ecaff4c902fb1dec2a93db1edf6d48e4fe148e1 +Subproject commit 3d889ae0175fb7048d1c0ba229b2ba6c069690a5 diff --git a/docs/changelog.md b/docs/changelog.md index 39bc6c8f63..e3f020e49c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -### 1.10.5 +### 1.10.6 * Fixes and improvements From e42ff22c2efbe1a69152498e1945925ea858a164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 7 Jan 2025 17:27:04 +0800 Subject: [PATCH 0145/1272] quic: Fix source not unwrapped --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index febd338ce4..1a639a44a6 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/sagernet/sing v0.5.1 github.com/sagernet/sing-dns v0.3.0 github.com/sagernet/sing-mux v0.2.1 - github.com/sagernet/sing-quic v0.3.1 + github.com/sagernet/sing-quic v0.3.2 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.5 diff --git a/go.sum b/go.sum index e4f1b71956..2604ee147f 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/sagernet/sing-dns v0.3.0 h1:uHCIlbCwBxALJwXcEK1d75d7t3vzCSVEQsPfZR1cx github.com/sagernet/sing-dns v0.3.0/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo= github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE= -github.com/sagernet/sing-quic v0.3.1 h1:kLg2n4JPnuzUPg7myJGbfGVJGeXiccXfV+PhXIlkSEc= -github.com/sagernet/sing-quic v0.3.1/go.mod h1:g8b5Fj88KRM0H9lpKAxJj0EpkL/Yk06qXJAG7FuZd2I= +github.com/sagernet/sing-quic v0.3.2 h1:bFawJFX4KPx0teFfH1toQm5MfDVz/RwsMbLueeiD2l8= +github.com/sagernet/sing-quic v0.3.2/go.mod h1:g8b5Fj88KRM0H9lpKAxJj0EpkL/Yk06qXJAG7FuZd2I= 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= From 50f5006c43c0be48cbbfba00064e5840f9684ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 7 Jan 2025 17:03:06 +0800 Subject: [PATCH 0146/1272] Fix leak in reality server --- common/tls/reality_client.go | 35 ++++++++++++++++++++++++++++++++++- common/tls/reality_server.go | 6 ++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/common/tls/reality_client.go b/common/tls/reality_client.go index 59ecf86003..f9a11a0b6c 100644 --- a/common/tls/reality_client.go +++ b/common/tls/reality_client.go @@ -184,7 +184,7 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn return nil, E.New("reality verification failed") } - return &utlsConnWrapper{uConn}, nil + return &realityClientConnWrapper{uConn}, nil } func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { @@ -249,3 +249,36 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain } return nil } + +type realityClientConnWrapper struct { + *utls.UConn +} + +func (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState { + state := c.Conn.ConnectionState() + //nolint:staticcheck + return tls.ConnectionState{ + Version: state.Version, + HandshakeComplete: state.HandshakeComplete, + DidResume: state.DidResume, + CipherSuite: state.CipherSuite, + NegotiatedProtocol: state.NegotiatedProtocol, + NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual, + ServerName: state.ServerName, + PeerCertificates: state.PeerCertificates, + VerifiedChains: state.VerifiedChains, + SignedCertificateTimestamps: state.SignedCertificateTimestamps, + OCSPResponse: state.OCSPResponse, + TLSUnique: state.TLSUnique, + } +} + +func (c *realityClientConnWrapper) Upstream() any { + return c.UConn +} + +// Due to low implementation quality, the reality server intercepted half close and caused memory leaks. +// We fixed it by calling Close() directly. +func (c *realityClientConnWrapper) CloseWrite() error { + return c.Close() +} diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index 6395f65201..995b454d86 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -195,3 +195,9 @@ func (c *realityConnWrapper) ConnectionState() ConnectionState { func (c *realityConnWrapper) Upstream() any { return c.Conn } + +// Due to low implementation quality, the reality server intercepted half close and caused memory leaks. +// We fixed it by calling Close() directly. +func (c *realityConnWrapper) CloseWrite() error { + return c.Close() +} From ce5b4b06b53d1dce14cc1eb563121865c6d17183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 7 Jan 2025 21:24:52 +0800 Subject: [PATCH 0147/1272] auto-redirect: Fix fetch interfaces --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1a639a44a6..88fcca8270 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.5 - github.com/sagernet/sing-tun v0.4.5 + github.com/sagernet/sing-tun v0.4.6 github.com/sagernet/sing-vmess v0.1.13 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 2604ee147f..a48af4bb9f 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo= github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4= -github.com/sagernet/sing-tun v0.4.5 h1:GgZR9pNRem0TigZw1AgdrCE/v2oRexjmyAytvp89kW0= -github.com/sagernet/sing-tun v0.4.5/go.mod h1:1WQVMelJQjrtlzhzHwwPTSa7n41b3zSWP2DeJqWxruk= +github.com/sagernet/sing-tun v0.4.6 h1:TBBr3akUqC0o6Vrwet9D56IDHylfM67hYJkVAfdlvCQ= +github.com/sagernet/sing-tun v0.4.6/go.mod h1:1WQVMelJQjrtlzhzHwwPTSa7n41b3zSWP2DeJqWxruk= github.com/sagernet/sing-vmess v0.1.13 h1:/GSfD1Rt6/mVfE80WFHNBykNT7KJNWWmvcMP9DoElEs= github.com/sagernet/sing-vmess v0.1.13/go.mod h1:D+g+lhv4iOk1Pn08pd3MtUd72khL5+wgEE3xVH8J+ow= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= From 253b41936ecd6ae17948d49d9c510d7100830927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 11 Jan 2025 20:36:22 +0800 Subject: [PATCH 0148/1272] 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 e1049099a0..b17fb6d857 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit e1049099a04dbb14a0e69a1970f51daa97ee5f7b +Subproject commit b17fb6d8570251ff1d0494d07d77b45f44006607 diff --git a/clients/apple b/clients/apple index 3d889ae017..64a4614aca 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 3d889ae0175fb7048d1c0ba229b2ba6c069690a5 +Subproject commit 64a4614aca01e1e9b8369ab9f542da737b3f7619 diff --git a/docs/changelog.md b/docs/changelog.md index e3f020e49c..9a550912fc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -### 1.10.6 +### 1.10.7 * Fixes and improvements From 8304295c48feeb8b2cb685130c660207781eec6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 21 Oct 2024 23:38:34 +0800 Subject: [PATCH 0149/1272] Crazy sekai overturns the small pond --- adapter/conn_router.go | 104 ---- adapter/handler.go | 26 + adapter/inbound.go | 35 +- adapter/outbound.go | 5 - adapter/router.go | 35 +- adapter/rule.go | 38 ++ adapter/upstream.go | 177 ++++-- adapter/upstream_legacy.go | 216 +++++++ adapter/v2ray.go | 4 +- cmd/sing-box/cmd_rule_set_match.go | 4 +- .../convertor/adguard/convertor_test.go | 8 +- common/mux/router.go | 17 +- common/mux/v2ray_legacy.go | 32 - common/sniff/sniff.go | 2 +- common/uot/router.go | 39 +- constant/rule.go | 15 + experimental/clashapi/rules.go | 3 +- .../clashapi/trafficontrol/tracker.go | 35 +- go.mod | 12 +- go.sum | 24 +- inbound/default.go | 51 +- inbound/default_tcp.go | 18 +- inbound/default_udp.go | 87 +-- inbound/direct.go | 49 +- inbound/http.go | 39 +- inbound/mixed.go | 26 +- inbound/naive.go | 26 +- inbound/redirect.go | 9 +- inbound/shadowsocks.go | 39 +- inbound/shadowsocks_multi.go | 33 +- inbound/shadowsocks_relay.go | 31 +- inbound/shadowtls.go | 9 + inbound/socks.go | 19 +- inbound/tproxy.go | 43 +- inbound/trojan.go | 26 +- inbound/tun.go | 88 +-- inbound/vless.go | 26 +- inbound/vmess.go | 26 +- include/quic_stub.go | 3 +- option/inbound.go | 1 + option/rule.go | 48 +- option/rule_action.go | 166 +++++ option/rule_dns.go | 57 +- option/types.go | 26 +- outbound/block.go | 2 + outbound/default.go | 10 +- outbound/direct.go | 14 +- outbound/dns.go | 2 + outbound/http.go | 8 - outbound/hysteria.go | 8 - outbound/hysteria2.go | 8 - outbound/proxy.go | 3 + outbound/selector.go | 16 +- outbound/shadowsocks.go | 8 - outbound/shadowtls.go | 8 - outbound/socks.go | 4 + outbound/ssh.go | 8 - outbound/tor.go | 8 - outbound/trojan.go | 8 - outbound/tuic.go | 8 - outbound/urltest.go | 4 + outbound/vless.go | 8 - outbound/vmess.go | 8 - outbound/wireguard.go | 4 + ...uter_geo_resources.go => geo_resources.go} | 3 +- route/route.go | 583 ++++++++++++++++++ route/{router_dns.go => route_dns.go} | 105 ++-- route/router.go | 394 +----------- route/{ => rule}/rule_abstract.go | 20 +- route/rule/rule_action.go | 228 +++++++ route/{ => rule}/rule_default.go | 48 +- route/{ => rule}/rule_dns.go | 63 +- route/{ => rule}/rule_headless.go | 2 +- route/{ => rule}/rule_item_adguard.go | 2 +- route/{ => rule}/rule_item_auth_user.go | 2 +- route/{ => rule}/rule_item_cidr.go | 2 +- route/{ => rule}/rule_item_clash_mode.go | 2 +- route/{ => rule}/rule_item_client.go | 2 +- route/{ => rule}/rule_item_domain.go | 2 +- route/{ => rule}/rule_item_domain_keyword.go | 2 +- route/{ => rule}/rule_item_domain_regex.go | 2 +- route/{ => rule}/rule_item_geoip.go | 2 +- route/{ => rule}/rule_item_geosite.go | 2 +- route/{ => rule}/rule_item_inbound.go | 2 +- route/{ => rule}/rule_item_ip_is_private.go | 2 +- route/{ => rule}/rule_item_ipversion.go | 2 +- route/{ => rule}/rule_item_network.go | 2 +- route/{ => rule}/rule_item_outbound.go | 2 +- route/{ => rule}/rule_item_package_name.go | 2 +- route/{ => rule}/rule_item_port.go | 2 +- route/{ => rule}/rule_item_port_range.go | 2 +- route/{ => rule}/rule_item_process_name.go | 2 +- route/{ => rule}/rule_item_process_path.go | 2 +- .../rule_item_process_path_regex.go | 2 +- route/{ => rule}/rule_item_protocol.go | 2 +- route/{ => rule}/rule_item_query_type.go | 2 +- route/{ => rule}/rule_item_rule_set.go | 2 +- route/{ => rule}/rule_item_user.go | 2 +- route/{ => rule}/rule_item_user_id.go | 2 +- route/{ => rule}/rule_item_wifi_bssid.go | 2 +- route/{ => rule}/rule_item_wifi_ssid.go | 2 +- route/{ => rule}/rule_set.go | 30 +- route/{ => rule}/rule_set_local.go | 2 +- route/{ => rule}/rule_set_remote.go | 2 +- route/{router_rule.go => rule_conds.go} | 28 - test/brutal_test.go | 52 +- test/clash_test.go | 12 +- test/direct_test.go | 13 +- test/docker_test.go | 9 +- test/domain_inbound_test.go | 13 +- test/ech_test.go | 38 +- test/go.mod | 87 +-- test/go.sum | 202 +++--- test/http_test.go | 13 +- test/hysteria2_test.go | 12 +- test/hysteria_test.go | 13 +- test/inbound_detour_test.go | 13 +- test/mux_cool_test.go | 13 +- test/mux_test.go | 26 +- test/shadowsocks_test.go | 39 +- test/shadowtls_test.go | 63 +- test/tfo_test.go | 13 +- test/tls_test.go | 13 +- test/trojan_test.go | 26 +- test/tuic_test.go | 13 +- test/v2ray_transport_test.go | 52 +- test/vmess_test.go | 12 +- transport/v2ray/grpc.go | 7 +- transport/v2ray/grpc_lite.go | 5 +- transport/v2ray/quic.go | 5 +- transport/v2ray/transport.go | 15 +- transport/v2raygrpc/client.go | 2 +- transport/v2raygrpc/conn.go | 10 +- transport/v2raygrpc/server.go | 25 +- transport/v2raygrpclite/server.go | 32 +- transport/v2rayhttp/server.go | 21 +- transport/v2rayhttpupgrade/server.go | 14 +- transport/v2rayquic/server.go | 12 +- transport/v2raywebsocket/server.go | 18 +- 139 files changed, 2854 insertions(+), 1547 deletions(-) delete mode 100644 adapter/conn_router.go create mode 100644 adapter/rule.go create mode 100644 adapter/upstream_legacy.go delete mode 100644 common/mux/v2ray_legacy.go create mode 100644 option/rule_action.go rename route/{router_geo_resources.go => geo_resources.go} (98%) create mode 100644 route/route.go rename route/{router_dns.go => route_dns.go} (75%) rename route/{ => rule}/rule_abstract.go (94%) create mode 100644 route/rule/rule_action.go rename route/{ => rule}/rule_default.go (88%) rename route/{ => rule}/rule_dns.go (89%) rename route/{ => rule}/rule_headless.go (99%) rename route/{ => rule}/rule_item_adguard.go (98%) rename route/{ => rule}/rule_item_auth_user.go (98%) rename route/{ => rule}/rule_item_cidr.go (99%) rename route/{ => rule}/rule_item_clash_mode.go (97%) rename route/{ => rule}/rule_item_client.go (98%) rename route/{ => rule}/rule_item_domain.go (99%) rename route/{ => rule}/rule_item_domain_keyword.go (98%) rename route/{ => rule}/rule_item_domain_regex.go (99%) rename route/{ => rule}/rule_item_geoip.go (99%) rename route/{ => rule}/rule_item_geosite.go (98%) rename route/{ => rule}/rule_item_inbound.go (98%) rename route/{ => rule}/rule_item_ip_is_private.go (98%) rename route/{ => rule}/rule_item_ipversion.go (97%) rename route/{ => rule}/rule_item_network.go (98%) rename route/{ => rule}/rule_item_outbound.go (98%) rename route/{ => rule}/rule_item_package_name.go (98%) rename route/{ => rule}/rule_item_port.go (98%) rename route/{ => rule}/rule_item_port_range.go (99%) rename route/{ => rule}/rule_item_process_name.go (98%) rename route/{ => rule}/rule_item_process_path.go (98%) rename route/{ => rule}/rule_item_process_path_regex.go (98%) rename route/{ => rule}/rule_item_protocol.go (98%) rename route/{ => rule}/rule_item_query_type.go (98%) rename route/{ => rule}/rule_item_rule_set.go (99%) rename route/{ => rule}/rule_item_user.go (98%) rename route/{ => rule}/rule_item_user_id.go (98%) rename route/{ => rule}/rule_item_wifi_bssid.go (98%) rename route/{ => rule}/rule_item_wifi_ssid.go (98%) rename route/{ => rule}/rule_set.go (59%) rename route/{ => rule}/rule_set_local.go (99%) rename route/{ => rule}/rule_set_remote.go (99%) rename route/{router_rule.go => rule_conds.go} (72%) diff --git a/adapter/conn_router.go b/adapter/conn_router.go deleted file mode 100644 index a87c45e8c5..0000000000 --- a/adapter/conn_router.go +++ /dev/null @@ -1,104 +0,0 @@ -package adapter - -import ( - "context" - "net" - - "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type ConnectionRouter interface { - RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error -} - -func NewRouteHandler( - metadata InboundContext, - router ConnectionRouter, - logger logger.ContextLogger, -) UpstreamHandlerAdapter { - return &routeHandlerWrapper{ - metadata: metadata, - router: router, - logger: logger, - } -} - -func NewRouteContextHandler( - router ConnectionRouter, - logger logger.ContextLogger, -) UpstreamHandlerAdapter { - return &routeContextHandlerWrapper{ - router: router, - logger: logger, - } -} - -var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil) - -type routeHandlerWrapper struct { - metadata InboundContext - router ConnectionRouter - logger logger.ContextLogger -} - -func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RouteConnection(ctx, conn, myMetadata) -} - -func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RoutePacketConnection(ctx, conn, myMetadata) -} - -func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) { - w.logger.ErrorContext(ctx, err) -} - -var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil) - -type routeContextHandlerWrapper struct { - router ConnectionRouter - logger logger.ContextLogger -} - -func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RouteConnection(ctx, conn, *myMetadata) -} - -func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RoutePacketConnection(ctx, conn, *myMetadata) -} - -func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) { - w.logger.ErrorContext(ctx, err) -} diff --git a/adapter/handler.go b/adapter/handler.go index bc5bcfbb02..6da158ab20 100644 --- a/adapter/handler.go +++ b/adapter/handler.go @@ -6,27 +6,53 @@ import ( "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" ) +// Deprecated type ConnectionHandler interface { NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error } +type ConnectionHandlerEx interface { + NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) +} + +// Deprecated: use PacketHandlerEx instead type PacketHandler interface { NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error } +type PacketHandlerEx interface { + NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) +} + +// Deprecated: use OOBPacketHandlerEx instead type OOBPacketHandler interface { NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error } +type OOBPacketHandlerEx interface { + NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) +} + +// Deprecated type PacketConnectionHandler interface { NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error } +type PacketConnectionHandlerEx interface { + NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) +} + type UpstreamHandlerAdapter interface { N.TCPConnectionHandler N.UDPConnectionHandler E.Handler } + +type UpstreamHandlerAdapterEx interface { + N.TCPConnectionHandlerEx + N.UDPConnectionHandlerEx +} diff --git a/adapter/inbound.go b/adapter/inbound.go index 82909d0112..f4d5802f31 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -2,13 +2,11 @@ package adapter import ( "context" - "net" "net/netip" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/option" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" ) type Inbound interface { @@ -17,11 +15,14 @@ type Inbound interface { Tag() string } -type InjectableInbound interface { +type TCPInjectableInbound interface { Inbound - Network() []string - NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error + ConnectionHandlerEx +} + +type UDPInjectableInbound interface { + Inbound + PacketConnectionHandlerEx } type InboundContext struct { @@ -43,16 +44,18 @@ type InboundContext struct { // cache - InboundDetour string - LastInbound string - OriginDestination M.Socksaddr - InboundOptions option.InboundOptions - DestinationAddresses []netip.Addr - SourceGeoIPCode string - GeoIPCode string - ProcessInfo *process.Info - QueryType uint16 - FakeIP bool + InboundDetour string + LastInbound string + OriginDestination M.Socksaddr + // Deprecated + InboundOptions option.InboundOptions + UDPDisableDomainUnmapping bool + DestinationAddresses []netip.Addr + SourceGeoIPCode string + GeoIPCode string + ProcessInfo *process.Info + QueryType uint16 + FakeIP bool // rule cache diff --git a/adapter/outbound.go b/adapter/outbound.go index b6980fb97c..312cdf3ed0 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -1,9 +1,6 @@ package adapter import ( - "context" - "net" - N "github.com/sagernet/sing/common/network" ) @@ -15,6 +12,4 @@ type Outbound interface { Network() []string Dependencies() []string N.Dialer - NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error } diff --git a/adapter/router.go b/adapter/router.go index 5dc4de538a..134c944208 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -34,6 +34,7 @@ type Router interface { FakeIPStore() FakeIPStore ConnectionRouter + ConnectionRouterEx GeoIPReader() *geoip.Reader LoadGeosite(code string) (Rule, error) @@ -70,34 +71,24 @@ type Router interface { ResetNetwork() error } -func ContextWithRouter(ctx context.Context, router Router) context.Context { - return service.ContextWith(ctx, router) +// Deprecated: Use ConnectionRouterEx instead. +type ConnectionRouter interface { + RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error + RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error } -func RouterFromContext(ctx context.Context) Router { - return service.FromContext[Router](ctx) -} - -type HeadlessRule interface { - Match(metadata *InboundContext) bool - String() string +type ConnectionRouterEx interface { + ConnectionRouter + RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) + RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) } -type Rule interface { - HeadlessRule - Service - Type() string - UpdateGeosite() error - Outbound() string +func ContextWithRouter(ctx context.Context, router Router) context.Context { + return service.ContextWith(ctx, router) } -type DNSRule interface { - Rule - DisableCache() bool - RewriteTTL() *uint32 - ClientSubnet() *netip.Prefix - WithAddressLimit() bool - MatchAddressLimit(metadata *InboundContext) bool +func RouterFromContext(ctx context.Context) Router { + return service.FromContext[Router](ctx) } type RuleSet interface { diff --git a/adapter/rule.go b/adapter/rule.go new file mode 100644 index 0000000000..f3737a25de --- /dev/null +++ b/adapter/rule.go @@ -0,0 +1,38 @@ +package adapter + +import ( + C "github.com/sagernet/sing-box/constant" +) + +type HeadlessRule interface { + Match(metadata *InboundContext) bool + String() string +} + +type Rule interface { + HeadlessRule + Service + Type() string + UpdateGeosite() error + Action() RuleAction +} + +type DNSRule interface { + Rule + WithAddressLimit() bool + MatchAddressLimit(metadata *InboundContext) bool +} + +type RuleAction interface { + Type() string + String() string +} + +func IsFinalAction(action RuleAction) bool { + switch action.Type() { + case C.RuleActionTypeSniff, C.RuleActionTypeResolve: + return false + default: + return true + } +} diff --git a/adapter/upstream.go b/adapter/upstream.go index 95dd98ea1b..8b0d7c035b 100644 --- a/adapter/upstream.go +++ b/adapter/upstream.go @@ -4,112 +4,165 @@ import ( "context" "net" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) type ( - ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error - PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error + ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) + PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) ) -func NewUpstreamHandler( +func NewUpstreamHandlerEx( metadata InboundContext, - connectionHandler ConnectionHandlerFunc, - packetHandler PacketConnectionHandlerFunc, - errorHandler E.Handler, -) UpstreamHandlerAdapter { - return &myUpstreamHandlerWrapper{ + connectionHandler ConnectionHandlerFuncEx, + packetHandler PacketConnectionHandlerFuncEx, +) UpstreamHandlerAdapterEx { + return &myUpstreamHandlerWrapperEx{ metadata: metadata, connectionHandler: connectionHandler, packetHandler: packetHandler, - errorHandler: errorHandler, } } -var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil) +var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil) -type myUpstreamHandlerWrapper struct { +type myUpstreamHandlerWrapperEx struct { metadata InboundContext - connectionHandler ConnectionHandlerFunc - packetHandler PacketConnectionHandlerFunc - errorHandler E.Handler + connectionHandler ConnectionHandlerFuncEx + packetHandler PacketConnectionHandlerFuncEx } -func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { +func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + myMetadata.Destination = destination } - return w.connectionHandler(ctx, conn, myMetadata) + w.connectionHandler(ctx, conn, myMetadata, onClose) } -func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { +func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + myMetadata.Destination = destination } - return w.packetHandler(ctx, conn, myMetadata) + w.packetHandler(ctx, conn, myMetadata, onClose) } -func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { - w.errorHandler.NewError(ctx, err) -} - -func UpstreamMetadata(metadata InboundContext) M.Metadata { - return M.Metadata{ - Source: metadata.Source, - Destination: metadata.Destination, - } -} +var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil) -type myUpstreamContextHandlerWrapper struct { - connectionHandler ConnectionHandlerFunc - packetHandler PacketConnectionHandlerFunc - errorHandler E.Handler +type myUpstreamContextHandlerWrapperEx struct { + connectionHandler ConnectionHandlerFuncEx + packetHandler PacketConnectionHandlerFuncEx } -func NewUpstreamContextHandler( - connectionHandler ConnectionHandlerFunc, - packetHandler PacketConnectionHandlerFunc, - errorHandler E.Handler, -) UpstreamHandlerAdapter { - return &myUpstreamContextHandlerWrapper{ +func NewUpstreamContextHandlerEx( + connectionHandler ConnectionHandlerFuncEx, + packetHandler PacketConnectionHandlerFuncEx, +) UpstreamHandlerAdapterEx { + return &myUpstreamContextHandlerWrapperEx{ connectionHandler: connectionHandler, packetHandler: packetHandler, - errorHandler: errorHandler, } } -func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { +func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + myMetadata.Destination = destination } - return w.connectionHandler(ctx, conn, *myMetadata) + w.connectionHandler(ctx, conn, *myMetadata, onClose) } -func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { +func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source + } + if destination.IsValid() { + myMetadata.Destination = destination + } + w.packetHandler(ctx, conn, *myMetadata, onClose) +} + +func NewRouteHandlerEx( + metadata InboundContext, + router ConnectionRouterEx, +) UpstreamHandlerAdapterEx { + return &routeHandlerWrapperEx{ + metadata: metadata, + router: router, + } +} + +var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil) + +type routeHandlerWrapperEx struct { + metadata InboundContext + router ConnectionRouterEx +} + +func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + if source.IsValid() { + r.metadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + r.metadata.Destination = destination } - return w.packetHandler(ctx, conn, *myMetadata) + r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose) } -func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) { - w.errorHandler.NewError(ctx, err) +func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + if source.IsValid() { + r.metadata.Source = source + } + if destination.IsValid() { + r.metadata.Destination = destination + } + r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose) +} + +func NewRouteContextHandlerEx( + router ConnectionRouterEx, +) UpstreamHandlerAdapterEx { + return &routeContextHandlerWrapperEx{ + router: router, + } +} + +var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil) + +type routeContextHandlerWrapperEx struct { + router ConnectionRouterEx +} + +func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + metadata := ContextFrom(ctx) + if source.IsValid() { + metadata.Source = source + } + if destination.IsValid() { + metadata.Destination = destination + } + r.router.RouteConnectionEx(ctx, conn, *metadata, onClose) +} + +func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + metadata := ContextFrom(ctx) + if source.IsValid() { + metadata.Source = source + } + if destination.IsValid() { + metadata.Destination = destination + } + r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose) } diff --git a/adapter/upstream_legacy.go b/adapter/upstream_legacy.go new file mode 100644 index 0000000000..1c6c15c017 --- /dev/null +++ b/adapter/upstream_legacy.go @@ -0,0 +1,216 @@ +package adapter + +import ( + "context" + "net" + + 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" +) + +type ( + // Deprecated + ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error + // Deprecated + PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error +) + +// Deprecated +func NewUpstreamHandler( + metadata InboundContext, + connectionHandler ConnectionHandlerFunc, + packetHandler PacketConnectionHandlerFunc, + errorHandler E.Handler, +) UpstreamHandlerAdapter { + return &myUpstreamHandlerWrapper{ + metadata: metadata, + connectionHandler: connectionHandler, + packetHandler: packetHandler, + errorHandler: errorHandler, + } +} + +var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil) + +// Deprecated +type myUpstreamHandlerWrapper struct { + metadata InboundContext + connectionHandler ConnectionHandlerFunc + packetHandler PacketConnectionHandlerFunc + errorHandler E.Handler +} + +func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.connectionHandler(ctx, conn, myMetadata) +} + +func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.packetHandler(ctx, conn, myMetadata) +} + +func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { + w.errorHandler.NewError(ctx, err) +} + +// Deprecated +func UpstreamMetadata(metadata InboundContext) M.Metadata { + return M.Metadata{ + Source: metadata.Source, + Destination: metadata.Destination, + } +} + +// Deprecated +type myUpstreamContextHandlerWrapper struct { + connectionHandler ConnectionHandlerFunc + packetHandler PacketConnectionHandlerFunc + errorHandler E.Handler +} + +// Deprecated +func NewUpstreamContextHandler( + connectionHandler ConnectionHandlerFunc, + packetHandler PacketConnectionHandlerFunc, + errorHandler E.Handler, +) UpstreamHandlerAdapter { + return &myUpstreamContextHandlerWrapper{ + connectionHandler: connectionHandler, + packetHandler: packetHandler, + errorHandler: errorHandler, + } +} + +func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.connectionHandler(ctx, conn, *myMetadata) +} + +func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.packetHandler(ctx, conn, *myMetadata) +} + +func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) { + w.errorHandler.NewError(ctx, err) +} + +// Deprecated: Use ConnectionRouterEx instead. +func NewRouteHandler( + metadata InboundContext, + router ConnectionRouter, + logger logger.ContextLogger, +) UpstreamHandlerAdapter { + return &routeHandlerWrapper{ + metadata: metadata, + router: router, + logger: logger, + } +} + +// Deprecated: Use ConnectionRouterEx instead. +func NewRouteContextHandler( + router ConnectionRouter, + logger logger.ContextLogger, +) UpstreamHandlerAdapter { + return &routeContextHandlerWrapper{ + router: router, + logger: logger, + } +} + +var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil) + +// Deprecated: Use ConnectionRouterEx instead. +type routeHandlerWrapper struct { + metadata InboundContext + router ConnectionRouter + logger logger.ContextLogger +} + +func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RouteConnection(ctx, conn, myMetadata) +} + +func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RoutePacketConnection(ctx, conn, myMetadata) +} + +func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) { + w.logger.ErrorContext(ctx, err) +} + +var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil) + +// Deprecated: Use ConnectionRouterEx instead. +type routeContextHandlerWrapper struct { + router ConnectionRouter + logger logger.ContextLogger +} + +func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RouteConnection(ctx, conn, *myMetadata) +} + +func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RoutePacketConnection(ctx, conn, *myMetadata) +} + +func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) { + w.logger.ErrorContext(ctx, err) +} diff --git a/adapter/v2ray.go b/adapter/v2ray.go index 5a98d2e55a..d9370807fb 100644 --- a/adapter/v2ray.go +++ b/adapter/v2ray.go @@ -4,7 +4,6 @@ import ( "context" "net" - E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" ) @@ -16,8 +15,7 @@ type V2RayServerTransport interface { } type V2RayServerTransportHandler interface { - N.TCPConnectionHandler - E.Handler + N.TCPConnectionHandlerEx } type V2RayClientTransport interface { diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index 7cded86ecf..b96ff22cf9 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -10,7 +10,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-box/route" + "github.com/sagernet/sing-box/route/rule" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" @@ -83,7 +83,7 @@ func ruleSetMatch(sourcePath string, domain string) error { } for i, ruleOptions := range plainRuleSet.Rules { var currentRule adapter.HeadlessRule - currentRule, err = route.NewHeadlessRule(nil, ruleOptions) + currentRule, err = rule.NewHeadlessRule(nil, ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } diff --git a/cmd/sing-box/internal/convertor/adguard/convertor_test.go b/cmd/sing-box/internal/convertor/adguard/convertor_test.go index 7da8a22841..6098485cf0 100644 --- a/cmd/sing-box/internal/convertor/adguard/convertor_test.go +++ b/cmd/sing-box/internal/convertor/adguard/convertor_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/route" + "github.com/sagernet/sing-box/route/rule" "github.com/stretchr/testify/require" ) @@ -26,7 +26,7 @@ example.arpa `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := route.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(nil, rules[0]) require.NoError(t, err) matchDomain := []string{ "example.org", @@ -85,7 +85,7 @@ func TestHosts(t *testing.T) { `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := route.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(nil, rules[0]) require.NoError(t, err) matchDomain := []string{ "google.com", @@ -115,7 +115,7 @@ www.example.org `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := route.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(nil, rules[0]) require.NoError(t, err) matchDomain := []string{ "example.com", diff --git a/common/mux/router.go b/common/mux/router.go index 8a2296852b..2d4e7705d9 100644 --- a/common/mux/router.go +++ b/common/mux/router.go @@ -15,11 +15,11 @@ import ( ) type Router struct { - router adapter.ConnectionRouter + router adapter.ConnectionRouterEx service *mux.Service } -func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) { +func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) { if !options.Enabled { return router, nil } @@ -54,6 +54,7 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if metadata.Destination == mux.Destination { + // TODO: check if WithContext is necessary return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) } else { return r.router.RouteConnection(ctx, conn, metadata) @@ -63,3 +64,15 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return r.router.RoutePacketConnection(ctx, conn, metadata) } + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + if metadata.Destination == mux.Destination { + r.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose) + return + } + r.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/common/mux/v2ray_legacy.go b/common/mux/v2ray_legacy.go deleted file mode 100644 index f53aff2db4..0000000000 --- a/common/mux/v2ray_legacy.go +++ /dev/null @@ -1,32 +0,0 @@ -package mux - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - vmess "github.com/sagernet/sing-vmess" - "github.com/sagernet/sing/common/logger" - N "github.com/sagernet/sing/common/network" -) - -type V2RayLegacyRouter struct { - router adapter.ConnectionRouter - logger logger.ContextLogger -} - -func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter { - return &V2RayLegacyRouter{router, logger} -} - -func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn { - r.logger.InfoContext(ctx, "inbound legacy multiplex connection") - return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger)) - } - return r.router.RouteConnection(ctx, conn, metadata) -} - -func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return r.router.RoutePacketConnection(ctx, conn, metadata) -} diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 80bb2984f9..81fc0a2794 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -18,7 +18,7 @@ type ( PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error ) -func Skip(metadata adapter.InboundContext) bool { +func Skip(metadata *adapter.InboundContext) bool { // skip server first protocols switch metadata.Destination.Port { case 25, 465, 587: diff --git a/common/uot/router.go b/common/uot/router.go index fb2d23d557..98c6d608f3 100644 --- a/common/uot/router.go +++ b/common/uot/router.go @@ -13,14 +13,14 @@ import ( "github.com/sagernet/sing/common/uot" ) -var _ adapter.ConnectionRouter = (*Router)(nil) +var _ adapter.ConnectionRouterEx = (*Router)(nil) type Router struct { - router adapter.ConnectionRouter + router adapter.ConnectionRouterEx logger logger.ContextLogger } -func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router { +func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router { return &Router{router, logger} } @@ -51,3 +51,36 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return r.router.RoutePacketConnection(ctx, conn, metadata) } + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + switch metadata.Destination.Fqdn { + case uot.MagicAddress: + request, err := uot.ReadRequest(conn) + if err != nil { + err = E.Cause(err, "UoT read request") + r.logger.ErrorContext(ctx, "process connection from ", metadata.Source, ": ", err) + N.CloseOnHandshakeFailure(conn, onClose, err) + return + } + if request.IsConnect { + r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination) + } else { + r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination) + } + metadata.Domain = metadata.Destination.Fqdn + metadata.Destination = request.Destination + r.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose) + return + case uot.LegacyMagicAddress: + r.logger.InfoContext(ctx, "inbound legacy UoT connection") + metadata.Domain = metadata.Destination.Fqdn + metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()} + r.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, onClose) + return + } + r.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/constant/rule.go b/constant/rule.go index 086b95a015..ba74ec63f7 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -23,3 +23,18 @@ const ( RuleSetVersion2 RuleSetVersionCurrent = RuleSetVersion2 ) + +const ( + RuleActionTypeRoute = "route" + RuleActionTypeReturn = "return" + RuleActionTypeReject = "reject" + RuleActionTypeHijackDNS = "hijack-dns" + RuleActionTypeSniff = "sniff" + RuleActionTypeResolve = "resolve" +) + +const ( + RuleActionRejectMethodDefault = "default" + RuleActionRejectMethodPortUnreachable = "port-unreachable" + RuleActionRejectMethodDrop = "drop" +) diff --git a/experimental/clashapi/rules.go b/experimental/clashapi/rules.go index 6ab5dda108..bc8fbb2bba 100644 --- a/experimental/clashapi/rules.go +++ b/experimental/clashapi/rules.go @@ -30,10 +30,9 @@ func getRules(router adapter.Router) func(w http.ResponseWriter, r *http.Request rules = append(rules, Rule{ Type: rule.Type(), Payload: rule.String(), - Proxy: rule.Outbound(), + Proxy: rule.Action().String(), }) } - render.JSON(w, r, render.M{ "rules": rules, }) diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index 73c28e69bd..9c18abebf6 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -5,6 +5,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" @@ -60,7 +61,7 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) { } var rule string if t.Rule != nil { - rule = F.ToString(t.Rule, " => ", t.Rule.Outbound()) + rule = F.ToString(t.Rule, " => ", t.Rule.Action()) } else { rule = "final" } @@ -131,19 +132,21 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont outbound string outboundType string ) - if rule == nil { - if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { - next = defaultOutbound.Tag() - } - } else { - next = rule.Outbound() + var action adapter.RuleAction + if rule != nil { + action = rule.Action() + } + if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { + next = routeAction.Outbound + } else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { + next = defaultOutbound.Tag() } for { - chain = append(chain, next) detour, loaded := router.Outbound(next) if !loaded { break } + chain = append(chain, next) outbound = detour.Tag() outboundType = detour.Type() group, isGroup := detour.(adapter.OutboundGroup) @@ -218,19 +221,21 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound outbound string outboundType string ) - if rule == nil { - if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil { - next = defaultOutbound.Tag() - } - } else { - next = rule.Outbound() + var action adapter.RuleAction + if rule != nil { + action = rule.Action() + } + if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { + next = routeAction.Outbound + } else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil { + next = defaultOutbound.Tag() } for { - chain = append(chain, next) detour, loaded := router.Outbound(next) if !loaded { break } + chain = append(chain, next) outbound = detour.Tag() outboundType = detour.Type() group, isGroup := detour.(adapter.OutboundGroup) diff --git a/go.mod b/go.mod index 88fcca8270..ad18132885 100644 --- a/go.mod +++ b/go.mod @@ -28,14 +28,14 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.48.2-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.5.1 - github.com/sagernet/sing-dns v0.3.0 - github.com/sagernet/sing-mux v0.2.1 - github.com/sagernet/sing-quic v0.3.2 + github.com/sagernet/sing v0.6.0-beta.12 + github.com/sagernet/sing-dns v0.4.0-beta.2 + github.com/sagernet/sing-mux v0.3.0-alpha.1 + github.com/sagernet/sing-quic v0.4.0-beta.4 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 - github.com/sagernet/sing-shadowtls v0.1.5 - github.com/sagernet/sing-tun v0.4.6 + github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 + github.com/sagernet/sing-tun v0.6.0-beta.8 github.com/sagernet/sing-vmess v0.1.13 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index a48af4bb9f..a7ec3829c6 100644 --- a/go.sum +++ b/go.sum @@ -124,22 +124,22 @@ github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/ 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.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y= -github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.3.0 h1:uHCIlbCwBxALJwXcEK1d75d7t3vzCSVEQsPfZR1cxQE= -github.com/sagernet/sing-dns v0.3.0/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= -github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo= -github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE= -github.com/sagernet/sing-quic v0.3.2 h1:bFawJFX4KPx0teFfH1toQm5MfDVz/RwsMbLueeiD2l8= -github.com/sagernet/sing-quic v0.3.2/go.mod h1:g8b5Fj88KRM0H9lpKAxJj0EpkL/Yk06qXJAG7FuZd2I= +github.com/sagernet/sing v0.6.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s= +github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY= +github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= +github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= +github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= +github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g= +github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM= 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.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo= -github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4= -github.com/sagernet/sing-tun v0.4.6 h1:TBBr3akUqC0o6Vrwet9D56IDHylfM67hYJkVAfdlvCQ= -github.com/sagernet/sing-tun v0.4.6/go.mod h1:1WQVMelJQjrtlzhzHwwPTSa7n41b3zSWP2DeJqWxruk= +github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= +github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= +github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw= +github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.1.13 h1:/GSfD1Rt6/mVfE80WFHNBykNT7KJNWWmvcMP9DoElEs= github.com/sagernet/sing-vmess v0.1.13/go.mod h1:D+g+lhv4iOk1Pn08pd3MtUd72khL5+wgEE3xVH8J+ow= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= diff --git a/inbound/default.go b/inbound/default.go index 44c580deb9..880dd26f61 100644 --- a/inbound/default.go +++ b/inbound/default.go @@ -22,13 +22,13 @@ type myInboundAdapter struct { protocol string network []string ctx context.Context - router adapter.ConnectionRouter + router adapter.ConnectionRouterEx logger log.ContextLogger tag string listenOptions option.ListenOptions - connHandler adapter.ConnectionHandler - packetHandler adapter.PacketHandler - oobPacketHandler adapter.OOBPacketHandler + connHandler adapter.ConnectionHandlerEx + packetHandler adapter.PacketHandlerEx + oobPacketHandler adapter.OOBPacketHandlerEx packetUpstream any // http mixed @@ -55,10 +55,6 @@ func (a *myInboundAdapter) Tag() string { return a.tag } -func (a *myInboundAdapter) Network() []string { - return a.network -} - func (a *myInboundAdapter) Start() error { var err error if common.Contains(a.network, N.NetworkTCP) { @@ -150,6 +146,31 @@ func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.Packe return a.router.RoutePacketConnection(ctx, conn, metadata) } +func (a *myInboundAdapter) upstreamHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { + return adapter.NewUpstreamHandlerEx(metadata, a.newConnectionEx, a.streamPacketConnectionEx) +} + +func (a *myInboundAdapter) upstreamContextHandlerEx() adapter.UpstreamHandlerAdapterEx { + return adapter.NewUpstreamContextHandlerEx(a.newConnectionEx, a.newPacketConnectionEx) +} + +func (a *myInboundAdapter) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + a.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (a *myInboundAdapter) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + ctx = log.ContextWithNewID(ctx) + a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (a *myInboundAdapter) streamPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.InboundContext) adapter.InboundContext { metadata.Inbound = a.tag metadata.InboundType = a.protocol @@ -167,25 +188,17 @@ func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.Inboun return metadata } -func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - return metadata -} - +// Deprecated: don't use func (a *myInboundAdapter) newError(err error) { a.logger.Error(err) } +// Deprecated: don't use func (a *myInboundAdapter) NewError(ctx context.Context, err error) { NewError(a.logger, ctx, err) } +// Deprecated: don't use func NewError(logger log.ContextLogger, ctx context.Context, err error) { common.Close(err) if E.IsClosedOrCanceled(err) { diff --git a/inbound/default_tcp.go b/inbound/default_tcp.go index d680c6951a..d38f96fe6e 100644 --- a/inbound/default_tcp.go +++ b/inbound/default_tcp.go @@ -71,18 +71,14 @@ func (a *myInboundAdapter) injectTCP(conn net.Conn, metadata adapter.InboundCont ctx := log.ContextWithNewID(a.ctx) metadata = a.createMetadata(conn, metadata) a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - hErr := a.connHandler.NewConnection(ctx, conn, metadata) - if hErr != nil { - conn.Close() - a.NewError(ctx, E.Cause(hErr, "process connection from ", metadata.Source)) - } + a.connHandler.NewConnectionEx(ctx, conn, metadata, nil) } -func (a *myInboundAdapter) routeTCP(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) { +func (a *myInboundAdapter) routeTCP(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + metadata := a.createMetadata(conn, adapter.InboundContext{ + Source: source, + Destination: destination, + }) a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - hErr := a.newConnection(ctx, conn, metadata) - if hErr != nil { - conn.Close() - a.NewError(ctx, E.Cause(hErr, "process connection from ", metadata.Source)) - } + a.connHandler.NewConnectionEx(ctx, conn, metadata, onClose) } diff --git a/inbound/default_udp.go b/inbound/default_udp.go index 8e39471ac7..6bcde79dae 100644 --- a/inbound/default_udp.go +++ b/inbound/default_udp.go @@ -42,7 +42,6 @@ func (a *myInboundAdapter) loopUDPIn() { defer buffer.Release() buffer.IncRef() defer buffer.DecRef() - packetService := (*myInboundPacketAdapter)(a) for { buffer.Reset() n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) @@ -50,16 +49,7 @@ func (a *myInboundAdapter) loopUDPIn() { return } buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) - if err != nil { - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } + a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) } } @@ -69,7 +59,6 @@ func (a *myInboundAdapter) loopUDPOOBIn() { defer buffer.Release() buffer.IncRef() defer buffer.DecRef() - packetService := (*myInboundPacketAdapter)(a) oob := make([]byte, 1024) for { buffer.Reset() @@ -78,22 +67,12 @@ func (a *myInboundAdapter) loopUDPOOBIn() { return } buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata) - if err != nil { - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } + a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) } } func (a *myInboundAdapter) loopUDPInThreadSafe() { defer close(a.packetOutboundClosed) - packetService := (*myInboundPacketAdapter)(a) for { buffer := buf.NewPacket() n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) @@ -102,23 +81,12 @@ func (a *myInboundAdapter) loopUDPInThreadSafe() { return } buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) - if err != nil { - buffer.Release() - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } + a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) } } func (a *myInboundAdapter) loopUDPOOBInThreadSafe() { defer close(a.packetOutboundClosed) - packetService := (*myInboundPacketAdapter)(a) oob := make([]byte, 1024) for { buffer := buf.NewPacket() @@ -128,17 +96,7 @@ func (a *myInboundAdapter) loopUDPOOBInThreadSafe() { return } buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata) - if err != nil { - buffer.Release() - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } + a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) } } @@ -148,7 +106,7 @@ func (a *myInboundAdapter) loopUDPOut() { case packet := <-a.packetOutbound: err := a.writePacket(packet.buffer, packet.destination) if err != nil && !E.IsClosed(err) { - a.newError(E.New("write back udp: ", err)) + a.logger.Error(E.New("write back udp: ", err)) } continue case <-a.packetOutboundClosed: @@ -164,15 +122,36 @@ func (a *myInboundAdapter) loopUDPOut() { } } +func (a *myInboundAdapter) packetConn() N.PacketConn { + return (*myInboundPacketAdapter)(a) +} + +func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext { + metadata.Inbound = a.tag + metadata.InboundType = a.protocol + metadata.InboundDetour = a.listenOptions.Detour + metadata.InboundOptions = a.listenOptions.InboundOptions + if !metadata.Destination.IsValid() { + metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + } + metadata.OriginDestination = a.udpAddr + return metadata +} + +func (a *myInboundAdapter) createPacketMetadataEx(source M.Socksaddr, destination M.Socksaddr) adapter.InboundContext { + var metadata adapter.InboundContext + metadata.Inbound = a.tag + metadata.InboundType = a.protocol + metadata.InboundDetour = a.listenOptions.Detour + metadata.InboundOptions = a.listenOptions.InboundOptions + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = a.udpAddr + return metadata +} + func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { defer buffer.Release() - if destination.IsFqdn() { - udpAddr, err := net.ResolveUDPAddr(N.NetworkUDP, destination.String()) - if err != nil { - return err - } - return common.Error(a.udpConn.WriteTo(buffer.Bytes(), udpAddr)) - } return common.Error(a.udpConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort())) } diff --git a/inbound/direct.go b/inbound/direct.go index 7079a9f24f..b9a99f12cc 100644 --- a/inbound/direct.go +++ b/inbound/direct.go @@ -3,7 +3,6 @@ package inbound import ( "context" "net" - "net/netip" "time" "github.com/sagernet/sing-box/adapter" @@ -13,14 +12,14 @@ import ( "github.com/sagernet/sing/common/buf" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/udpnat" + "github.com/sagernet/sing/common/udpnat2" ) var _ adapter.Inbound = (*Direct)(nil) type Direct struct { myInboundAdapter - udpNat *udpnat.Service[netip.AddrPort] + udpNat *udpnat.Service overrideOption int overrideDestination M.Socksaddr } @@ -54,10 +53,9 @@ func NewDirect(ctx context.Context, router adapter.Router, logger log.ContextLog } else { udpTimeout = C.UDPTimeout } - inbound.udpNat = udpnat.New[netip.AddrPort](int64(udpTimeout.Seconds()), adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) + inbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false) inbound.connHandler = inbound inbound.packetHandler = inbound - inbound.packetUpstream = inbound.udpNat return inbound } @@ -76,29 +74,38 @@ func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adap return d.router.RouteConnection(ctx, conn, metadata) } -func (d *Direct) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { +func (d *Direct) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + var destination M.Socksaddr switch d.overrideOption { case 1: - metadata.Destination = d.overrideDestination + destination = d.overrideDestination case 2: - destination := d.overrideDestination - destination.Port = metadata.Destination.Port - metadata.Destination = destination + destination = d.overrideDestination + destination.Port = source.Port case 3: - metadata.Destination.Port = d.overrideDestination.Port + destination = source + destination.Port = d.overrideDestination.Port } - d.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) { - return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &udpnat.DirectBackWriter{Source: conn, Nat: natConn} - }) - return nil + d.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil) } -func (d *Direct) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return d.router.RouteConnection(ctx, conn, metadata) +func (d *Direct) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + d.newConnectionEx(ctx, conn, metadata, onClose) +} + +func (d *Direct) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + d.newPacketConnectionEx(ctx, conn, d.createPacketMetadataEx(source, destination), onClose) +} + +func (d *Direct) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + return true, d.ctx, &directPacketWriter{d.packetConn(), source}, nil +} + +type directPacketWriter struct { + writer N.PacketWriter + source M.Socksaddr } -func (d *Direct) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - d.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - return d.router.RoutePacketConnection(ctx, conn, metadata) +func (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { + return w.writer.WritePacket(buffer, w.source) } diff --git a/inbound/http.go b/inbound/http.go index b466331995..20c8f6903d 100644 --- a/inbound/http.go +++ b/inbound/http.go @@ -4,7 +4,6 @@ import ( std_bufio "bufio" "context" "net" - "os" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" @@ -20,8 +19,8 @@ import ( ) var ( - _ adapter.Inbound = (*HTTP)(nil) - _ adapter.InjectableInbound = (*HTTP)(nil) + _ adapter.Inbound = (*HTTP)(nil) + _ adapter.TCPInjectableInbound = (*HTTP)(nil) ) type HTTP struct { @@ -72,7 +71,15 @@ func (h *HTTP) Close() error { ) } -func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *HTTP) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *HTTP) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { var err error if h.tlsConfig != nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -80,35 +87,33 @@ func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapte return err } } - return http.HandleConnection(ctx, conn, std_bufio.NewReader(conn), h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) -} - -func (h *HTTP) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid + return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) } -func (a *myInboundAdapter) upstreamUserHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamHandler(metadata, a.newUserConnection, a.streamUserPacketConnection, a) +func (a *myInboundAdapter) upstreamUserHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { + return adapter.NewUpstreamHandlerEx(metadata, a.newUserConnection, a.streamUserPacketConnection) } -func (a *myInboundAdapter) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (a *myInboundAdapter) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { user, loaded := auth.UserFromContext[string](ctx) if !loaded { a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) + a.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return } metadata.User = user a.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) + a.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (a *myInboundAdapter) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (a *myInboundAdapter) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { user, loaded := auth.UserFromContext[string](ctx) if !loaded { a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) + a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return } metadata.User = user a.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) + a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } diff --git a/inbound/mixed.go b/inbound/mixed.go index 3933f7af05..81f6a43a09 100644 --- a/inbound/mixed.go +++ b/inbound/mixed.go @@ -4,7 +4,6 @@ import ( std_bufio "bufio" "context" "net" - "os" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/uot" @@ -12,6 +11,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/protocol/http" "github.com/sagernet/sing/protocol/socks" @@ -20,8 +20,8 @@ import ( ) var ( - _ adapter.Inbound = (*Mixed)(nil) - _ adapter.InjectableInbound = (*Mixed)(nil) + _ adapter.Inbound = (*Mixed)(nil) + _ adapter.TCPInjectableInbound = (*Mixed)(nil) ) type Mixed struct { @@ -47,20 +47,24 @@ func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogg return inbound } -func (h *Mixed) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Mixed) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Mixed) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { reader := std_bufio.NewReader(conn) headerBytes, err := reader.Peek(1) if err != nil { - return err + return E.Cause(err, "peek first byte") } switch headerBytes[0] { case socks4.Version, socks5.Version: - return socks.HandleConnection0(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) + return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) default: - return http.HandleConnection(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) + return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) } } - -func (h *Mixed) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/inbound/naive.go b/inbound/naive.go index 07328c09f4..498e823cec 100644 --- a/inbound/naive.go +++ b/inbound/naive.go @@ -17,6 +17,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-box/transport/v2rayhttp" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/buf" @@ -164,13 +165,13 @@ func (n *Naive) ServeHTTP(writer http.ResponseWriter, request *http.Request) { n.badRequest(ctx, request, E.New("hijack failed")) return } - n.newConnection(ctx, &naiveH1Conn{Conn: conn}, userName, source, destination) + n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination) } else { - n.newConnection(ctx, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) + n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) } } -func (n *Naive) newConnection(ctx context.Context, conn net.Conn, userName string, source, destination M.Socksaddr) { +func (n *Naive) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) { if userName != "" { n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) @@ -178,19 +179,26 @@ func (n *Naive) newConnection(ctx context.Context, conn net.Conn, userName strin n.logger.InfoContext(ctx, "inbound connection from ", source) n.logger.InfoContext(ctx, "inbound connection to ", destination) } - hErr := n.router.RouteConnection(ctx, conn, n.createMetadata(conn, adapter.InboundContext{ + metadata := n.createMetadata(conn, adapter.InboundContext{ Source: source, Destination: destination, User: userName, - })) - if hErr != nil { - conn.Close() - n.NewError(ctx, E.Cause(hErr, "process connection from ", source)) + }) + if !waitForClose { + n.router.RouteConnectionEx(ctx, conn, metadata, nil) + } else { + done := make(chan struct{}) + wrapper := v2rayhttp.NewHTTP2Wrapper(conn) + n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) + <-done + wrapper.CloseWrapper() } } func (n *Naive) badRequest(ctx context.Context, request *http.Request, err error) { - n.NewError(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) + n.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) } func rejectHTTP(writer http.ResponseWriter, statusCode int) { diff --git a/inbound/redirect.go b/inbound/redirect.go index 4c7cf1d585..c4c6faf341 100644 --- a/inbound/redirect.go +++ b/inbound/redirect.go @@ -9,7 +9,6 @@ import ( 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" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -34,11 +33,13 @@ func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextL return redirect } -func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (r *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { destination, err := redir.GetOriginalDestination(conn) if err != nil { - return E.Cause(err, "get redirect destination") + conn.Close() + r.logger.ErrorContext(ctx, "process connection from ", conn.RemoteAddr(), ": get redirect destination: ", err) + return } metadata.Destination = M.SocksaddrFromNetIP(destination) - return r.newConnection(ctx, conn, metadata) + r.newConnectionEx(ctx, conn, metadata, onClose) } diff --git a/inbound/shadowsocks.go b/inbound/shadowsocks.go index ca15b8d8e8..3fff231d8f 100644 --- a/inbound/shadowsocks.go +++ b/inbound/shadowsocks.go @@ -3,7 +3,6 @@ package inbound import ( "context" "net" - "os" "time" "github.com/sagernet/sing-box/adapter" @@ -18,6 +17,7 @@ import ( "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" "github.com/sagernet/sing/common/ntp" ) @@ -36,8 +36,8 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte } var ( - _ adapter.Inbound = (*Shadowsocks)(nil) - _ adapter.InjectableInbound = (*Shadowsocks)(nil) + _ adapter.Inbound = (*Shadowsocks)(nil) + _ adapter.TCPInjectableInbound = (*Shadowsocks)(nil) ) type Shadowsocks struct { @@ -74,11 +74,11 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte } switch { case options.Method == shadowsocks.MethodNone: - inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), inbound.upstreamContextHandler()) + inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) case common.Contains(shadowaead.List, options.Method): - inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), inbound.upstreamContextHandler()) + inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) case common.Contains(shadowaead_2022.List, options.Method): - inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), inbound.upstreamContextHandler(), ntp.TimeFuncFromContext(ctx)) + inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx)) default: err = E.New("unsupported method: ", options.Method) } @@ -86,14 +86,29 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte return inbound, err } -func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) +func (h *Shadowsocks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Shadowsocks) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } } -func (h *Shadowsocks) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata)) +func (h *Shadowsocks) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) } -func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *Shadowsocks) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + ctx = log.ContextWithNewID(ctx) + h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) } diff --git a/inbound/shadowsocks_multi.go b/inbound/shadowsocks_multi.go index a291af4acb..2953419490 100644 --- a/inbound/shadowsocks_multi.go +++ b/inbound/shadowsocks_multi.go @@ -20,13 +20,14 @@ import ( "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" ) var ( - _ adapter.Inbound = (*ShadowsocksMulti)(nil) - _ adapter.InjectableInbound = (*ShadowsocksMulti)(nil) + _ adapter.Inbound = (*ShadowsocksMulti)(nil) + _ adapter.TCPInjectableInbound = (*ShadowsocksMulti)(nil) ) type ShadowsocksMulti struct { @@ -66,14 +67,15 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log. options.Method, options.Password, int64(udpTimeout.Seconds()), - adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), + adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx), ) } else if common.Contains(shadowaead.List, options.Method) { service, err = shadowaead.NewMultiService[int]( options.Method, int64(udpTimeout.Seconds()), - adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) + adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), + ) } else { return nil, E.New("unsupported method: " + options.Method) } @@ -94,16 +96,19 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log. return inbound, err } -func (h *ShadowsocksMulti) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *ShadowsocksMulti) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata)) +func (h *ShadowsocksMulti) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } -func (h *ShadowsocksMulti) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *ShadowsocksMulti) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } } func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -118,7 +123,7 @@ func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, met metadata.User = user } h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) + return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) } func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { @@ -135,5 +140,5 @@ func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, metadata) + return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) } diff --git a/inbound/shadowsocks_relay.go b/inbound/shadowsocks_relay.go index fbc2838ade..02246a3f3c 100644 --- a/inbound/shadowsocks_relay.go +++ b/inbound/shadowsocks_relay.go @@ -16,13 +16,15 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) var ( - _ adapter.Inbound = (*ShadowsocksRelay)(nil) - _ adapter.InjectableInbound = (*ShadowsocksRelay)(nil) + _ adapter.Inbound = (*ShadowsocksRelay)(nil) + _ adapter.TCPInjectableInbound = (*ShadowsocksRelay)(nil) ) type ShadowsocksRelay struct { @@ -61,7 +63,7 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log. options.Method, options.Password, int64(udpTimeout.Seconds()), - adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), + adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ) if err != nil { return nil, err @@ -79,16 +81,19 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log. return inbound, err } -func (h *ShadowsocksRelay) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *ShadowsocksRelay) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata)) +func (h *ShadowsocksRelay) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } -func (h *ShadowsocksRelay) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *ShadowsocksRelay) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } } func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -103,7 +108,7 @@ func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, met metadata.User = destination } h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) + return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) } func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { @@ -120,5 +125,5 @@ func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, metadata) + return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) } diff --git a/inbound/shadowtls.go b/inbound/shadowtls.go index 358564f221..ca14228635 100644 --- a/inbound/shadowtls.go +++ b/inbound/shadowtls.go @@ -12,6 +12,7 @@ import ( "github.com/sagernet/sing-shadowtls" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" ) @@ -91,3 +92,11 @@ func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata a } return h.router.RouteConnection(ctx, conn, metadata) } + +func (h *ShadowTLS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.NewConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} diff --git a/inbound/socks.go b/inbound/socks.go index 7c5183c9f7..04b0a77dc7 100644 --- a/inbound/socks.go +++ b/inbound/socks.go @@ -1,9 +1,9 @@ package inbound import ( + std_bufio "bufio" "context" "net" - "os" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/uot" @@ -11,13 +11,14 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/protocol/socks" ) var ( - _ adapter.Inbound = (*Socks)(nil) - _ adapter.InjectableInbound = (*Socks)(nil) + _ adapter.Inbound = (*Socks)(nil) + _ adapter.TCPInjectableInbound = (*Socks)(nil) ) type Socks struct { @@ -42,10 +43,10 @@ func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogg return inbound } -func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return socks.HandleConnection(ctx, conn, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) -} - -func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *Socks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } diff --git a/inbound/tproxy.go b/inbound/tproxy.go index a074eb495b..40653c797b 100644 --- a/inbound/tproxy.go +++ b/inbound/tproxy.go @@ -18,12 +18,12 @@ 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/udpnat" + "github.com/sagernet/sing/common/udpnat2" ) type TProxy struct { myInboundAdapter - udpNat *udpnat.Service[netip.AddrPort] + udpNat *udpnat.Service } func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) *TProxy { @@ -46,8 +46,7 @@ func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLog } tproxy.connHandler = tproxy tproxy.oobPacketHandler = tproxy - tproxy.udpNat = udpnat.New[netip.AddrPort](int64(udpTimeout.Seconds()), tproxy.upstreamContextHandler()) - tproxy.packetUpstream = tproxy.udpNat + tproxy.udpNat = udpnat.New(tproxy, tproxy.preparePacketConnection, udpTimeout, false) return tproxy } @@ -75,35 +74,43 @@ func (t *TProxy) Start() error { return nil } -func (t *TProxy) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (t *TProxy) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - return t.newConnection(ctx, conn, metadata) + t.newConnectionEx(ctx, conn, metadata, onClose) } -func (t *TProxy) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata adapter.InboundContext) error { +func (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + t.newPacketConnectionEx(ctx, conn, t.createPacketMetadataEx(source, destination), onClose) +} + +func (t *TProxy) NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) { destination, err := redir.GetOriginalDestinationFromOOB(oob) if err != nil { - return E.Cause(err, "get tproxy destination") + t.logger.Warn("process packet from ", source, ": get tproxy destination: ", err) + return } - metadata.Destination = M.SocksaddrFromNetIP(destination).Unwrap() - t.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) { - return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &tproxyPacketWriter{ctx: ctx, source: natConn, destination: metadata.Destination} - }) - return nil + t.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.SocksaddrFromNetIP(destination), nil) } type tproxyPacketWriter struct { ctx context.Context - source N.PacketConn + source netip.AddrPort destination M.Socksaddr conn *net.UDPConn } +func (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + writer := &tproxyPacketWriter{ctx: t.ctx, source: source.AddrPort(), destination: destination} + return true, t.ctx, writer, func(it error) { + common.Close(common.PtrOrNil(writer.conn)) + } +} + func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { defer buffer.Release() conn := w.conn if w.destination == destination && conn != nil { - _, err := conn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr())) + _, err := conn.WriteToUDPAddrPort(buffer.Bytes(), w.source) if err != nil { w.conn = nil } @@ -122,9 +129,5 @@ func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socks } else { defer udpConn.Close() } - return common.Error(udpConn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr()))) -} - -func (w *tproxyPacketWriter) Close() error { - return common.Close(common.PtrOrNil(w.conn)) + return common.Error(udpConn.WriteToUDPAddrPort(buffer.Bytes(), w.source)) } diff --git a/inbound/trojan.go b/inbound/trojan.go index 203b6d3927..ce003dda70 100644 --- a/inbound/trojan.go +++ b/inbound/trojan.go @@ -22,8 +22,8 @@ import ( ) var ( - _ adapter.Inbound = (*Trojan)(nil) - _ adapter.InjectableInbound = (*Trojan)(nil) + _ adapter.Inbound = (*Trojan)(nil) + _ adapter.TCPInjectableInbound = (*Trojan)(nil) ) type Trojan struct { @@ -90,7 +90,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*trojanTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*trojanTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -149,11 +149,6 @@ func (h *Trojan) Close() error { ) } -func (h *Trojan) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.injectTCP(conn, metadata) - return nil -} - func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { @@ -165,8 +160,12 @@ func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adap return h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *Trojan) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.NewConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -226,9 +225,6 @@ var _ adapter.V2RayServerTransportHandler = (*trojanTransportHandler)(nil) type trojanTransportHandler Trojan -func (t *trojanTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - return (*Trojan)(t).newTransportConnection(ctx, conn, adapter.InboundContext{ - Source: metadata.Source, - Destination: metadata.Destination, - }) +func (t *trojanTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + (*Trojan)(t).routeTCP(ctx, conn, source, destination, onClose) } diff --git a/inbound/tun.go b/inbound/tun.go index 721112d779..0d856419ef 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -28,17 +28,18 @@ import ( "go4.org/netipx" ) -var _ adapter.Inbound = (*Tun)(nil) +var _ adapter.Inbound = (*TUN)(nil) -type Tun struct { - tag string - ctx context.Context - router adapter.Router - logger log.ContextLogger +type TUN struct { + tag string + ctx context.Context + router adapter.Router + logger log.ContextLogger + // Deprecated inboundOptions option.InboundOptions tunOptions tun.Options endpointIndependentNat bool - udpTimeout int64 + udpTimeout time.Duration stack string tunIf tun.Tun tunStack tun.Stack @@ -53,7 +54,7 @@ type Tun struct { routeExcludeAddressSet []*netipx.IPSet } -func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) { +func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*TUN, error) { address := options.Address var deprecatedAddressUsed bool //nolint:staticcheck @@ -162,7 +163,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger outputMark = tun.DefaultAutoRedirectOutputMark } - inbound := &Tun{ + inbound := &TUN{ tag: tag, ctx: ctx, router: router, @@ -194,7 +195,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger InterfaceMonitor: router.InterfaceMonitor(), }, endpointIndependentNat: options.EndpointIndependentNat, - udpTimeout: int64(udpTimeout.Seconds()), + udpTimeout: udpTimeout, stack: options.Stack, platformInterface: platformInterface, platformOptions: common.PtrValueOrDefault(options.Platform), @@ -207,7 +208,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{ TunOptions: &inbound.tunOptions, Context: ctx, - Handler: inbound, + Handler: (*autoRedirectHandler)(inbound), Logger: logger, NetworkMonitor: router.NetworkMonitor(), InterfaceFinder: router.InterfaceFinder(), @@ -283,17 +284,17 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges. return uidRanges, nil } -func (t *Tun) Type() string { +func (t *TUN) Type() string { return C.TypeTun } -func (t *Tun) Tag() string { +func (t *TUN) Tag() string { return t.tag } -func (t *Tun) Start() error { +func (t *TUN) Start() error { if C.IsAndroid && t.platformInterface == nil { - t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t) + t.tunOptions.BuildAndroidRules(t.router.PackageManager()) } if t.tunOptions.Name == "" { t.tunOptions.Name = tun.CalculateInterfaceName("") @@ -327,7 +328,6 @@ func (t *Tun) Start() error { Context: t.ctx, Tun: tunInterface, TunOptions: t.tunOptions, - EndpointIndependentNat: t.endpointIndependentNat, UDPTimeout: t.udpTimeout, Handler: t, Logger: t.logger, @@ -349,7 +349,7 @@ func (t *Tun) Start() error { return nil } -func (t *Tun) PostStart() error { +func (t *TUN) PostStart() error { monitor := taskmonitor.New(t.logger, C.StartTimeout) if t.autoRedirect != nil { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) @@ -388,7 +388,7 @@ func (t *Tun) PostStart() error { return nil } -func (t *Tun) updateRouteAddressSet(it adapter.RuleSet) { +func (t *TUN) updateRouteAddressSet(it adapter.RuleSet) { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) t.autoRedirect.UpdateRouteAddressSet() @@ -396,7 +396,7 @@ func (t *Tun) updateRouteAddressSet(it adapter.RuleSet) { t.routeExcludeAddressSet = nil } -func (t *Tun) Close() error { +func (t *TUN) Close() error { return common.Close( t.tunStack, t.tunIf, @@ -404,44 +404,48 @@ func (t *Tun) Close() error { ) } -func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error { +func (t *TUN) PrepareConnection(source M.Socksaddr, destination M.Socksaddr) error { + // TODO: implement rejects + return nil +} + +func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag metadata.InboundType = C.TypeTun - metadata.Source = upstreamMetadata.Source - metadata.Destination = upstreamMetadata.Destination + metadata.Source = source + metadata.Destination = destination metadata.InboundOptions = t.inboundOptions - if upstreamMetadata.Protocol != "" { - t.logger.InfoContext(ctx, "inbound ", upstreamMetadata.Protocol, " connection from ", metadata.Source) - } else { - t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - } + t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - err := t.router.RouteConnection(ctx, conn, metadata) - if err != nil { - t.NewError(ctx, err) - } - return nil + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { +func (t *TUN) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag metadata.InboundType = C.TypeTun - metadata.Source = upstreamMetadata.Source - metadata.Destination = upstreamMetadata.Destination + metadata.Source = source + metadata.Destination = destination metadata.InboundOptions = t.inboundOptions t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - err := t.router.RoutePacketConnection(ctx, conn, metadata) - if err != nil { - t.NewError(ctx, err) - } - return nil + t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (t *Tun) NewError(ctx context.Context, err error) { - NewError(t.logger, ctx, err) +type autoRedirectHandler TUN + +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 + metadata.Inbound = t.tag + metadata.InboundType = C.TypeTun + metadata.Source = source + metadata.Destination = destination + 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/inbound/vless.go b/inbound/vless.go index 567475647a..ec26bd8859 100644 --- a/inbound/vless.go +++ b/inbound/vless.go @@ -25,8 +25,8 @@ import ( ) var ( - _ adapter.Inbound = (*VLESS)(nil) - _ adapter.InjectableInbound = (*VLESS)(nil) + _ adapter.Inbound = (*VLESS)(nil) + _ adapter.TCPInjectableInbound = (*VLESS)(nil) ) type VLESS struct { @@ -73,7 +73,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -128,11 +128,6 @@ func (h *VLESS) Close() error { ) } -func (h *VLESS) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.injectTCP(conn, metadata) - return nil -} - func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { @@ -144,8 +139,12 @@ func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *VLESS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.NewConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -188,9 +187,6 @@ var _ adapter.V2RayServerTransportHandler = (*vlessTransportHandler)(nil) type vlessTransportHandler VLESS -func (t *vlessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - return (*VLESS)(t).newTransportConnection(ctx, conn, adapter.InboundContext{ - Source: metadata.Source, - Destination: metadata.Destination, - }) +func (t *vlessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + t.routeTCP(ctx, conn, source, destination, onClose) } diff --git a/inbound/vmess.go b/inbound/vmess.go index 154512751c..9099bd6293 100644 --- a/inbound/vmess.go +++ b/inbound/vmess.go @@ -25,8 +25,8 @@ import ( ) var ( - _ adapter.Inbound = (*VMess)(nil) - _ adapter.InjectableInbound = (*VMess)(nil) + _ adapter.Inbound = (*VMess)(nil) + _ adapter.TCPInjectableInbound = (*VMess)(nil) ) type VMess struct { @@ -83,7 +83,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vmessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vmessTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -142,11 +142,6 @@ func (h *VMess) Close() error { ) } -func (h *VMess) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.injectTCP(conn, metadata) - return nil -} - func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { @@ -158,8 +153,12 @@ func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *VMess) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.NewConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -202,9 +201,6 @@ var _ adapter.V2RayServerTransportHandler = (*vmessTransportHandler)(nil) type vmessTransportHandler VMess -func (t *vmessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - return (*VMess)(t).newTransportConnection(ctx, conn, adapter.InboundContext{ - Source: metadata.Source, - Destination: metadata.Destination, - }) +func (t *vmessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + (*VMess)(t).routeTCP(ctx, conn, source, destination, onClose) } diff --git a/include/quic_stub.go b/include/quic_stub.go index ddf9723f9a..43aa58d996 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -11,6 +11,7 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -20,7 +21,7 @@ func init() { return nil, C.ErrQUICNotIncluded }) v2ray.RegisterQUICConstructor( - func(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { + func(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { return nil, C.ErrQUICNotIncluded }, func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/option/inbound.go b/option/inbound.go index 54e8bab84a..d38799048f 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -98,6 +98,7 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error { return nil } +// Deprecated: Use rule action instead type InboundOptions struct { SniffEnabled bool `json:"sniff,omitempty"` SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` diff --git a/option/rule.go b/option/rule.go index 0c66ab1d53..0b11cbdda1 100644 --- a/option/rule.go +++ b/option/rule.go @@ -64,7 +64,7 @@ func (r Rule) IsValid() bool { } } -type DefaultRule struct { +type RawDefaultRule struct { Inbound Listable[string] `json:"inbound,omitempty"` IPVersion int `json:"ip_version,omitempty"` Network Listable[string] `json:"network,omitempty"` @@ -98,26 +98,58 @@ type DefaultRule struct { RuleSet Listable[string] `json:"rule_set,omitempty"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` Invert bool `json:"invert,omitempty"` - Outbound string `json:"outbound,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` } +type DefaultRule struct { + RawDefaultRule + RuleAction +} + +func (r *DefaultRule) MarshalJSON() ([]byte, error) { + return MarshallObjects(r.RawDefaultRule, r.RuleAction) +} + +func (r *DefaultRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r.RawDefaultRule) + if err != nil { + return err + } + return UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) +} + func (r *DefaultRule) IsValid() bool { var defaultValue DefaultRule defaultValue.Invert = r.Invert - defaultValue.Outbound = r.Outbound + defaultValue.Action = r.Action return !reflect.DeepEqual(r, defaultValue) } +type _LogicalRule struct { + Mode string `json:"mode"` + Rules []Rule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` +} + type LogicalRule struct { - Mode string `json:"mode"` - Rules []Rule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Outbound string `json:"outbound,omitempty"` + _LogicalRule + RuleAction +} + +func (r *LogicalRule) MarshalJSON() ([]byte, error) { + return MarshallObjects(r._LogicalRule, r.RuleAction) +} + +func (r *LogicalRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r._LogicalRule) + if err != nil { + return err + } + return UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) } -func (r LogicalRule) IsValid() bool { +func (r *LogicalRule) IsValid() bool { return len(r.Rules) > 0 && common.All(r.Rules, Rule.IsValid) } diff --git a/option/rule_action.go b/option/rule_action.go new file mode 100644 index 0000000000..4f0ec177b8 --- /dev/null +++ b/option/rule_action.go @@ -0,0 +1,166 @@ +package option + +import ( + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" +) + +type _RuleAction struct { + Action string `json:"action,omitempty"` + RouteOptions RouteActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` + SniffOptions RouteActionSniff `json:"-"` + ResolveOptions RouteActionResolve `json:"-"` +} + +type RuleAction _RuleAction + +func (r RuleAction) MarshalJSON() ([]byte, error) { + var v any + switch r.Action { + case C.RuleActionTypeRoute: + r.Action = "" + v = r.RouteOptions + case C.RuleActionTypeReturn: + v = nil + case C.RuleActionTypeReject: + v = r.RejectOptions + case C.RuleActionTypeHijackDNS: + v = nil + case C.RuleActionTypeSniff: + v = r.SniffOptions + case C.RuleActionTypeResolve: + v = r.ResolveOptions + default: + return nil, E.New("unknown rule action: " + r.Action) + } + if v == nil { + return MarshallObjects((_RuleAction)(r)) + } + return MarshallObjects((_RuleAction)(r), v) +} + +func (r *RuleAction) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_RuleAction)(r)) + if err != nil { + return err + } + var v any + switch r.Action { + case "", C.RuleActionTypeRoute: + r.Action = C.RuleActionTypeRoute + v = &r.RouteOptions + case C.RuleActionTypeReturn: + v = nil + case C.RuleActionTypeReject: + v = &r.RejectOptions + case C.RuleActionTypeHijackDNS: + v = nil + case C.RuleActionTypeSniff: + v = &r.SniffOptions + case C.RuleActionTypeResolve: + v = &r.ResolveOptions + default: + return E.New("unknown rule action: " + r.Action) + } + if v == nil { + // check unknown fields + return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{}) + } + return UnmarshallExcluded(data, (*_RuleAction)(r), v) +} + +type _DNSRuleAction struct { + Action string `json:"action,omitempty"` + RouteOptions DNSRouteActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` + SniffOptions RouteActionSniff `json:"-"` + ResolveOptions RouteActionResolve `json:"-"` +} + +type DNSRuleAction _DNSRuleAction + +func (r DNSRuleAction) MarshalJSON() ([]byte, error) { + var v any + switch r.Action { + case C.RuleActionTypeRoute: + r.Action = "" + v = r.RouteOptions + case C.RuleActionTypeReturn: + v = nil + case C.RuleActionTypeReject: + v = r.RejectOptions + default: + return nil, E.New("unknown DNS rule action: " + r.Action) + } + if v == nil { + return MarshallObjects((_DNSRuleAction)(r)) + } + return MarshallObjects((_DNSRuleAction)(r), v) +} + +func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DNSRuleAction)(r)) + if err != nil { + return err + } + var v any + switch r.Action { + case "", C.RuleActionTypeRoute: + r.Action = C.RuleActionTypeRoute + v = &r.RouteOptions + case C.RuleActionTypeReturn: + v = nil + case C.RuleActionTypeReject: + v = &r.RejectOptions + default: + return E.New("unknown DNS rule action: " + r.Action) + } + if v == nil { + // check unknown fields + return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{}) + } + return UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) +} + +type RouteActionOptions struct { + Outbound string `json:"outbound"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` +} + +type DNSRouteActionOptions struct { + Server string `json:"server"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` +} + +type RejectActionOptions struct { + Method RejectMethod `json:"method,omitempty"` +} + +type RejectMethod string + +func (m *RejectMethod) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*string)(m)) + if err != nil { + return err + } + switch *m { + case C.RuleActionRejectMethodDefault, C.RuleActionRejectMethodPortUnreachable, C.RuleActionRejectMethodDrop: + return nil + default: + return E.New("unknown reject method: " + *m) + } +} + +type RouteActionSniff struct { + Sniffer Listable[string] `json:"sniffer,omitempty"` + Timeout Duration `json:"timeout,omitempty"` +} + +type RouteActionResolve struct { + Strategy DomainStrategy `json:"strategy,omitempty"` + Server string `json:"server,omitempty"` +} diff --git a/option/rule_dns.go b/option/rule_dns.go index 2e52d6c561..b328c45c01 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -64,7 +64,7 @@ func (r DNSRule) IsValid() bool { } } -type DefaultDNSRule struct { +type RawDefaultDNSRule struct { Inbound Listable[string] `json:"inbound,omitempty"` IPVersion int `json:"ip_version,omitempty"` QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` @@ -100,35 +100,58 @@ type DefaultDNSRule struct { 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"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` } +type DefaultDNSRule struct { + RawDefaultDNSRule + DNSRuleAction +} + +func (r *DefaultDNSRule) MarshalJSON() ([]byte, error) { + return MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) +} + +func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r.RawDefaultDNSRule) + if err != nil { + return err + } + return UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) +} + func (r *DefaultDNSRule) IsValid() bool { var defaultValue DefaultDNSRule defaultValue.Invert = r.Invert - defaultValue.Server = r.Server - defaultValue.DisableCache = r.DisableCache - defaultValue.RewriteTTL = r.RewriteTTL - defaultValue.ClientSubnet = r.ClientSubnet + defaultValue.DNSRuleAction = r.DNSRuleAction return !reflect.DeepEqual(r, defaultValue) } +type _LogicalDNSRule struct { + Mode string `json:"mode"` + Rules []DNSRule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` +} + type LogicalDNSRule struct { - Mode string `json:"mode"` - Rules []DNSRule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + _LogicalDNSRule + DNSRuleAction +} + +func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) { + return MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) +} + +func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r._LogicalDNSRule) + if err != nil { + return err + } + return UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) } -func (r LogicalDNSRule) IsValid() bool { +func (r *LogicalDNSRule) IsValid() bool { return len(r.Rules) > 0 && common.All(r.Rules, DNSRule.IsValid) } diff --git a/option/types.go b/option/types.go index b17f222248..bb6485491e 100644 --- a/option/types.go +++ b/option/types.go @@ -81,8 +81,11 @@ func (a *AddrPrefix) UnmarshalJSON(content []byte) error { return prefixErr } -func (a AddrPrefix) Build() netip.Prefix { - return netip.Prefix(a) +func (a *AddrPrefix) Build() netip.Prefix { + if a == nil { + return netip.Prefix{} + } + return netip.Prefix(*a) } type NetworkList string @@ -143,12 +146,29 @@ func (l *Listable[T]) UnmarshalJSON(content []byte) error { type DomainStrategy dns.DomainStrategy +func (s DomainStrategy) String() string { + switch dns.DomainStrategy(s) { + case dns.DomainStrategyAsIS: + return "" + case dns.DomainStrategyPreferIPv4: + return "prefer_ipv4" + case dns.DomainStrategyPreferIPv6: + return "prefer_ipv6" + case dns.DomainStrategyUseIPv4: + return "ipv4_only" + case dns.DomainStrategyUseIPv6: + return "ipv6_only" + default: + panic(E.New("unknown domain strategy: ", s)) + } +} + func (s DomainStrategy) MarshalJSON() ([]byte, error) { var value string switch dns.DomainStrategy(s) { case dns.DomainStrategyAsIS: value = "" - // value = "AsIS" + // value = "as_is" case dns.DomainStrategyPreferIPv4: value = "prefer_ipv4" case dns.DomainStrategyPreferIPv6: diff --git a/outbound/block.go b/outbound/block.go index e73c44228b..b6ccefe2be 100644 --- a/outbound/block.go +++ b/outbound/block.go @@ -39,12 +39,14 @@ func (h *Block) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. return nil, io.EOF } +// Deprecated func (h *Block) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { conn.Close() h.logger.InfoContext(ctx, "blocked connection to ", metadata.Destination) return nil } +// Deprecated func (h *Block) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { conn.Close() h.logger.InfoContext(ctx, "blocked packet connection to ", metadata.Destination) diff --git a/outbound/default.go b/outbound/default.go index 972aca9479..a34ac97af1 100644 --- a/outbound/default.go +++ b/outbound/default.go @@ -69,7 +69,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a if err != nil { return N.ReportHandshakeFailure(conn, err) } - err = N.ReportHandshakeSuccess(conn) + err = N.ReportConnHandshakeSuccess(conn, outConn) if err != nil { outConn.Close() return err @@ -96,7 +96,7 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial if err != nil { return N.ReportHandshakeFailure(conn, err) } - err = N.ReportHandshakeSuccess(conn) + err = N.ReportConnHandshakeSuccess(conn, outConn) if err != nil { outConn.Close() return err @@ -117,14 +117,14 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, if err != nil { return N.ReportHandshakeFailure(conn, err) } - err = N.ReportHandshakeSuccess(conn) + err = N.ReportPacketConnHandshakeSuccess(conn, outConn) if err != nil { outConn.Close() return err } if destinationAddress.IsValid() { if metadata.Destination.IsFqdn() { - if metadata.InboundOptions.UDPDisableDomainUnmapping { + if metadata.UDPDisableDomainUnmapping { outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) } else { outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) @@ -165,7 +165,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this if err != nil { return N.ReportHandshakeFailure(conn, err) } - err = N.ReportHandshakeSuccess(conn) + err = N.ReportPacketConnHandshakeSuccess(conn, outConn) if err != nil { outConn.Close() return err diff --git a/outbound/direct.go b/outbound/direct.go index c873941c11..415a72f370 100644 --- a/outbound/direct.go +++ b/outbound/direct.go @@ -30,7 +30,7 @@ type Direct struct { fallbackDelay time.Duration overrideOption int overrideDestination M.Socksaddr - loopBack *loopBackDetector + // loopBack *loopBackDetector } func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) { @@ -51,7 +51,7 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), dialer: outboundDialer, - loopBack: newLoopBackDetector(router), + // loopBack: newLoopBackDetector(router), } if options.ProxyProtocol != 0 { return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") @@ -90,11 +90,12 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M. case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - conn, err := h.dialer.DialContext(ctx, network, destination) + /*conn, err := h.dialer.DialContext(ctx, network, destination) if err != nil { return nil, err } - return h.loopBack.NewConn(conn), nil + return h.loopBack.NewConn(conn), nil*/ + return h.dialer.DialContext(ctx, network, destination) } func (h *Direct) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { @@ -148,14 +149,14 @@ func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net if err != nil { return nil, err } - conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination) + // conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination) if originDestination != destination { conn = bufio.NewNATPacketConn(bufio.NewPacketConn(conn), destination, originDestination) } return conn, nil } -func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +/*func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { return E.New("reject loopback connection to ", metadata.Destination) } @@ -168,3 +169,4 @@ func (h *Direct) NewPacketConnection(ctx context.Context, conn N.PacketConn, met } return NewPacketConnection(ctx, h, conn, metadata) } +*/ diff --git a/outbound/dns.go b/outbound/dns.go index 6ad4a0f8ce..08661a99aa 100644 --- a/outbound/dns.go +++ b/outbound/dns.go @@ -45,6 +45,7 @@ func (d *DNS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.Pa return nil, os.ErrInvalid } +// Deprecated func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} defer conn.Close() @@ -97,6 +98,7 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap return nil } +// Deprecated func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn diff --git a/outbound/http.go b/outbound/http.go index cbcbce075a..6f15afb5dc 100644 --- a/outbound/http.go +++ b/outbound/http.go @@ -64,11 +64,3 @@ func (h *HTTP) DialContext(ctx context.Context, network string, destination M.So func (h *HTTP) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *HTTP) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/outbound/hysteria.go b/outbound/hysteria.go index 50383af5d4..f3c30739cf 100644 --- a/outbound/hysteria.go +++ b/outbound/hysteria.go @@ -122,14 +122,6 @@ func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return h.client.ListenPacket(ctx, destination) } -func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Hysteria) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *Hysteria) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } diff --git a/outbound/hysteria2.go b/outbound/hysteria2.go index 9079e40308..5e46f6a866 100644 --- a/outbound/hysteria2.go +++ b/outbound/hysteria2.go @@ -108,14 +108,6 @@ func (h *Hysteria2) ListenPacket(ctx context.Context, destination M.Socksaddr) ( return h.client.ListenPacket(ctx) } -func (h *Hysteria2) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Hysteria2) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *Hysteria2) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } diff --git a/outbound/proxy.go b/outbound/proxy.go index fbc4848180..38c1845392 100644 --- a/outbound/proxy.go +++ b/outbound/proxy.go @@ -94,6 +94,9 @@ func (l *ProxyListener) acceptLoop() { } } +// TODO: migrate to new api +// +//nolint:staticcheck func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error { return socks.HandleConnection(ctx, conn, l.authenticator, l, M.Metadata{}) } diff --git a/outbound/selector.go b/outbound/selector.go index 7326f58f0f..64e6a2f936 100644 --- a/outbound/selector.go +++ b/outbound/selector.go @@ -146,14 +146,26 @@ func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil } +// TODO +// Deprecated func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return s.selected.NewConnection(ctx, conn, metadata) + if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { + return legacyHandler.NewConnection(ctx, conn, metadata) + } else { + return NewConnection(ctx, s.selected, conn, metadata) + } } +// TODO +// Deprecated func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return s.selected.NewPacketConnection(ctx, conn, metadata) + if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { + return legacyHandler.NewPacketConnection(ctx, conn, metadata) + } else { + return NewPacketConnection(ctx, s.selected, conn, metadata) + } } func RealTag(detour adapter.Outbound) string { diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index 9f8c1cbdb2..153542742a 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -125,14 +125,6 @@ func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) } } -func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *Shadowsocks) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() diff --git a/outbound/shadowtls.go b/outbound/shadowtls.go index 38de8c0baf..ff1b9d6c45 100644 --- a/outbound/shadowtls.go +++ b/outbound/shadowtls.go @@ -106,11 +106,3 @@ func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination func (h *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *ShadowTLS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/outbound/socks.go b/outbound/socks.go index f4757467c5..575d6eb3dd 100644 --- a/outbound/socks.go +++ b/outbound/socks.go @@ -113,6 +113,8 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. return h.client.ListenPacket(ctx, destination) } +// TODO +// Deprecated func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.resolve { return NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) @@ -121,6 +123,8 @@ func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapt } } +// TODO +// Deprecated func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { if h.resolve { return NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) diff --git a/outbound/ssh.go b/outbound/ssh.go index ce62b00481..28abe9a5e3 100644 --- a/outbound/ssh.go +++ b/outbound/ssh.go @@ -199,11 +199,3 @@ func (s *SSH) DialContext(ctx context.Context, network string, destination M.Soc func (s *SSH) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (s *SSH) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, s, conn, metadata) -} - -func (s *SSH) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/outbound/tor.go b/outbound/tor.go index 8ae73a66fa..ccc0c0cfe5 100644 --- a/outbound/tor.go +++ b/outbound/tor.go @@ -211,11 +211,3 @@ func (t *Tor) DialContext(ctx context.Context, network string, destination M.Soc func (t *Tor) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (t *Tor) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, t, conn, metadata) -} - -func (t *Tor) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/outbound/trojan.go b/outbound/trojan.go index bde251d00a..ee0b2a4b9e 100644 --- a/outbound/trojan.go +++ b/outbound/trojan.go @@ -99,14 +99,6 @@ func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net } } -func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *Trojan) InterfaceUpdated() { if h.transport != nil { h.transport.Close() diff --git a/outbound/tuic.go b/outbound/tuic.go index c098332375..aaf998b16e 100644 --- a/outbound/tuic.go +++ b/outbound/tuic.go @@ -136,14 +136,6 @@ func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.P } } -func (h *TUIC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *TUIC) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *TUIC) InterfaceUpdated() { _ = h.client.CloseWithError(E.New("network changed")) } diff --git a/outbound/urltest.go b/outbound/urltest.go index c6e38ec5a8..564a0ddc3a 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -167,11 +167,15 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne return nil, err } +// TODO +// Deprecated func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) return NewConnection(ctx, s, conn, metadata) } +// TODO +// Deprecated func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) return NewPacketConnection(ctx, s, conn, metadata) diff --git a/outbound/vless.go b/outbound/vless.go index a81678f015..536a1e8fba 100644 --- a/outbound/vless.go +++ b/outbound/vless.go @@ -118,14 +118,6 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - func (h *VLESS) InterfaceUpdated() { if h.transport != nil { h.transport.Close() diff --git a/outbound/vmess.go b/outbound/vmess.go index 3149729c00..126d2fd05f 100644 --- a/outbound/vmess.go +++ b/outbound/vmess.go @@ -146,14 +146,6 @@ func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - type vmessDialer VMess func (h *vmessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/outbound/wireguard.go b/outbound/wireguard.go index 6ecf536dc1..e6e4b8f8e4 100644 --- a/outbound/wireguard.go +++ b/outbound/wireguard.go @@ -241,10 +241,14 @@ func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) ( return w.tunDevice.ListenPacket(ctx, destination) } +// TODO +// Deprecated func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { return NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } +// TODO +// Deprecated func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } diff --git a/route/router_geo_resources.go b/route/geo_resources.go similarity index 98% rename from route/router_geo_resources.go rename to route/geo_resources.go index d4f65cc801..91c06796bc 100644 --- a/route/router_geo_resources.go +++ b/route/geo_resources.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing-box/common/geosite" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/deprecated" + R "github.com/sagernet/sing-box/route/rule" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/rw" @@ -32,7 +33,7 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { if err != nil { return nil, err } - rule, err = NewDefaultRule(r.ctx, r, nil, geosite.Compile(items)) + rule, err = R.NewDefaultRule(r.ctx, r, nil, geosite.Compile(items)) if err != nil { return nil, err } diff --git a/route/route.go b/route/route.go new file mode 100644 index 0000000000..86d4d95cc9 --- /dev/null +++ b/route/route.go @@ -0,0 +1,583 @@ +package route + +import ( + "context" + "errors" + "net" + "net/netip" + "os" + "os/user" + "strings" + "syscall" + "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" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/route/rule" + "github.com/sagernet/sing-dns" + "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" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/bufio/deadline" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" +) + +// Deprecated: use RouteConnectionEx instead. +func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return r.routeConnection(ctx, conn, metadata, nil) +} + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := r.routeConnection(ctx, conn, metadata, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + if E.IsClosedOrCanceled(err) { + r.logger.DebugContext(ctx, "connection closed: ", err) + } else { + r.logger.ErrorContext(ctx, err) + } + } +} + +func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + if r.pauseManager.IsDevicePaused() { + return E.New("reject connection to ", metadata.Destination, " while device paused") + } + + if metadata.InboundDetour != "" { + if metadata.LastInbound == metadata.InboundDetour { + return E.New("routing loop on detour: ", metadata.InboundDetour) + } + detour := r.inboundByTag[metadata.InboundDetour] + if detour == nil { + return E.New("inbound detour not found: ", metadata.InboundDetour) + } + injectable, isInjectable := detour.(adapter.TCPInjectableInbound) + if !isInjectable { + return E.New("inbound detour is not TCP injectable: ", metadata.InboundDetour) + } + metadata.LastInbound = metadata.Inbound + metadata.Inbound = metadata.InboundDetour + metadata.InboundDetour = "" + injectable.NewConnectionEx(ctx, conn, metadata, onClose) + return nil + } + conntrack.KillerCheck() + metadata.Network = N.NetworkTCP + switch metadata.Destination.Fqdn { + case mux.Destination.Fqdn: + return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in inbound options instead.") + case vmess.MuxDestination.Fqdn: + return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.") + case uot.MagicAddress: + return E.New("global UoT not supported since sing-box v1.7.0.") + case uot.LegacyMagicAddress: + return E.New("global UoT (legacy) not supported since sing-box v1.7.0.") + } + if deadline.NeedAdditionalReadDeadline(conn) { + conn = deadline.NewConn(conn) + } + selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, conn, nil, -1) + if err != nil { + return err + } + var selectedOutbound adapter.Outbound + var selectReturn bool + if selectedRule != nil { + switch action := selectedRule.Action().(type) { + case *rule.RuleActionRoute: + var loaded bool + selectedOutbound, loaded = r.Outbound(action.Outbound) + if !loaded { + buf.ReleaseMulti(buffers) + return E.New("outbound not found: ", action.Outbound) + } + case *rule.RuleActionReturn: + selectReturn = true + case *rule.RuleActionReject: + buf.ReleaseMulti(buffers) + var rejectErr error + switch action.Method { + case C.RuleActionRejectMethodDefault: + rejectErr = os.ErrClosed + case C.RuleActionRejectMethodPortUnreachable: + rejectErr = syscall.ECONNREFUSED + case C.RuleActionRejectMethodDrop: + rejectErr = tun.ErrDrop + } + N.CloseOnHandshakeFailure(conn, onClose, rejectErr) + return nil + } + } + if selectedRule == nil || selectReturn { + if r.defaultOutboundForConnection == nil { + buf.ReleaseMulti(buffers) + return E.New("missing default outbound with TCP support") + } + selectedOutbound = r.defaultOutboundForConnection + } + if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) { + buf.ReleaseMulti(buffers) + return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) + } + for _, buffer := range buffers { + conn = bufio.NewCachedConn(conn, buffer) + } + if r.clashServer != nil { + trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, selectedRule) + defer tracker.Leave() + conn = trackerConn + } + if r.v2rayServer != nil { + if statsService := r.v2rayServer.StatsService(); statsService != nil { + conn = statsService.RoutedConnection(metadata.Inbound, selectedOutbound.Tag(), metadata.User, conn) + } + } + legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler) + if isLegacy { + err = legacyOutbound.NewConnection(ctx, conn, metadata) + if err != nil { + conn.Close() + if onClose != nil { + onClose(err) + } + return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + } else { + if onClose != nil { + onClose(nil) + } + } + return nil + } + // TODO + err = outbound.NewConnection(ctx, selectedOutbound, conn, metadata) + if err != nil { + conn.Close() + if onClose != nil { + onClose(err) + } + return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + } else { + if onClose != nil { + onClose(nil) + } + } + return nil +} + +func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + err := r.routePacketConnection(ctx, conn, metadata, nil) + if err != nil { + conn.Close() + if E.IsClosedOrCanceled(err) { + r.logger.DebugContext(ctx, "connection closed: ", err) + } else { + r.logger.ErrorContext(ctx, err) + } + } + return nil +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := r.routePacketConnection(ctx, conn, metadata, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + if E.IsClosedOrCanceled(err) { + r.logger.DebugContext(ctx, "connection closed: ", err) + } else { + r.logger.ErrorContext(ctx, err) + } + } else if onClose != nil { + onClose(nil) + } +} + +func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + if r.pauseManager.IsDevicePaused() { + return E.New("reject packet connection to ", metadata.Destination, " while device paused") + } + if metadata.InboundDetour != "" { + if metadata.LastInbound == metadata.InboundDetour { + return E.New("routing loop on detour: ", metadata.InboundDetour) + } + detour := r.inboundByTag[metadata.InboundDetour] + if detour == nil { + return E.New("inbound detour not found: ", metadata.InboundDetour) + } + injectable, isInjectable := detour.(adapter.UDPInjectableInbound) + if !isInjectable { + return E.New("inbound detour is not UDP injectable: ", metadata.InboundDetour) + } + metadata.LastInbound = metadata.Inbound + metadata.Inbound = metadata.InboundDetour + metadata.InboundDetour = "" + injectable.NewPacketConnectionEx(ctx, conn, metadata, onClose) + return nil + } + conntrack.KillerCheck() + + // TODO: move to UoT + metadata.Network = N.NetworkUDP + + // Currently we don't have deadline usages for UDP connections + /*if deadline.NeedAdditionalReadDeadline(conn) { + conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) + }*/ + + selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, nil, conn, -1) + if err != nil { + return err + } + var selectedOutbound adapter.Outbound + var selectReturn bool + if selectedRule != nil { + switch action := selectedRule.Action().(type) { + case *rule.RuleActionRoute: + var loaded bool + selectedOutbound, loaded = r.Outbound(action.Outbound) + if !loaded { + buf.ReleaseMulti(buffers) + return E.New("outbound not found: ", action.Outbound) + } + metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping + case *rule.RuleActionReturn: + selectReturn = true + case *rule.RuleActionReject: + buf.ReleaseMulti(buffers) + N.CloseOnHandshakeFailure(conn, onClose, syscall.ECONNREFUSED) + return nil + } + } + if selectedRule == nil || selectReturn { + if r.defaultOutboundForPacketConnection == nil { + buf.ReleaseMulti(buffers) + return E.New("missing default outbound with UDP support") + } + selectedOutbound = r.defaultOutboundForPacketConnection + } + if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { + buf.ReleaseMulti(buffers) + return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) + } + for _, buffer := range buffers { + // TODO: check if metadata.Destination == packet destination + conn = bufio.NewCachedPacketConn(conn, buffer, metadata.Destination) + } + if r.clashServer != nil { + trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, selectedRule) + defer tracker.Leave() + conn = trackerConn + } + if r.v2rayServer != nil { + if statsService := r.v2rayServer.StatsService(); statsService != nil { + conn = statsService.RoutedPacketConnection(metadata.Inbound, selectedOutbound.Tag(), metadata.User, conn) + } + } + if metadata.FakeIP { + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) + } + legacyOutbound, isLegacy := selectedOutbound.(adapter.PacketConnectionHandler) + if isLegacy { + err = legacyOutbound.NewPacketConnection(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + } + return nil + } + // TODO + err = outbound.NewPacketConnection(ctx, selectedOutbound, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + } + return nil +} + +func (r *Router) matchRule( + ctx context.Context, metadata *adapter.InboundContext, + inputConn net.Conn, inputPacketConn N.PacketConn, ruleIndex int, +) (selectedRule adapter.Rule, selectedRuleIndex int, buffers []*buf.Buffer, fatalErr error) { + if r.processSearcher != nil && metadata.ProcessInfo == nil { + var originDestination netip.AddrPort + if metadata.OriginDestination.IsValid() { + originDestination = metadata.OriginDestination.AddrPort() + } else if metadata.Destination.IsIP() { + originDestination = metadata.Destination.AddrPort() + } + processInfo, fErr := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort(), originDestination) + if fErr != nil { + r.logger.InfoContext(ctx, "failed to search process: ", fErr) + } else { + if processInfo.ProcessPath != "" { + 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.UserId != -1 { + if /*needUserName &&*/ true { + osUser, _ := user.LookupId(F.ToString(processInfo.UserId)) + if osUser != nil { + processInfo.User = osUser.Username + } + } + if processInfo.User != "" { + r.logger.InfoContext(ctx, "found user: ", processInfo.User) + } else { + r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) + } + } + metadata.ProcessInfo = processInfo + } + } + if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { + domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr) + if !loaded { + fatalErr = E.New("missing fakeip record, try to configure experimental.cache_file") + return + } + metadata.OriginDestination = metadata.Destination + metadata.Destination = M.Socksaddr{ + Fqdn: domain, + Port: metadata.Destination.Port, + } + metadata.FakeIP = true + r.logger.DebugContext(ctx, "found fakeip domain: ", domain) + } + if r.dnsReverseMapping != nil && metadata.Domain == "" { + domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) + if loaded { + metadata.Domain = domain + r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) + } + } + if metadata.Destination.IsIPv4() { + metadata.IPVersion = 4 + } else if metadata.Destination.IsIPv6() { + metadata.IPVersion = 6 + } + + //nolint:staticcheck + if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() { + if metadata.InboundOptions.SniffEnabled { + newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ + OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, + Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), + }, inputConn, inputPacketConn) + if newErr != nil { + fatalErr = newErr + return + } + buffers = append(buffers, newBuffers...) + } + if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { + fatalErr = r.actionResolve(ctx, metadata, &rule.RuleActionResolve{ + Strategy: dns.DomainStrategy(metadata.InboundOptions.DomainStrategy), + }) + if fatalErr != nil { + return + } + } + if metadata.InboundOptions.UDPDisableDomainUnmapping { + metadata.UDPDisableDomainUnmapping = true + } + metadata.InboundOptions = option.InboundOptions{} + } + +match: + for ruleIndex < len(r.rules) { + rules := r.rules + if ruleIndex != -1 { + rules = rules[ruleIndex+1:] + } + var ( + currentRule adapter.Rule + currentRuleIndex int + matched bool + ) + for currentRuleIndex, currentRule = range rules { + if currentRule.Match(metadata) { + matched = true + break + } + } + if !matched { + break + } + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + switch action := currentRule.Action().(type) { + case *rule.RuleActionSniff: + newBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) + if newErr != nil { + fatalErr = newErr + return + } + buffers = append(buffers, newBuffers...) + case *rule.RuleActionResolve: + fatalErr = r.actionResolve(ctx, metadata, action) + if fatalErr != nil { + return + } + default: + selectedRule = currentRule + selectedRuleIndex = currentRuleIndex + break match + } + ruleIndex = currentRuleIndex + } + if metadata.Destination.Addr.IsUnspecified() { + newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) + if newErr != nil { + fatalErr = newErr + return + } + buffers = append(buffers, newBuffers...) + } + return +} + +func (r *Router) actionSniff( + ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff, + inputConn net.Conn, inputPacketConn N.PacketConn, +) (buffers []*buf.Buffer, fatalErr error) { + if sniff.Skip(metadata) { + return + } else if inputConn != nil && len(action.StreamSniffers) > 0 { + buffer := buf.NewPacket() + err := sniff.PeekStream( + ctx, + metadata, + inputConn, + buffer, + action.Timeout, + action.StreamSniffers..., + ) + if err == nil { + //goland:noinspection GoDeprecation + if action.OverrideDestination && M.IsDomainName(metadata.Domain) { + metadata.Destination = M.Socksaddr{ + Fqdn: metadata.Domain, + Port: metadata.Destination.Port, + } + } + if metadata.Domain != "" && metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) + } else if metadata.Domain != "" { + r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) + } else { + r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) + } + } + if !buffer.IsEmpty() { + buffers = append(buffers, buffer) + } else { + buffer.Release() + } + } else if inputPacketConn != nil && len(action.PacketSniffers) > 0 { + for { + var ( + buffer = buf.NewPacket() + destination M.Socksaddr + done = make(chan struct{}) + err error + ) + go func() { + sniffTimeout := C.ReadPayloadTimeout + if action.Timeout > 0 { + sniffTimeout = action.Timeout + } + inputPacketConn.SetReadDeadline(time.Now().Add(sniffTimeout)) + destination, err = inputPacketConn.ReadPacket(buffer) + inputPacketConn.SetReadDeadline(time.Time{}) + close(done) + }() + select { + case <-done: + case <-ctx.Done(): + inputPacketConn.Close() + fatalErr = ctx.Err() + return + } + if err != nil { + buffer.Release() + if !errors.Is(err, os.ErrDeadlineExceeded) { + fatalErr = err + return + } + } else { + // TODO: maybe always override destination + if metadata.Destination.Addr.IsUnspecified() { + metadata.Destination = destination + } + if len(buffers) > 0 { + err = sniff.PeekPacket( + ctx, + metadata, + buffer.Bytes(), + sniff.QUICClientHello, + ) + } else { + err = sniff.PeekPacket( + ctx, metadata, + buffer.Bytes(), + action.PacketSniffers..., + ) + } + buffers = append(buffers, buffer) + if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(buffers) == 0 { + r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") + continue + } + if metadata.Protocol != "" { + //goland:noinspection GoDeprecation + if action.OverrideDestination && M.IsDomainName(metadata.Domain) { + metadata.Destination = M.Socksaddr{ + Fqdn: metadata.Domain, + Port: metadata.Destination.Port, + } + } + if metadata.Domain != "" && metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) + } else if metadata.Domain != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) + } else if metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client) + } else { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) + } + } + } + break + } + } + return +} + +func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error { + if metadata.Destination.IsFqdn() { + // TODO: check if WithContext is necessary + addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy) + if err != nil { + return err + } + metadata.DestinationAddresses = addresses + r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") + if metadata.Destination.IsIPv4() { + metadata.IPVersion = 4 + } else if metadata.Destination.IsIPv6() { + metadata.IPVersion = 6 + } + } + return nil +} diff --git a/route/router_dns.go b/route/route_dns.go similarity index 75% rename from route/router_dns.go rename to route/route_dns.go index ead8c28943..43eb61e6df 100644 --- a/route/router_dns.go +++ b/route/route_dns.go @@ -8,6 +8,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/cache" E "github.com/sagernet/sing/common/exceptions" @@ -36,15 +37,16 @@ func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) { return domain, loaded } -func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int, isAddressQuery bool) (context.Context, dns.Transport, dns.DomainStrategy, adapter.DNSRule, int) { +func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, isAddressQuery bool) (dns.Transport, dns.QueryOptions, adapter.DNSRule, int) { metadata := adapter.ContextFrom(ctx) if metadata == nil { panic("no context") } - if index < len(r.dnsRules) { + var options dns.QueryOptions + if ruleIndex < len(r.dnsRules) { dnsRules := r.dnsRules - if index != -1 { - dnsRules = dnsRules[index+1:] + if ruleIndex != -1 { + dnsRules = dnsRules[ruleIndex+1:] } for currentRuleIndex, rule := range dnsRules { if rule.WithAddressLimit() && !isAddressQuery { @@ -52,43 +54,42 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int, isAd } metadata.ResetRuleCache() if rule.Match(metadata) { - detour := rule.Outbound() - transport, loaded := r.transportMap[detour] - if !loaded { - r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour) - continue + displayRuleIndex := currentRuleIndex + if displayRuleIndex != -1 { + displayRuleIndex += displayRuleIndex + 1 } - _, isFakeIP := transport.(adapter.FakeIPTransport) - if isFakeIP && !allowFakeIP { - continue - } - ruleIndex := currentRuleIndex - if index != -1 { - ruleIndex += index + 1 - } - r.dnsLogger.DebugContext(ctx, "match[", ruleIndex, "] ", rule.String(), " => ", detour) - if isFakeIP || rule.DisableCache() { - ctx = dns.ContextWithDisableCache(ctx, true) - } - if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { - ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) - } - if clientSubnet := rule.ClientSubnet(); clientSubnet != nil { - ctx = dns.ContextWithClientSubnet(ctx, *clientSubnet) - } - if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { - return ctx, transport, domainStrategy, rule, ruleIndex + if routeAction, isRoute := rule.Action().(*R.RuleActionDNSRoute); isRoute { + transport, loaded := r.transportMap[routeAction.Server] + if !loaded { + r.dnsLogger.ErrorContext(ctx, "transport not found: ", routeAction.Server) + continue + } + _, isFakeIP := transport.(adapter.FakeIPTransport) + if isFakeIP && !allowFakeIP { + continue + } + options.DisableCache = isFakeIP || routeAction.DisableCache + options.RewriteTTL = routeAction.RewriteTTL + options.ClientSubnet = routeAction.ClientSubnet + if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { + options.Strategy = domainStrategy + } else { + options.Strategy = r.defaultDomainStrategy + } + r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", rule.Action()) + return transport, options, rule, currentRuleIndex } else { - return ctx, transport, r.defaultDomainStrategy, rule, ruleIndex + return nil, options, rule, currentRuleIndex } } } } if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded { - return ctx, r.defaultTransport, domainStrategy, nil, -1 + options.Strategy = domainStrategy } else { - return ctx, r.defaultTransport, r.defaultDomainStrategy, nil, -1 + options.Strategy = r.defaultDomainStrategy } + return r.defaultTransport, options, nil, -1 } func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { @@ -117,21 +118,18 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er metadata.Domain = fqdnToDomain(message.Question[0].Name) } var ( - strategy dns.DomainStrategy + options dns.QueryOptions rule adapter.DNSRule ruleIndex int ) ruleIndex = -1 for { - var ( - dnsCtx context.Context - addressLimit bool - ) - dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) - dnsCtx = adapter.OverrideContext(dnsCtx) + dnsCtx := adapter.OverrideContext(ctx) + var addressLimit bool + transport, options, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) if rule != nil && rule.WithAddressLimit() { addressLimit = true - response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool { + response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(response *mDNS.Msg) bool { addresses, addrErr := dns.MessageToAddresses(response) if addrErr != nil { return false @@ -141,7 +139,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er }) } else { addressLimit = false - response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy) + response, err = r.dnsClient.Exchange(dnsCtx, transport, message, options) } var rejected bool if err != nil { @@ -199,31 +197,28 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS metadata.Destination = M.Socksaddr{} metadata.Domain = domain var ( - transport dns.Transport - transportStrategy dns.DomainStrategy - rule adapter.DNSRule - ruleIndex int + transport dns.Transport + options dns.QueryOptions + rule adapter.DNSRule + ruleIndex int ) ruleIndex = -1 for { - var ( - dnsCtx context.Context - addressLimit bool - ) - dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) - dnsCtx = adapter.OverrideContext(dnsCtx) - if strategy == dns.DomainStrategyAsIS { - strategy = transportStrategy + dnsCtx := adapter.OverrideContext(ctx) + var addressLimit bool + transport, options, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) + if strategy != dns.DomainStrategyAsIS { + options.Strategy = strategy } if rule != nil && rule.WithAddressLimit() { addressLimit = true - responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool { + responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, options, func(responseAddrs []netip.Addr) bool { metadata.DestinationAddresses = responseAddrs return rule.MatchAddressLimit(metadata) }) } else { addressLimit = false - responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy) + responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, options) } if err != nil { if errors.Is(err, dns.ErrResponseRejectedCached) { diff --git a/route/router.go b/route/router.go index 110809f0d2..05349485d2 100644 --- a/route/router.go +++ b/route/router.go @@ -3,11 +3,9 @@ package route import ( "context" "errors" - "net" "net/netip" "net/url" "os" - "os/user" "runtime" "strings" "syscall" @@ -19,22 +17,16 @@ import ( "github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geosite" "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/common/sniff" "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" - "github.com/sagernet/sing-box/outbound" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/transport/fakeip" "github.com/sagernet/sing-dns" - "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" - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/bufio/deadline" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -42,7 +34,6 @@ import ( N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/task" - "github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/common/winpowrprof" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" @@ -154,14 +145,14 @@ func NewRouter( Logger: router.dnsLogger, }) for i, ruleOptions := range options.Rules { - routeRule, err := NewRule(ctx, router, router.logger, ruleOptions, true) + routeRule, err := R.NewRule(ctx, router, router.logger, ruleOptions, true) if err != nil { return nil, E.Cause(err, "parse rule[", i, "]") } router.rules = append(router.rules, routeRule) } for i, dnsRuleOptions := range dnsOptions.Rules { - dnsRule, err := NewDNSRule(ctx, router, router.logger, dnsRuleOptions, true) + dnsRule, err := R.NewDNSRule(ctx, router, router.logger, dnsRuleOptions, true) if err != nil { return nil, E.Cause(err, "parse dns rule[", i, "]") } @@ -171,7 +162,7 @@ func NewRouter( if _, exists := router.ruleSetMap[ruleSetOptions.Tag]; exists { return nil, E.New("duplicate rule-set tag: ", ruleSetOptions.Tag) } - ruleSet, err := NewRuleSet(ctx, router, router.logger, ruleSetOptions) + ruleSet, err := R.NewRuleSet(ctx, router, router.logger, ruleSetOptions) if err != nil { return nil, E.Cause(err, "parse rule-set[", i, "]") } @@ -437,8 +428,12 @@ func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outb r.defaultOutboundForPacketConnection = defaultOutboundForPacketConnection r.outboundByTag = outboundByTag for i, rule := range r.rules { - if _, loaded := outboundByTag[rule.Outbound()]; !loaded { - return E.New("outbound not found for rule[", i, "]: ", rule.Outbound()) + routeAction, isRoute := rule.Action().(*R.RuleActionRoute) + if !isRoute { + continue + } + if _, loaded := outboundByTag[routeAction.Outbound]; !loaded { + return E.New("outbound not found for rule[", i, "]: ", routeAction.Outbound) } } return nil @@ -804,375 +799,6 @@ func (r *Router) NeedWIFIState() bool { return r.needWIFIState } -func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if r.pauseManager.IsDevicePaused() { - return E.New("reject connection to ", metadata.Destination, " while device paused") - } - - if metadata.InboundDetour != "" { - if metadata.LastInbound == metadata.InboundDetour { - return E.New("routing loop on detour: ", metadata.InboundDetour) - } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { - return E.New("inbound detour not found: ", metadata.InboundDetour) - } - injectable, isInjectable := detour.(adapter.InjectableInbound) - if !isInjectable { - return E.New("inbound detour is not injectable: ", metadata.InboundDetour) - } - if !common.Contains(injectable.Network(), N.NetworkTCP) { - return E.New("inject: TCP unsupported") - } - metadata.LastInbound = metadata.Inbound - metadata.Inbound = metadata.InboundDetour - metadata.InboundDetour = "" - err := injectable.NewConnection(ctx, conn, metadata) - if err != nil { - return E.Cause(err, "inject ", detour.Tag()) - } - return nil - } - conntrack.KillerCheck() - metadata.Network = N.NetworkTCP - switch metadata.Destination.Fqdn { - case mux.Destination.Fqdn: - return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in inbound options instead.") - case vmess.MuxDestination.Fqdn: - return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.") - case uot.MagicAddress: - return E.New("global UoT not supported since sing-box v1.7.0.") - case uot.LegacyMagicAddress: - return E.New("global UoT (legacy) not supported since sing-box v1.7.0.") - } - - if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { - domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr) - if !loaded { - return E.New("missing fakeip context") - } - metadata.OriginDestination = metadata.Destination - metadata.Destination = M.Socksaddr{ - Fqdn: domain, - Port: metadata.Destination.Port, - } - metadata.FakeIP = true - r.logger.DebugContext(ctx, "found fakeip domain: ", domain) - } - - if deadline.NeedAdditionalReadDeadline(conn) { - conn = deadline.NewConn(conn) - } - - if metadata.InboundOptions.SniffEnabled && !sniff.Skip(metadata) { - buffer := buf.NewPacket() - err := sniff.PeekStream( - ctx, - &metadata, - conn, - buffer, - time.Duration(metadata.InboundOptions.SniffTimeout), - sniff.TLSClientHello, - sniff.HTTPHost, - sniff.StreamDomainNameQuery, - sniff.SSH, - sniff.BitTorrent, - ) - if err == nil { - if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { - metadata.Destination = M.Socksaddr{ - Fqdn: metadata.Domain, - Port: metadata.Destination.Port, - } - } - if metadata.Domain != "" { - r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) - } else { - r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) - } - } - if !buffer.IsEmpty() { - conn = bufio.NewCachedConn(conn, buffer) - } else { - buffer.Release() - } - } - - if r.dnsReverseMapping != nil && metadata.Domain == "" { - domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) - if loaded { - metadata.Domain = domain - r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) - } - } - - if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { - addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)) - if err != nil { - return err - } - metadata.DestinationAddresses = addresses - r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") - } - if metadata.Destination.IsIPv4() { - metadata.IPVersion = 4 - } else if metadata.Destination.IsIPv6() { - metadata.IPVersion = 6 - } - ctx, matchedRule, detour, err := r.match(ctx, &metadata, r.defaultOutboundForConnection) - if err != nil { - return err - } - if !common.Contains(detour.Network(), N.NetworkTCP) { - return E.New("missing supported outbound, closing connection") - } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, matchedRule) - defer tracker.Leave() - conn = trackerConn - } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedConnection(metadata.Inbound, detour.Tag(), metadata.User, conn) - } - } - return detour.NewConnection(ctx, conn, metadata) -} - -func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - if r.pauseManager.IsDevicePaused() { - return E.New("reject packet connection to ", metadata.Destination, " while device paused") - } - if metadata.InboundDetour != "" { - if metadata.LastInbound == metadata.InboundDetour { - return E.New("routing loop on detour: ", metadata.InboundDetour) - } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { - return E.New("inbound detour not found: ", metadata.InboundDetour) - } - injectable, isInjectable := detour.(adapter.InjectableInbound) - if !isInjectable { - return E.New("inbound detour is not injectable: ", metadata.InboundDetour) - } - if !common.Contains(injectable.Network(), N.NetworkUDP) { - return E.New("inject: UDP unsupported") - } - metadata.LastInbound = metadata.Inbound - metadata.Inbound = metadata.InboundDetour - metadata.InboundDetour = "" - err := injectable.NewPacketConnection(ctx, conn, metadata) - if err != nil { - return E.Cause(err, "inject ", detour.Tag()) - } - return nil - } - conntrack.KillerCheck() - metadata.Network = N.NetworkUDP - - if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { - domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr) - if !loaded { - return E.New("missing fakeip context") - } - metadata.OriginDestination = metadata.Destination - metadata.Destination = M.Socksaddr{ - Fqdn: domain, - Port: metadata.Destination.Port, - } - metadata.FakeIP = true - r.logger.DebugContext(ctx, "found fakeip domain: ", domain) - } - - // Currently we don't have deadline usages for UDP connections - /*if deadline.NeedAdditionalReadDeadline(conn) { - conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) - }*/ - - if metadata.InboundOptions.SniffEnabled || metadata.Destination.Addr.IsUnspecified() { - var bufferList []*buf.Buffer - for { - var ( - buffer = buf.NewPacket() - destination M.Socksaddr - done = make(chan struct{}) - err error - ) - go func() { - sniffTimeout := C.ReadPayloadTimeout - if metadata.InboundOptions.SniffTimeout > 0 { - sniffTimeout = time.Duration(metadata.InboundOptions.SniffTimeout) - } - conn.SetReadDeadline(time.Now().Add(sniffTimeout)) - destination, err = conn.ReadPacket(buffer) - conn.SetReadDeadline(time.Time{}) - close(done) - }() - select { - case <-done: - case <-ctx.Done(): - conn.Close() - return ctx.Err() - } - if err != nil { - buffer.Release() - if !errors.Is(err, os.ErrDeadlineExceeded) { - return err - } - } else { - if metadata.Destination.Addr.IsUnspecified() { - metadata.Destination = destination - } - if metadata.InboundOptions.SniffEnabled { - if len(bufferList) > 0 { - err = sniff.PeekPacket( - ctx, - &metadata, - buffer.Bytes(), - sniff.QUICClientHello, - ) - } else { - err = sniff.PeekPacket( - ctx, &metadata, - buffer.Bytes(), - sniff.DomainNameQuery, - sniff.QUICClientHello, - sniff.STUNMessage, - sniff.UTP, - sniff.UDPTracker, - sniff.DTLSRecord) - } - if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(bufferList) == 0 { - bufferList = append(bufferList, buffer) - r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") - continue - } - if metadata.Protocol != "" { - if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { - metadata.Destination = M.Socksaddr{ - Fqdn: metadata.Domain, - Port: metadata.Destination.Port, - } - } - if metadata.Domain != "" && metadata.Client != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) - } else if metadata.Domain != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) - } else if metadata.Client != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client) - } else { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) - } - } - } - conn = bufio.NewCachedPacketConn(conn, buffer, destination) - } - for _, cachedBuffer := range common.Reverse(bufferList) { - conn = bufio.NewCachedPacketConn(conn, cachedBuffer, destination) - } - break - } - } - if r.dnsReverseMapping != nil && metadata.Domain == "" { - domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) - if loaded { - metadata.Domain = domain - r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) - } - } - if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { - addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)) - if err != nil { - return err - } - metadata.DestinationAddresses = addresses - r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") - } - if metadata.Destination.IsIPv4() { - metadata.IPVersion = 4 - } else if metadata.Destination.IsIPv6() { - metadata.IPVersion = 6 - } - ctx, matchedRule, detour, err := r.match(ctx, &metadata, r.defaultOutboundForPacketConnection) - if err != nil { - return err - } - if !common.Contains(detour.Network(), N.NetworkUDP) { - return E.New("missing supported outbound, closing packet connection") - } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, matchedRule) - defer tracker.Leave() - conn = trackerConn - } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn) - } - } - if metadata.FakeIP { - conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) - } - return detour.NewPacketConnection(ctx, conn, metadata) -} - -func (r *Router) match(ctx context.Context, metadata *adapter.InboundContext, defaultOutbound adapter.Outbound) (context.Context, adapter.Rule, adapter.Outbound, error) { - matchRule, matchOutbound := r.match0(ctx, metadata, defaultOutbound) - if contextOutbound, loaded := outbound.TagFromContext(ctx); loaded { - if contextOutbound == matchOutbound.Tag() { - return nil, nil, nil, E.New("connection loopback in outbound/", matchOutbound.Type(), "[", matchOutbound.Tag(), "]") - } - } - ctx = outbound.ContextWithTag(ctx, matchOutbound.Tag()) - return ctx, matchRule, matchOutbound, nil -} - -func (r *Router) match0(ctx context.Context, metadata *adapter.InboundContext, defaultOutbound adapter.Outbound) (adapter.Rule, adapter.Outbound) { - if r.processSearcher != nil { - var originDestination netip.AddrPort - if metadata.OriginDestination.IsValid() { - originDestination = metadata.OriginDestination.AddrPort() - } else if metadata.Destination.IsIP() { - originDestination = metadata.Destination.AddrPort() - } - processInfo, err := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort(), originDestination) - if err != nil { - r.logger.InfoContext(ctx, "failed to search process: ", err) - } else { - if processInfo.ProcessPath != "" { - 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.UserId != -1 { - if /*needUserName &&*/ true { - osUser, _ := user.LookupId(F.ToString(processInfo.UserId)) - if osUser != nil { - processInfo.User = osUser.Username - } - } - if processInfo.User != "" { - r.logger.InfoContext(ctx, "found user: ", processInfo.User) - } else { - r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) - } - } - metadata.ProcessInfo = processInfo - } - } - for i, rule := range r.rules { - metadata.ResetRuleCache() - if rule.Match(metadata) { - detour := rule.Outbound() - r.logger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour) - if outbound, loaded := r.Outbound(detour); loaded { - return rule, outbound - } - r.logger.ErrorContext(ctx, "outbound not found: ", detour) - } - } - return nil, defaultOutbound -} - func (r *Router) InterfaceFinder() control.InterfaceFinder { return r.interfaceFinder } diff --git a/route/rule_abstract.go b/route/rule/rule_abstract.go similarity index 94% rename from route/rule_abstract.go rename to route/rule/rule_abstract.go index 9ef2e93277..6a56934188 100644 --- a/route/rule_abstract.go +++ b/route/rule/rule_abstract.go @@ -1,4 +1,4 @@ -package route +package rule import ( "io" @@ -20,7 +20,7 @@ type abstractDefaultRule struct { allItems []RuleItem ruleSetItem RuleItem invert bool - outbound string + action adapter.RuleAction } func (r *abstractDefaultRule) Type() string { @@ -150,8 +150,8 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { return !r.invert } -func (r *abstractDefaultRule) Outbound() string { - return r.outbound +func (r *abstractDefaultRule) Action() adapter.RuleAction { + return r.action } func (r *abstractDefaultRule) String() string { @@ -163,10 +163,10 @@ func (r *abstractDefaultRule) String() string { } type abstractLogicalRule struct { - rules []adapter.HeadlessRule - mode string - invert bool - outbound string + rules []adapter.HeadlessRule + mode string + invert bool + action adapter.RuleAction } func (r *abstractLogicalRule) Type() string { @@ -231,8 +231,8 @@ func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool { } } -func (r *abstractLogicalRule) Outbound() string { - return r.outbound +func (r *abstractLogicalRule) Action() adapter.RuleAction { + return r.action } func (r *abstractLogicalRule) String() string { diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go new file mode 100644 index 0000000000..e85fc76381 --- /dev/null +++ b/route/rule/rule_action.go @@ -0,0 +1,228 @@ +package rule + +import ( + "net/netip" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-dns" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { + switch action.Action { + case C.RuleActionTypeRoute: + return &RuleActionRoute{ + Outbound: action.RouteOptions.Outbound, + UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, + }, nil + case C.RuleActionTypeReturn: + return &RuleActionReject{}, nil + case C.RuleActionTypeReject: + return &RuleActionReject{ + Method: string(action.RejectOptions.Method), + }, nil + case C.RuleActionTypeHijackDNS: + return &RuleActionHijackDNS{}, nil + case C.RuleActionTypeSniff: + sniffAction := &RuleActionSniff{ + snifferNames: action.SniffOptions.Sniffer, + Timeout: time.Duration(action.SniffOptions.Timeout), + } + return sniffAction, sniffAction.build() + case C.RuleActionTypeResolve: + return &RuleActionResolve{ + Strategy: dns.DomainStrategy(action.ResolveOptions.Strategy), + Server: action.ResolveOptions.Server, + }, nil + default: + panic(F.ToString("unknown rule action: ", action.Action)) + } +} + +func NewDNSRuleAction(action option.DNSRuleAction) adapter.RuleAction { + switch action.Action { + case C.RuleActionTypeRoute: + return &RuleActionDNSRoute{ + Server: action.RouteOptions.Server, + DisableCache: action.RouteOptions.DisableCache, + RewriteTTL: action.RouteOptions.RewriteTTL, + ClientSubnet: action.RouteOptions.ClientSubnet.Build(), + } + case C.RuleActionTypeReturn: + return &RuleActionReturn{} + case C.RuleActionTypeReject: + return &RuleActionReject{ + Method: string(action.RejectOptions.Method), + } + default: + panic(F.ToString("unknown rule action: ", action.Action)) + } +} + +type RuleActionRoute struct { + Outbound string + UDPDisableDomainUnmapping bool +} + +func (r *RuleActionRoute) Type() string { + return C.RuleActionTypeRoute +} + +func (r *RuleActionRoute) String() string { + return F.ToString("route(", r.Outbound, ")") +} + +type RuleActionDNSRoute struct { + Server string + DisableCache bool + RewriteTTL *uint32 + ClientSubnet netip.Prefix +} + +func (r *RuleActionDNSRoute) Type() string { + return C.RuleActionTypeRoute +} + +func (r *RuleActionDNSRoute) String() string { + return F.ToString("route(", r.Server, ")") +} + +type RuleActionReturn struct{} + +func (r *RuleActionReturn) Type() string { + return C.RuleActionTypeReturn +} + +func (r *RuleActionReturn) String() string { + return "return" +} + +type RuleActionReject struct { + Method string +} + +func (r *RuleActionReject) Type() string { + return C.RuleActionTypeReject +} + +func (r *RuleActionReject) String() string { + if r.Method == C.RuleActionRejectMethodDefault { + return "reject" + } + return F.ToString("reject(", r.Method, ")") +} + +type RuleActionHijackDNS struct{} + +func (r *RuleActionHijackDNS) Type() string { + return C.RuleActionTypeHijackDNS +} + +func (r *RuleActionHijackDNS) String() string { + return "hijack-dns" +} + +type RuleActionSniff struct { + snifferNames []string + StreamSniffers []sniff.StreamSniffer + PacketSniffers []sniff.PacketSniffer + Timeout time.Duration + // Deprecated + OverrideDestination bool +} + +func (r *RuleActionSniff) Type() string { + return C.RuleActionTypeSniff +} + +func (r *RuleActionSniff) build() error { + if len(r.StreamSniffers) > 0 || len(r.PacketSniffers) > 0 { + return nil + } + if len(r.snifferNames) > 0 { + for _, name := range r.snifferNames { + switch name { + case C.ProtocolTLS: + r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello) + case C.ProtocolHTTP: + r.StreamSniffers = append(r.StreamSniffers, sniff.HTTPHost) + case C.ProtocolQUIC: + r.PacketSniffers = append(r.PacketSniffers, sniff.QUICClientHello) + case C.ProtocolDNS: + r.StreamSniffers = append(r.StreamSniffers, sniff.StreamDomainNameQuery) + r.PacketSniffers = append(r.PacketSniffers, sniff.DomainNameQuery) + case C.ProtocolSTUN: + r.PacketSniffers = append(r.PacketSniffers, sniff.STUNMessage) + case C.ProtocolBitTorrent: + r.StreamSniffers = append(r.StreamSniffers, sniff.BitTorrent) + r.PacketSniffers = append(r.PacketSniffers, sniff.UTP) + r.PacketSniffers = append(r.PacketSniffers, sniff.UDPTracker) + case C.ProtocolDTLS: + r.PacketSniffers = append(r.PacketSniffers, sniff.DTLSRecord) + case C.ProtocolSSH: + r.StreamSniffers = append(r.StreamSniffers, sniff.SSH) + case C.ProtocolRDP: + r.StreamSniffers = append(r.StreamSniffers, sniff.RDP) + default: + return E.New("unknown sniffer: ", name) + } + } + } else { + r.StreamSniffers = []sniff.StreamSniffer{ + sniff.TLSClientHello, + sniff.HTTPHost, + sniff.StreamDomainNameQuery, + sniff.BitTorrent, + sniff.SSH, + sniff.RDP, + } + r.PacketSniffers = []sniff.PacketSniffer{ + sniff.DomainNameQuery, + sniff.QUICClientHello, + sniff.STUNMessage, + sniff.UTP, + sniff.UDPTracker, + sniff.DTLSRecord, + } + } + return nil +} + +func (r *RuleActionSniff) String() string { + if len(r.snifferNames) == 0 && r.Timeout == 0 { + return "sniff" + } else if len(r.snifferNames) > 0 && r.Timeout == 0 { + return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ")") + } else if len(r.snifferNames) == 0 && r.Timeout > 0 { + return F.ToString("sniff(", r.Timeout.String(), ")") + } else { + return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ",", r.Timeout.String(), ")") + } +} + +type RuleActionResolve struct { + Strategy dns.DomainStrategy + Server string +} + +func (r *RuleActionResolve) Type() string { + return C.RuleActionTypeResolve +} + +func (r *RuleActionResolve) String() string { + if r.Strategy == dns.DomainStrategyAsIS && r.Server == "" { + return F.ToString("resolve") + } else if r.Strategy != dns.DomainStrategyAsIS && r.Server == "" { + return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ")") + } else if r.Strategy == dns.DomainStrategyAsIS && r.Server != "" { + return F.ToString("resolve(", r.Server, ")") + } else { + return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ",", r.Server, ")") + } +} diff --git a/route/rule_default.go b/route/rule/rule_default.go similarity index 88% rename from route/rule_default.go rename to route/rule/rule_default.go index fb4f6d82be..4f5d1e8a3c 100644 --- a/route/rule_default.go +++ b/route/rule/rule_default.go @@ -1,4 +1,4 @@ -package route +package rule import ( "context" @@ -17,16 +17,22 @@ func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogge if !options.DefaultOptions.IsValid() { return nil, E.New("missing conditions") } - if options.DefaultOptions.Outbound == "" && checkOutbound { - return nil, E.New("missing outbound field") + switch options.DefaultOptions.Action { + case "", C.RuleActionTypeRoute: + if options.DefaultOptions.RouteOptions.Outbound == "" && checkOutbound { + return nil, E.New("missing outbound field") + } } return NewDefaultRule(ctx, router, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") } - if options.LogicalOptions.Outbound == "" && checkOutbound { - return nil, E.New("missing outbound field") + switch options.LogicalOptions.Action { + case "", C.RuleActionTypeRoute: + if options.LogicalOptions.RouteOptions.Outbound == "" && checkOutbound { + return nil, E.New("missing outbound field") + } } return NewLogicalRule(ctx, router, logger, options.LogicalOptions) default: @@ -46,10 +52,14 @@ type RuleItem interface { } func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { + action, err := NewRuleAction(options.RuleAction) + if err != nil { + return nil, E.Cause(err, "action") + } rule := &DefaultRule{ abstractDefaultRule{ - invert: options.Invert, - outbound: options.Outbound, + invert: options.Invert, + action: action, }, } if len(options.Inbound) > 0 { @@ -244,27 +254,31 @@ type LogicalRule struct { } func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { - r := &LogicalRule{ + action, err := NewRuleAction(options.RuleAction) + if err != nil { + return nil, E.Cause(err, "action") + } + rule := &LogicalRule{ abstractLogicalRule{ - rules: make([]adapter.HeadlessRule, len(options.Rules)), - invert: options.Invert, - outbound: options.Outbound, + rules: make([]adapter.HeadlessRule, len(options.Rules)), + invert: options.Invert, + action: action, }, } switch options.Mode { case C.LogicalTypeAnd: - r.mode = C.LogicalTypeAnd + rule.mode = C.LogicalTypeAnd case C.LogicalTypeOr: - r.mode = C.LogicalTypeOr + rule.mode = C.LogicalTypeOr default: return nil, E.New("unknown logical mode: ", options.Mode) } - for i, subRule := range options.Rules { - rule, err := NewRule(ctx, router, logger, subRule, false) + for i, subOptions := range options.Rules { + subRule, err := NewRule(ctx, router, logger, subOptions, false) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } - r.rules[i] = rule + rule.rules[i] = subRule } - return r, nil + return rule, nil } diff --git a/route/rule_dns.go b/route/rule/rule_dns.go similarity index 89% rename from route/rule_dns.go rename to route/rule/rule_dns.go index 4740488e3d..6e57633d10 100644 --- a/route/rule_dns.go +++ b/route/rule/rule_dns.go @@ -1,8 +1,7 @@ -package route +package rule import ( "context" - "net/netip" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" @@ -19,16 +18,22 @@ func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLo if !options.DefaultOptions.IsValid() { return nil, E.New("missing conditions") } - if options.DefaultOptions.Server == "" && checkServer { - return nil, E.New("missing server field") + switch options.DefaultOptions.Action { + case "", C.RuleActionTypeRoute: + if options.DefaultOptions.RouteOptions.Server == "" && checkServer { + return nil, E.New("missing server field") + } } return NewDefaultDNSRule(ctx, router, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") } - if options.LogicalOptions.Server == "" && checkServer { - return nil, E.New("missing server field") + switch options.LogicalOptions.Action { + case "", C.RuleActionTypeRoute: + if options.LogicalOptions.RouteOptions.Server == "" && checkServer { + return nil, E.New("missing server field") + } } return NewLogicalDNSRule(ctx, router, logger, options.LogicalOptions) default: @@ -40,20 +45,14 @@ var _ adapter.DNSRule = (*DefaultDNSRule)(nil) type DefaultDNSRule struct { abstractDefaultRule - disableCache bool - rewriteTTL *uint32 - clientSubnet *netip.Prefix } func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { rule := &DefaultDNSRule{ abstractDefaultRule: abstractDefaultRule{ - invert: options.Invert, - outbound: options.Server, + invert: options.Invert, + action: NewDNSRuleAction(options.DNSRuleAction), }, - disableCache: options.DisableCache, - rewriteTTL: options.RewriteTTL, - clientSubnet: (*netip.Prefix)(options.ClientSubnet), } if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) @@ -245,16 +244,8 @@ func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.Co return rule, nil } -func (r *DefaultDNSRule) DisableCache() bool { - return r.disableCache -} - -func (r *DefaultDNSRule) RewriteTTL() *uint32 { - return r.rewriteTTL -} - -func (r *DefaultDNSRule) ClientSubnet() *netip.Prefix { - return r.clientSubnet +func (r *DefaultDNSRule) Action() adapter.RuleAction { + return r.action } func (r *DefaultDNSRule) WithAddressLimit() bool { @@ -289,21 +280,15 @@ var _ adapter.DNSRule = (*LogicalDNSRule)(nil) type LogicalDNSRule struct { abstractLogicalRule - disableCache bool - rewriteTTL *uint32 - clientSubnet *netip.Prefix } func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { r := &LogicalDNSRule{ abstractLogicalRule: abstractLogicalRule{ - rules: make([]adapter.HeadlessRule, len(options.Rules)), - invert: options.Invert, - outbound: options.Server, + rules: make([]adapter.HeadlessRule, len(options.Rules)), + invert: options.Invert, + action: NewDNSRuleAction(options.DNSRuleAction), }, - disableCache: options.DisableCache, - rewriteTTL: options.RewriteTTL, - clientSubnet: (*netip.Prefix)(options.ClientSubnet), } switch options.Mode { case C.LogicalTypeAnd: @@ -323,16 +308,8 @@ func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.Co return r, nil } -func (r *LogicalDNSRule) DisableCache() bool { - return r.disableCache -} - -func (r *LogicalDNSRule) RewriteTTL() *uint32 { - return r.rewriteTTL -} - -func (r *LogicalDNSRule) ClientSubnet() *netip.Prefix { - return r.clientSubnet +func (r *LogicalDNSRule) Action() adapter.RuleAction { + return r.action } func (r *LogicalDNSRule) WithAddressLimit() bool { diff --git a/route/rule_headless.go b/route/rule/rule_headless.go similarity index 99% rename from route/rule_headless.go rename to route/rule/rule_headless.go index 23a98c7237..9ea357af8c 100644 --- a/route/rule_headless.go +++ b/route/rule/rule_headless.go @@ -1,4 +1,4 @@ -package route +package rule import ( "github.com/sagernet/sing-box/adapter" diff --git a/route/rule_item_adguard.go b/route/rule/rule_item_adguard.go similarity index 98% rename from route/rule_item_adguard.go rename to route/rule/rule_item_adguard.go index bdbb3b75fd..84252e606d 100644 --- a/route/rule_item_adguard.go +++ b/route/rule/rule_item_adguard.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_auth_user.go b/route/rule/rule_item_auth_user.go similarity index 98% rename from route/rule_item_auth_user.go rename to route/rule/rule_item_auth_user.go index fbe053e6be..5799e3c718 100644 --- a/route/rule_item_auth_user.go +++ b/route/rule/rule_item_auth_user.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_cidr.go b/route/rule/rule_item_cidr.go similarity index 99% rename from route/rule_item_cidr.go rename to route/rule/rule_item_cidr.go index be0bb1369c..c823dcf30a 100644 --- a/route/rule_item_cidr.go +++ b/route/rule/rule_item_cidr.go @@ -1,4 +1,4 @@ -package route +package rule import ( "net/netip" diff --git a/route/rule_item_clash_mode.go b/route/rule/rule_item_clash_mode.go similarity index 97% rename from route/rule_item_clash_mode.go rename to route/rule/rule_item_clash_mode.go index 70141f1116..aa5126cbde 100644 --- a/route/rule_item_clash_mode.go +++ b/route/rule/rule_item_clash_mode.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_client.go b/route/rule/rule_item_client.go similarity index 98% rename from route/rule_item_client.go rename to route/rule/rule_item_client.go index eeab440240..63ff410355 100644 --- a/route/rule_item_client.go +++ b/route/rule/rule_item_client.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_domain.go b/route/rule/rule_item_domain.go similarity index 99% rename from route/rule_item_domain.go rename to route/rule/rule_item_domain.go index c77890df24..b7655a7942 100644 --- a/route/rule_item_domain.go +++ b/route/rule/rule_item_domain.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_domain_keyword.go b/route/rule/rule_item_domain_keyword.go similarity index 98% rename from route/rule_item_domain_keyword.go rename to route/rule/rule_item_domain_keyword.go index c6ca1e8c28..6e19a10ccd 100644 --- a/route/rule_item_domain_keyword.go +++ b/route/rule/rule_item_domain_keyword.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_domain_regex.go b/route/rule/rule_item_domain_regex.go similarity index 99% rename from route/rule_item_domain_regex.go rename to route/rule/rule_item_domain_regex.go index b3555168a2..b9752a45ad 100644 --- a/route/rule_item_domain_regex.go +++ b/route/rule/rule_item_domain_regex.go @@ -1,4 +1,4 @@ -package route +package rule import ( "regexp" diff --git a/route/rule_item_geoip.go b/route/rule/rule_item_geoip.go similarity index 99% rename from route/rule_item_geoip.go rename to route/rule/rule_item_geoip.go index 3611613a48..3c967fec5f 100644 --- a/route/rule_item_geoip.go +++ b/route/rule/rule_item_geoip.go @@ -1,4 +1,4 @@ -package route +package rule import ( "net/netip" diff --git a/route/rule_item_geosite.go b/route/rule/rule_item_geosite.go similarity index 98% rename from route/rule_item_geosite.go rename to route/rule/rule_item_geosite.go index 5fdbfe5962..9e5e03c819 100644 --- a/route/rule_item_geosite.go +++ b/route/rule/rule_item_geosite.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_inbound.go b/route/rule/rule_item_inbound.go similarity index 98% rename from route/rule_item_inbound.go rename to route/rule/rule_item_inbound.go index 7e28781f00..87e84740c9 100644 --- a/route/rule_item_inbound.go +++ b/route/rule/rule_item_inbound.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_ip_is_private.go b/route/rule/rule_item_ip_is_private.go similarity index 98% rename from route/rule_item_ip_is_private.go rename to route/rule/rule_item_ip_is_private.go index 6592a9d3fe..e185db1db4 100644 --- a/route/rule_item_ip_is_private.go +++ b/route/rule/rule_item_ip_is_private.go @@ -1,4 +1,4 @@ -package route +package rule import ( "net/netip" diff --git a/route/rule_item_ipversion.go b/route/rule/rule_item_ipversion.go similarity index 97% rename from route/rule_item_ipversion.go rename to route/rule/rule_item_ipversion.go index 3d8762b417..8ab6494270 100644 --- a/route/rule_item_ipversion.go +++ b/route/rule/rule_item_ipversion.go @@ -1,4 +1,4 @@ -package route +package rule import ( "github.com/sagernet/sing-box/adapter" diff --git a/route/rule_item_network.go b/route/rule/rule_item_network.go similarity index 98% rename from route/rule_item_network.go rename to route/rule/rule_item_network.go index fc54f425da..bfb334d310 100644 --- a/route/rule_item_network.go +++ b/route/rule/rule_item_network.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_outbound.go b/route/rule/rule_item_outbound.go similarity index 98% rename from route/rule_item_outbound.go rename to route/rule/rule_item_outbound.go index 4b3e16fc6b..3f37dee7fb 100644 --- a/route/rule_item_outbound.go +++ b/route/rule/rule_item_outbound.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_package_name.go b/route/rule/rule_item_package_name.go similarity index 98% rename from route/rule_item_package_name.go rename to route/rule/rule_item_package_name.go index d1ca09eb2d..0066735c30 100644 --- a/route/rule_item_package_name.go +++ b/route/rule/rule_item_package_name.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_port.go b/route/rule/rule_item_port.go similarity index 98% rename from route/rule_item_port.go rename to route/rule/rule_item_port.go index 6247893364..af166ee640 100644 --- a/route/rule_item_port.go +++ b/route/rule/rule_item_port.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_port_range.go b/route/rule/rule_item_port_range.go similarity index 99% rename from route/rule_item_port_range.go rename to route/rule/rule_item_port_range.go index f87575f2ce..980f7d2391 100644 --- a/route/rule_item_port_range.go +++ b/route/rule/rule_item_port_range.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strconv" diff --git a/route/rule_item_process_name.go b/route/rule/rule_item_process_name.go similarity index 98% rename from route/rule_item_process_name.go rename to route/rule/rule_item_process_name.go index ce051666d0..fa0f71651d 100644 --- a/route/rule_item_process_name.go +++ b/route/rule/rule_item_process_name.go @@ -1,4 +1,4 @@ -package route +package rule import ( "path/filepath" diff --git a/route/rule_item_process_path.go b/route/rule/rule_item_process_path.go similarity index 98% rename from route/rule_item_process_path.go rename to route/rule/rule_item_process_path.go index feae4b2756..75dee476d6 100644 --- a/route/rule_item_process_path.go +++ b/route/rule/rule_item_process_path.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_process_path_regex.go b/route/rule/rule_item_process_path_regex.go similarity index 98% rename from route/rule_item_process_path_regex.go rename to route/rule/rule_item_process_path_regex.go index 01b2723cc6..76cf67b9f5 100644 --- a/route/rule_item_process_path_regex.go +++ b/route/rule/rule_item_process_path_regex.go @@ -1,4 +1,4 @@ -package route +package rule import ( "regexp" diff --git a/route/rule_item_protocol.go b/route/rule/rule_item_protocol.go similarity index 98% rename from route/rule_item_protocol.go rename to route/rule/rule_item_protocol.go index 1988f8ade1..319b81d50d 100644 --- a/route/rule_item_protocol.go +++ b/route/rule/rule_item_protocol.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_query_type.go b/route/rule/rule_item_query_type.go similarity index 98% rename from route/rule_item_query_type.go rename to route/rule/rule_item_query_type.go index 7b6efdd044..36b615f3af 100644 --- a/route/rule_item_query_type.go +++ b/route/rule/rule_item_query_type.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_rule_set.go b/route/rule/rule_item_rule_set.go similarity index 99% rename from route/rule_item_rule_set.go rename to route/rule/rule_item_rule_set.go index b80fca995c..a0115a044a 100644 --- a/route/rule_item_rule_set.go +++ b/route/rule/rule_item_rule_set.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_user.go b/route/rule/rule_item_user.go similarity index 98% rename from route/rule_item_user.go rename to route/rule/rule_item_user.go index bed97fbaa6..d635fa165b 100644 --- a/route/rule_item_user.go +++ b/route/rule/rule_item_user.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_user_id.go b/route/rule/rule_item_user_id.go similarity index 98% rename from route/rule_item_user_id.go rename to route/rule/rule_item_user_id.go index 43ab704e3a..57372de003 100644 --- a/route/rule_item_user_id.go +++ b/route/rule/rule_item_user_id.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_wifi_bssid.go b/route/rule/rule_item_wifi_bssid.go similarity index 98% rename from route/rule_item_wifi_bssid.go rename to route/rule/rule_item_wifi_bssid.go index 3b1ff9c852..ae94bd6df2 100644 --- a/route/rule_item_wifi_bssid.go +++ b/route/rule/rule_item_wifi_bssid.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_wifi_ssid.go b/route/rule/rule_item_wifi_ssid.go similarity index 98% rename from route/rule_item_wifi_ssid.go rename to route/rule/rule_item_wifi_ssid.go index 62cf935eb0..3a928f77e5 100644 --- a/route/rule_item_wifi_ssid.go +++ b/route/rule/rule_item_wifi_ssid.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_set.go b/route/rule/rule_set.go similarity index 59% rename from route/rule_set.go rename to route/rule/rule_set.go index a2c6d0c192..be9821c0c3 100644 --- a/route/rule_set.go +++ b/route/rule/rule_set.go @@ -1,4 +1,4 @@ -package route +package rule import ( "context" @@ -41,3 +41,31 @@ func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet { panic("unexpected rule type") } } + +func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool { + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if cond(rule.DefaultOptions) { + return true + } + case C.RuleTypeLogical: + if hasHeadlessRule(rule.LogicalOptions.Rules, cond) { + return true + } + } + } + return false +} + +func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 +} + +func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 +} + +func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.IPCIDR) > 0 || rule.IPSet != nil +} diff --git a/route/rule_set_local.go b/route/rule/rule_set_local.go similarity index 99% rename from route/rule_set_local.go rename to route/rule/rule_set_local.go index c3ecf9ca28..9186454e68 100644 --- a/route/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -1,4 +1,4 @@ -package route +package rule import ( "context" diff --git a/route/rule_set_remote.go b/route/rule/rule_set_remote.go similarity index 99% rename from route/rule_set_remote.go rename to route/rule/rule_set_remote.go index 5045bf2dab..f055771827 100644 --- a/route/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -1,4 +1,4 @@ -package route +package rule import ( "bytes" diff --git a/route/router_rule.go b/route/rule_conds.go similarity index 72% rename from route/router_rule.go rename to route/rule_conds.go index 4c345b59a3..7644717674 100644 --- a/route/router_rule.go +++ b/route/rule_conds.go @@ -38,22 +38,6 @@ func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bo return false } -func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool { - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if cond(rule.DefaultOptions) { - return true - } - case C.RuleTypeLogical: - if hasHeadlessRule(rule.LogicalOptions.Rules, cond) { - return true - } - } - } - return false -} - func isGeoIPRule(rule option.DefaultRule) bool { return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) } @@ -78,10 +62,6 @@ func isProcessDNSRule(rule option.DefaultDNSRule) bool { return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 } -func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool { - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 -} - func notPrivateNode(code string) bool { return code != "private" } @@ -93,11 +73,3 @@ func isWIFIRule(rule option.DefaultRule) bool { func isWIFIDNSRule(rule option.DefaultDNSRule) bool { return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 } - -func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { - return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 -} - -func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool { - return len(rule.IPCIDR) > 0 || rule.IPSet != nil -} diff --git a/test/brutal_test.go b/test/brutal_test.go index bfe4d1fc02..18aae2e201 100644 --- a/test/brutal_test.go +++ b/test/brutal_test.go @@ -76,9 +76,18 @@ func TestBrutalShadowsocks(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -165,9 +174,18 @@ func TestBrutalTrojan(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -238,9 +256,18 @@ func TestBrutalVMess(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -342,9 +369,18 @@ func TestBrutalVLESS(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/clash_test.go b/test/clash_test.go index ffd3e10c78..bba7f3bebe 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -17,7 +17,7 @@ import ( "github.com/sagernet/sing/common/control" F "github.com/sagernet/sing/common/format" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -68,7 +68,7 @@ func init() { } defer dockerClient.Close() - list, err := dockerClient.ImageList(context.Background(), types.ImageListOptions{All: true}) + list, err := dockerClient.ImageList(context.Background(), image.ListOptions{All: true}) if err != nil { log.Warn(err) return @@ -85,13 +85,13 @@ func init() { return false } - for _, image := range allImages { - if imageExist(image) { + for _, i := range allImages { + if imageExist(i) { continue } - log.Info("pulling image: ", image) - imageStream, err := dockerClient.ImagePull(context.Background(), image, types.ImagePullOptions{}) + log.Info("pulling image: ", i) + imageStream, err := dockerClient.ImagePull(context.Background(), i, image.PullOptions{}) if err != nil { panic(err) } diff --git a/test/direct_test.go b/test/direct_test.go index ec3cf88c07..1dbf1de1b6 100644 --- a/test/direct_test.go +++ b/test/direct_test.go @@ -50,9 +50,18 @@ func _TestProxyProtocol(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "proxy-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "proxy-out", + }, + }, }, }, }, diff --git a/test/docker_test.go b/test/docker_test.go index ade813d75c..a85dd12cd9 100644 --- a/test/docker_test.go +++ b/test/docker_test.go @@ -11,7 +11,6 @@ import ( F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/rw" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" @@ -85,10 +84,10 @@ func startDockerContainer(t *testing.T, options DockerOptions) { cleanContainer(dockerContainer.ID) }) - require.NoError(t, dockerClient.ContainerStart(context.Background(), dockerContainer.ID, types.ContainerStartOptions{})) + require.NoError(t, dockerClient.ContainerStart(context.Background(), dockerContainer.ID, container.StartOptions{})) if writeStdin { - stdinAttach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, types.ContainerAttachOptions{ + stdinAttach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, container.AttachOptions{ Stdin: writeStdin, Stream: true, }) @@ -98,7 +97,7 @@ func startDockerContainer(t *testing.T, options DockerOptions) { stdinAttach.Close() } if debug.Enabled { - attach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, types.ContainerAttachOptions{ + attach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, container.AttachOptions{ Stdout: true, Stderr: true, Logs: true, @@ -118,5 +117,5 @@ func cleanContainer(id string) error { return err } defer dockerClient.Close() - return dockerClient.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{Force: true}) + return dockerClient.ContainerRemove(context.Background(), id, container.RemoveOptions{Force: true}) } diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index f22fe24982..1ca2121dae 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -75,9 +75,18 @@ func TestTUICDomainUDP(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "tuic-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "tuic-out", + }, + }, }, }, }, diff --git a/test/ech_test.go b/test/ech_test.go index 35d5d89158..90eae1f48c 100644 --- a/test/ech_test.go +++ b/test/ech_test.go @@ -85,9 +85,18 @@ func TestECH(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, @@ -166,9 +175,18 @@ func TestECHQUIC(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "tuic-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "tuic-out", + }, + }, }, }, }, @@ -249,8 +267,16 @@ func TestECHHysteria2(t *testing.T) { { Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "hy2-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "hy2-out", + }, + }, }, }, }, diff --git a/test/go.mod b/test/go.mod index 6caf6240d4..458ff8800b 100644 --- a/test/go.mod +++ b/test/go.mod @@ -1,25 +1,27 @@ module test -go 1.20 +go 1.23 + +toolchain go1.23.2 require github.com/sagernet/sing-box v0.0.0 replace github.com/sagernet/sing-box => ../ require ( - github.com/docker/docker v24.0.7+incompatible - github.com/docker/go-connections v0.4.0 - github.com/gofrs/uuid/v5 v5.2.0 - github.com/sagernet/quic-go v0.45.1-beta.2 - github.com/sagernet/sing v0.4.2 - github.com/sagernet/sing-dns v0.2.3 - github.com/sagernet/sing-quic v0.2.0-beta.12 + github.com/docker/docker v27.3.1+incompatible + github.com/docker/go-connections v0.5.0 + github.com/gofrs/uuid/v5 v5.3.0 + github.com/sagernet/quic-go v0.48.2-beta.1 + github.com/sagernet/sing v0.6.0-beta.2 + github.com/sagernet/sing-dns v0.4.0-beta.1 + github.com/sagernet/sing-quic v0.4.0-alpha.4 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 - github.com/spyzhov/ajson v0.9.0 + github.com/spyzhov/ajson v0.9.4 github.com/stretchr/testify v1.9.0 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.25.0 + golang.org/x/net v0.31.0 ) require ( @@ -28,30 +30,38 @@ require ( github.com/andybalholm/brotli v1.0.6 // indirect github.com/caddyserver/certmagic v0.20.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/cretz/bine v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.5.0 // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gaukas/godicttls v0.0.4 // indirect - github.com/go-chi/chi/v5 v5.0.12 // indirect + github.com/go-chi/chi/v5 v5.1.0 // 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 v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/btree v1.1.2 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect - github.com/hashicorp/yamux v0.1.1 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // 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/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect github.com/mholt/acmez v1.2.0 // indirect - github.com/miekg/dns v1.1.59 // indirect + github.com/miekg/dns v1.1.62 // 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.9.7 // indirect @@ -65,34 +75,41 @@ require ( github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect - github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect - github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect + github.com/sagernet/fswatch v0.1.1 // indirect + github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff // 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.2.0 // indirect - github.com/sagernet/sing-shadowtls v0.1.4 // indirect - github.com/sagernet/sing-tun v0.3.2 // indirect + github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect + github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect + github.com/sagernet/sing-tun v0.6.0-beta.2 // indirect github.com/sagernet/sing-vmess v0.1.12 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect - github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect - github.com/sagernet/utls v1.5.4 // indirect + github.com/sagernet/utls v1.6.7 // indirect github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect + github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.7.0 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // 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 f482438e37..5fc5292c9c 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1,14 +1,19 @@ berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= +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/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= @@ -17,21 +22,23 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +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/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/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= -github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +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/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 v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -40,18 +47,26 @@ 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/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= -github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= +github.com/gofrs/uuid/v5 v5.3.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/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +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/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +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/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/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 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.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= @@ -60,6 +75,7 @@ github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +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= @@ -69,18 +85,28 @@ github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/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.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= -github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -101,53 +127,65 @@ 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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.45.1-beta.2 h1:zkEeCbhdFFkrxKcuIRBtXNKci/1t2J/39QSG/sPvlmc= -github.com/sagernet/quic-go v0.45.1-beta.2/go.mod h1:+N3FqM9DAzOWfe64uxXuBejVJwX7DeW7BslzLO6N/xI= +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-20241021032506-a4324256e4a3 h1:RxEz7LhPNiF/gX/Hg+OXr5lqsM9iVAgmaK1L1vzlDRM= +github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= +github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= +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.48.0-beta.1 h1:86hQZrmuoARI3BpDRkQaP0iAVpywA4YsRrzJPYuPKWg= +github.com/sagernet/quic-go v0.48.0-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= +github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= 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.4.2 h1:jzGNJdZVRI0xlAfFugsIQUPvyB9SuWvbJK7zQCXc4QM= -github.com/sagernet/sing v0.4.2/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= -github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k= -github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= -github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= -github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= -github.com/sagernet/sing-quic v0.2.0-beta.12 h1:BhvA5mmrDFEyDUQB5eeu+9UhF+ieyuNJ5Rsb0dAG3QY= -github.com/sagernet/sing-quic v0.2.0-beta.12/go.mod h1:YVpLfVi8BvYM7NMrjmnvcRm3E8iMETf1gFQmTQDN9jI= +github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb h1:3IhGq2UmcbQfAcuqyE8RYKFapqEEa3eItS/MrZr+5l8= +github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.0-beta.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce h1:OfpxE5qnXMyU/9LtNgX4M7bGP11lJx4s+KZ3Sijb0HE= +github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= +github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= +github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec h1:6Fd/VsEsw9qIjaGi1IBTZSb4b4v5JYtNcoiBtGsQC48= +github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec/go.mod h1:RSwqqHwbtTOX3vs6ms8vMtBGH/0ZNyLm/uwt6TlmR84= +github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= +github.com/sagernet/sing-quic v0.3.0-rc.1 h1:SlzL1yfEAKJyRduub8vzOVtbyTLAX7RZEEBZxO5utts= +github.com/sagernet/sing-quic v0.3.0-rc.1/go.mod h1:uX+aUHA0fgIN6U3WViseDpSdTQriViZ7qz0Wbsf1mNQ= +github.com/sagernet/sing-quic v0.4.0-alpha.4/go.mod h1:h5RkKTmUhudJKzK7c87FPXD5w1bJjVyxMN9+opZcctA= 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.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY= -github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= +github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= +github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d h1:zWcIQM3eAKJGzy7zhqkO7zm7ZI890OdR4vSwx2mevS0= +github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d/go.mod h1:Xhv+Mz2nE7HZTwResni8EtTa7AMJz/S6uQLT5lV23M8= +github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= 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/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI= -github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY= -github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= -github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= +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.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= 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/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0= -github.com/spyzhov/ajson v0.9.0/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= @@ -156,6 +194,22 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +go.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.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +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.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +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= @@ -169,31 +223,33 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE 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.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= 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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= @@ -201,35 +257,41 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.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.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.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/http_test.go b/test/http_test.go index 4b5fe70fc9..88385c2757 100644 --- a/test/http_test.go +++ b/test/http_test.go @@ -49,9 +49,18 @@ func TestHTTPSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "http-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "http-out", + }, + }, }, }, }, diff --git a/test/hysteria2_test.go b/test/hysteria2_test.go index f549442811..9ca2f5d38d 100644 --- a/test/hysteria2_test.go +++ b/test/hysteria2_test.go @@ -94,8 +94,16 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { { Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "hy2-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "hy2-out", + }, + }, }, }, }, diff --git a/test/hysteria_test.go b/test/hysteria_test.go index 90ff62dd10..bde1b9fa73 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -75,9 +75,18 @@ func TestHysteriaSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "hy-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "hy-out", + }, + }, }, }, }, diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go index c2ef57a56c..9505c217ff 100644 --- a/test/inbound_detour_test.go +++ b/test/inbound_detour_test.go @@ -80,9 +80,18 @@ func TestChainedInbound(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/mux_cool_test.go b/test/mux_cool_test.go index ef47695a50..81130fad9a 100644 --- a/test/mux_cool_test.go +++ b/test/mux_cool_test.go @@ -159,9 +159,18 @@ func TestMuxCoolSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/mux_test.go b/test/mux_test.go index c02f270878..8d75518554 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -102,9 +102,18 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -166,9 +175,18 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index 7d063d9a96..4ef1ee9d93 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -197,9 +197,18 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -258,9 +267,18 @@ func TestShadowsocksUoT(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -319,9 +337,18 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index 2f53d46ada..6f9ee1e566 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -118,12 +118,23 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) }, }, Route: &option.RouteOptions{ - Rules: []option.Rule{{ - DefaultOptions: option.DefaultRule{ - Inbound: []string{"detour"}, - Outbound: "direct", + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"detour"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "direct", + }, + }, + }, }, - }}, + }, }, }) testTCP(t, clientPort, testPort) @@ -239,12 +250,23 @@ func TestShadowTLSInbound(t *testing.T) { }, }, Route: &option.RouteOptions{ - Rules: []option.Rule{{ - DefaultOptions: option.DefaultRule{ - Inbound: []string{"in"}, - Outbound: "out", + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "out", + }, + }, + }, }, - }}, + }, }, }) testTCP(t, clientPort, testPort) @@ -319,12 +341,23 @@ func TestShadowTLSOutbound(t *testing.T) { }, }, Route: &option.RouteOptions{ - Rules: []option.Rule{{ - DefaultOptions: option.DefaultRule{ - Inbound: []string{"detour"}, - Outbound: "direct", + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"detour"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "direct", + }, + }, + }, }, - }}, + }, }, }) testTCP(t, clientPort, testPort) diff --git a/test/tfo_test.go b/test/tfo_test.go index cc97e189d1..7bd34e2db7 100644 --- a/test/tfo_test.go +++ b/test/tfo_test.go @@ -60,9 +60,18 @@ func TestTCPSlowOpen(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/tls_test.go b/test/tls_test.go index da55faf5bf..cfc6c1a50b 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -76,9 +76,18 @@ func TestUTLS(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, diff --git a/test/trojan_test.go b/test/trojan_test.go index d8659b2e4f..f88ec88502 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -118,9 +118,18 @@ func TestTrojanSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, @@ -177,9 +186,18 @@ func TestTrojanPlainSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, diff --git a/test/tuic_test.go b/test/tuic_test.go index c2b71111f0..5b838f22f7 100644 --- a/test/tuic_test.go +++ b/test/tuic_test.go @@ -90,9 +90,18 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "tuic-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "tuic-out", + }, + }, }, }, }, diff --git a/test/v2ray_transport_test.go b/test/v2ray_transport_test.go index 2f39d18aed..c7362f345f 100644 --- a/test/v2ray_transport_test.go +++ b/test/v2ray_transport_test.go @@ -108,9 +108,18 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, @@ -187,9 +196,18 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, @@ -270,9 +288,18 @@ func TestVMessQUICSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, @@ -334,9 +361,18 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/vmess_test.go b/test/vmess_test.go index cc7879ab84..fcf7bf8f47 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -315,9 +315,17 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/transport/v2ray/grpc.go b/transport/v2ray/grpc.go index 05bc5a2a4e..1b4250addf 100644 --- a/transport/v2ray/grpc.go +++ b/transport/v2ray/grpc.go @@ -10,15 +10,16 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2raygrpc" "github.com/sagernet/sing-box/transport/v2raygrpclite" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewGRPCServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { if options.ForceLite { - return v2raygrpclite.NewServer(ctx, options, tlsConfig, handler) + return v2raygrpclite.NewServer(ctx, logger, options, tlsConfig, handler) } - return v2raygrpc.NewServer(ctx, options, tlsConfig, handler) + return v2raygrpc.NewServer(ctx, logger, options, tlsConfig, handler) } func NewGRPCClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/transport/v2ray/grpc_lite.go b/transport/v2ray/grpc_lite.go index 94f6fad13d..4f2814a726 100644 --- a/transport/v2ray/grpc_lite.go +++ b/transport/v2ray/grpc_lite.go @@ -9,12 +9,13 @@ import ( "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2raygrpclite" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { - return v2raygrpclite.NewServer(ctx, options, tlsConfig, handler) +func NewGRPCServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { + return v2raygrpclite.NewServer(ctx, logger, options, tlsConfig, handler) } func NewGRPCClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/transport/v2ray/quic.go b/transport/v2ray/quic.go index 5471157aff..4d3cdc6f8f 100644 --- a/transport/v2ray/quic.go +++ b/transport/v2ray/quic.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" "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" ) @@ -21,11 +22,11 @@ func RegisterQUICConstructor(server ServerConstructor[option.V2RayQUICOptions], quicClientConstructor = client } -func NewQUICServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewQUICServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { if quicServerConstructor == nil { return nil, os.ErrInvalid } - return quicServerConstructor(ctx, options, tlsConfig, handler) + return quicServerConstructor(ctx, logger, options, tlsConfig, handler) } func NewQUICClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/transport/v2ray/transport.go b/transport/v2ray/transport.go index deb8a7f0ee..ab52f55e8e 100644 --- a/transport/v2ray/transport.go +++ b/transport/v2ray/transport.go @@ -11,33 +11,34 @@ import ( "github.com/sagernet/sing-box/transport/v2rayhttpupgrade" "github.com/sagernet/sing-box/transport/v2raywebsocket" 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" ) type ( - ServerConstructor[O any] func(ctx context.Context, options O, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) + ServerConstructor[O any] func(ctx context.Context, logger logger.ContextLogger, options O, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) ClientConstructor[O any] func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options O, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) ) -func NewServerTransport(ctx context.Context, options option.V2RayTransportOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewServerTransport(ctx context.Context, logger logger.ContextLogger, options option.V2RayTransportOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { if options.Type == "" { return nil, nil } switch options.Type { case C.V2RayTransportTypeHTTP: - return v2rayhttp.NewServer(ctx, options.HTTPOptions, tlsConfig, handler) + return v2rayhttp.NewServer(ctx, logger, options.HTTPOptions, tlsConfig, handler) case C.V2RayTransportTypeWebsocket: - return v2raywebsocket.NewServer(ctx, options.WebsocketOptions, tlsConfig, handler) + return v2raywebsocket.NewServer(ctx, logger, options.WebsocketOptions, tlsConfig, handler) case C.V2RayTransportTypeQUIC: if tlsConfig == nil { return nil, C.ErrTLSRequired } - return NewQUICServer(ctx, options.QUICOptions, tlsConfig, handler) + return NewQUICServer(ctx, logger, options.QUICOptions, tlsConfig, handler) case C.V2RayTransportTypeGRPC: - return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler) + return NewGRPCServer(ctx, logger, options.GRPCOptions, tlsConfig, handler) case C.V2RayTransportTypeHTTPUpgrade: - return v2rayhttpupgrade.NewServer(ctx, options.HTTPUpgradeOptions, tlsConfig, handler) + return v2rayhttpupgrade.NewServer(ctx, logger, options.HTTPUpgradeOptions, tlsConfig, handler) default: return nil, E.New("unknown transport type: " + options.Type) } diff --git a/transport/v2raygrpc/client.go b/transport/v2raygrpc/client.go index 1e72040a15..af922b45c2 100644 --- a/transport/v2raygrpc/client.go +++ b/transport/v2raygrpc/client.go @@ -105,7 +105,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { cancel(err) return nil, err } - return NewGRPCConn(stream, cancel), nil + return NewGRPCConn(stream), nil } func (c *Client) Close() error { diff --git a/transport/v2raygrpc/conn.go b/transport/v2raygrpc/conn.go index bc78f91e3c..0a0a627f73 100644 --- a/transport/v2raygrpc/conn.go +++ b/transport/v2raygrpc/conn.go @@ -5,7 +5,6 @@ import ( "os" "time" - "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/baderror" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -15,17 +14,15 @@ var _ net.Conn = (*GRPCConn)(nil) type GRPCConn struct { GunService - cancel common.ContextCancelCauseFunc - cache []byte + cache []byte } -func NewGRPCConn(service GunService, cancel common.ContextCancelCauseFunc) *GRPCConn { +func NewGRPCConn(service GunService) *GRPCConn { if client, isClient := service.(GunService_TunClient); isClient { service = &clientConnWrapper{client} } return &GRPCConn{ GunService: service, - cancel: cancel, } } @@ -38,7 +35,6 @@ func (c *GRPCConn) Read(b []byte) (n int, err error) { hunk, err := c.Recv() err = baderror.WrapGRPC(err) if err != nil { - c.cancel(err) return } n = copy(b, hunk.Data) @@ -51,14 +47,12 @@ func (c *GRPCConn) Read(b []byte) (n int, err error) { func (c *GRPCConn) Write(b []byte) (n int, err error) { err = baderror.WrapGRPC(c.Send(&Hunk{Data: b})) if err != nil { - c.cancel(err) return } return len(b), nil } func (c *GRPCConn) Close() error { - c.cancel(net.ErrClosed) return nil } diff --git a/transport/v2raygrpc/server.go b/transport/v2raygrpc/server.go index 15088b268c..b6b13f829d 100644 --- a/transport/v2raygrpc/server.go +++ b/transport/v2raygrpc/server.go @@ -9,8 +9,10 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -25,11 +27,12 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context - handler N.TCPConnectionHandler + logger logger.ContextLogger + handler adapter.V2RayServerTransportHandler server *grpc.Server } -func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { var serverOptions []grpc.ServerOption if tlsConfig != nil { if !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) { @@ -43,17 +46,16 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t Timeout: time.Duration(options.PingTimeout), })) } - server := &Server{ctx, handler, grpc.NewServer(serverOptions...)} + server := &Server{ctx, logger, handler, grpc.NewServer(serverOptions...)} RegisterGunServiceCustomNameServer(server.server, server, options.ServiceName) return server, nil } func (s *Server) Tun(server GunService_TunServer) error { - ctx, cancel := common.ContextWithCancelCause(s.ctx) - conn := NewGRPCConn(server, cancel) - var metadata M.Metadata + conn := NewGRPCConn(server) + var source M.Socksaddr if remotePeer, loaded := peer.FromContext(server.Context()); loaded { - metadata.Source = M.SocksaddrFromNet(remotePeer.Addr) + source = M.SocksaddrFromNet(remotePeer.Addr) } if grpcMetadata, loaded := gM.FromIncomingContext(server.Context()); loaded { forwardFrom := strings.Join(grpcMetadata.Get("X-Forwarded-For"), ",") @@ -61,13 +63,16 @@ func (s *Server) Tun(server GunService_TunServer) error { for _, from := range strings.Split(forwardFrom, ",") { originAddr := M.ParseSocksaddr(from) if originAddr.IsValid() { - metadata.Source = originAddr.Unwrap() + source = originAddr.Unwrap() } } } } - go s.handler.NewConnection(ctx, conn, metadata) - <-ctx.Done() + done := make(chan struct{}) + go s.handler.NewConnectionEx(log.ContextWithNewID(s.ctx), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) { + close(done) + })) + <-done return nil } diff --git a/transport/v2raygrpclite/server.go b/transport/v2raygrpclite/server.go index 6d3e42ebe5..622d785abd 100644 --- a/transport/v2raygrpclite/server.go +++ b/transport/v2raygrpclite/server.go @@ -10,10 +10,12 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2rayhttp" "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" aTLS "github.com/sagernet/sing/common/tls" @@ -26,18 +28,19 @@ import ( var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { - tlsConfig tls.ServerConfig - handler adapter.V2RayServerTransportHandler - errorHandler E.Handler - httpServer *http.Server - h2Server *http2.Server - h2cHandler http.Handler - path string + tlsConfig tls.ServerConfig + logger logger.ContextLogger + handler adapter.V2RayServerTransportHandler + httpServer *http.Server + h2Server *http2.Server + h2cHandler http.Handler + path string } -func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ tlsConfig: tlsConfig, + logger: logger, handler: handler, path: "/" + options.ServiceName + "/Tun", h2Server: &http2.Server{ @@ -49,6 +52,9 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, } server.h2cHandler = h2c.NewHandler(server, server.h2Server) return server, nil @@ -74,10 +80,12 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "application/grpc") writer.Header().Set("TE", "trailers") writer.WriteHeader(http.StatusOK) - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) + done := make(chan struct{}) conn := v2rayhttp.NewHTTP2Wrapper(newGunConn(request.Body, writer, writer.(http.Flusher))) - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, N.OnceClose(func(it error) { + close(done) + })) + <-done conn.CloseWrapper() } @@ -85,7 +93,7 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index cad7d906b4..e0ee42a77e 100644 --- a/transport/v2rayhttp/server.go +++ b/transport/v2rayhttp/server.go @@ -11,11 +11,13 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" @@ -29,6 +31,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig handler adapter.V2RayServerTransportHandler httpServer *http.Server @@ -40,7 +43,7 @@ type Server struct { headers http.Header } -func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, tlsConfig: tlsConfig, @@ -63,6 +66,9 @@ func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig t BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, } server.h2cHandler = h2c.NewHandler(server, server.h2Server) return server, nil @@ -95,8 +101,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { } } - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) + source := sHttp.SourceAddress(request) if h, ok := writer.(http.Hijacker); ok { var requestBody *buf.Buffer if contentLength := int(request.ContentLength); contentLength > 0 { @@ -127,14 +132,18 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { if requestBody != nil { conn = bufio.NewCachedConn(conn, requestBody) } - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil) } else { writer.WriteHeader(http.StatusOK) + done := make(chan struct{}) conn := NewHTTP2Wrapper(&ServerHTTPConn{ NewHTTPConn(request.Body, writer), writer.(http.Flusher), }) - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) { + close(done) + })) + <-done conn.CloseWrapper() } } @@ -143,7 +152,7 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/v2rayhttpupgrade/server.go b/transport/v2rayhttpupgrade/server.go index a3b5d23ed9..6a42912e92 100644 --- a/transport/v2rayhttpupgrade/server.go +++ b/transport/v2rayhttpupgrade/server.go @@ -10,9 +10,11 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/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" aTLS "github.com/sagernet/sing/common/tls" @@ -23,6 +25,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig handler adapter.V2RayServerTransportHandler httpServer *http.Server @@ -31,7 +34,7 @@ type Server struct { headers http.Header } -func NewServer(ctx context.Context, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, tlsConfig: tlsConfig, @@ -50,6 +53,9 @@ func NewServer(ctx context.Context, options option.V2RayHTTPUpgradeOptions, tlsC BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, TLSNextProto: make(map[string]func(*http.Server, *tls.STDConn, http.Handler)), } return server, nil @@ -104,16 +110,14 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed")) return } - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil) } func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/v2rayquic/server.go b/transport/v2rayquic/server.go index f772103012..4c4397e6b6 100644 --- a/transport/v2rayquic/server.go +++ b/transport/v2rayquic/server.go @@ -15,6 +15,8 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-quic" "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" ) @@ -23,6 +25,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig quicConfig *quic.Config handler adapter.V2RayServerTransportHandler @@ -30,7 +33,7 @@ type Server struct { quicListener qtls.Listener } -func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { quicConfig := &quic.Config{ DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows, } @@ -39,6 +42,7 @@ func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig t } server := &Server{ ctx: ctx, + logger: logger, tlsConfig: tlsConfig, quicConfig: quicConfig, handler: handler, @@ -73,8 +77,8 @@ func (s *Server) acceptLoop() { } go func() { hErr := s.streamAcceptLoop(conn) - if hErr != nil { - s.handler.NewError(conn.Context(), hErr) + if hErr != nil && !E.IsClosedOrCanceled(hErr) { + s.logger.ErrorContext(conn.Context(), hErr) } }() } @@ -86,7 +90,7 @@ func (s *Server) streamAcceptLoop(conn quic.Connection) error { if err != nil { return err } - go s.handler.NewConnection(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.Metadata{}) + go s.handler.NewConnectionEx(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.SocksaddrFromNet(conn.RemoteAddr()), M.Socksaddr{}, nil) } } diff --git a/transport/v2raywebsocket/server.go b/transport/v2raywebsocket/server.go index 86f2de9cd3..ccabf086ad 100644 --- a/transport/v2raywebsocket/server.go +++ b/transport/v2raywebsocket/server.go @@ -11,11 +11,13 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" @@ -27,6 +29,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig handler adapter.V2RayServerTransportHandler httpServer *http.Server @@ -36,9 +39,10 @@ type Server struct { upgrader ws.HTTPUpgrader } -func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, + logger: logger, tlsConfig: tlsConfig, handler: handler, path: options.Path, @@ -59,6 +63,9 @@ func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsCon BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, } return server, nil } @@ -102,20 +109,19 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { s.invalidRequest(writer, request, 0, E.Cause(err, "upgrade websocket connection")) return } - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) - conn = NewConn(wsConn, metadata.Source.TCPAddr(), ws.StateServerSide) + source := sHttp.SourceAddress(request) + conn = NewConn(wsConn, source, ws.StateServerSide) if len(earlyData) > 0 { conn = bufio.NewCachedConn(conn, buf.As(earlyData)) } - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil) } func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { From 41b960552d98d21d03fed619d63351d98efb4652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 22 Oct 2024 21:28:22 +0800 Subject: [PATCH 0150/1272] Implement TCP and ICMP rejects --- adapter/router.go | 1 + constant/rule.go | 9 ++++-- inbound/tun.go | 12 ++++++-- option/rule_action.go | 24 +++++++++------ route/route.go | 62 +++++++++++++++++++++++++-------------- route/rule/rule_action.go | 26 ++++++++++++++-- 6 files changed, 94 insertions(+), 40 deletions(-) diff --git a/adapter/router.go b/adapter/router.go index 134c944208..c9cd46e91a 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -34,6 +34,7 @@ type Router interface { FakeIPStore() FakeIPStore ConnectionRouter + PreMatch(metadata InboundContext) error ConnectionRouterEx GeoIPReader() *geoip.Reader diff --git a/constant/rule.go b/constant/rule.go index ba74ec63f7..c7717376c1 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -34,7 +34,10 @@ const ( ) const ( - RuleActionRejectMethodDefault = "default" - RuleActionRejectMethodPortUnreachable = "port-unreachable" - RuleActionRejectMethodDrop = "drop" + RuleActionRejectMethodDefault = "default" + RuleActionRejectMethodReset = "reset" + RuleActionRejectMethodNetworkUnreachable = "network-unreachable" + RuleActionRejectMethodHostUnreachable = "host-unreachable" + RuleActionRejectMethodPortUnreachable = "port-unreachable" + RuleActionRejectMethodDrop = "drop" ) diff --git a/inbound/tun.go b/inbound/tun.go index 0d856419ef..f04ae71b3e 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -404,9 +404,15 @@ func (t *TUN) Close() error { ) } -func (t *TUN) PrepareConnection(source M.Socksaddr, destination M.Socksaddr) error { - // TODO: implement rejects - return nil +func (t *TUN) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { + return t.router.PreMatch(adapter.InboundContext{ + Inbound: t.tag, + InboundType: C.TypeTun, + Network: network, + Source: source, + Destination: destination, + InboundOptions: t.inboundOptions, + }) } func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { diff --git a/option/rule_action.go b/option/rule_action.go index 4f0ec177b8..f446d81d5f 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -136,23 +136,29 @@ type DNSRouteActionOptions struct { ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` } -type RejectActionOptions struct { - Method RejectMethod `json:"method,omitempty"` +type _RejectActionOptions struct { + Method string `json:"method,omitempty"` } -type RejectMethod string +type RejectActionOptions _RejectActionOptions -func (m *RejectMethod) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*string)(m)) +func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_RejectActionOptions)(r)) if err != nil { return err } - switch *m { - case C.RuleActionRejectMethodDefault, C.RuleActionRejectMethodPortUnreachable, C.RuleActionRejectMethodDrop: - return nil + switch r.Method { + case "", C.RuleActionRejectMethodDefault: + r.Method = C.RuleActionRejectMethodDefault + case C.RuleActionRejectMethodReset, + C.RuleActionRejectMethodNetworkUnreachable, + C.RuleActionRejectMethodHostUnreachable, + C.RuleActionRejectMethodPortUnreachable, + C.RuleActionRejectMethodDrop: default: - return E.New("unknown reject method: " + *m) + return E.New("unknown reject method: " + r.Method) } + return nil } type RouteActionSniff struct { diff --git a/route/route.go b/route/route.go index 86d4d95cc9..cecd0f2ae9 100644 --- a/route/route.go +++ b/route/route.go @@ -21,7 +21,6 @@ import ( "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" "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" @@ -89,7 +88,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, conn, nil, -1) + selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, false, conn, nil, -1) if err != nil { return err } @@ -108,16 +107,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad selectReturn = true case *rule.RuleActionReject: buf.ReleaseMulti(buffers) - var rejectErr error - switch action.Method { - case C.RuleActionRejectMethodDefault: - rejectErr = os.ErrClosed - case C.RuleActionRejectMethodPortUnreachable: - rejectErr = syscall.ECONNREFUSED - case C.RuleActionRejectMethodDrop: - rejectErr = tun.ErrDrop - } - N.CloseOnHandshakeFailure(conn, onClose, rejectErr) + N.CloseOnHandshakeFailure(conn, onClose, action.Error()) return nil } } @@ -236,7 +226,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) }*/ - selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, nil, conn, -1) + selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, false, nil, conn, -1) if err != nil { return err } @@ -306,8 +296,23 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m return nil } +func (r *Router) PreMatch(metadata adapter.InboundContext) error { + selectedRule, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil, -1) + if err != nil { + return err + } + if selectedRule == nil { + return nil + } + rejectAction, isReject := selectedRule.Action().(*rule.RuleActionReject) + if !isReject { + return nil + } + return rejectAction.Error() +} + func (r *Router) matchRule( - ctx context.Context, metadata *adapter.InboundContext, + ctx context.Context, metadata *adapter.InboundContext, preMatch bool, inputConn net.Conn, inputPacketConn N.PacketConn, ruleIndex int, ) (selectedRule adapter.Rule, selectedRuleIndex int, buffers []*buf.Buffer, fatalErr error) { if r.processSearcher != nil && metadata.ProcessInfo == nil { @@ -370,7 +375,7 @@ func (r *Router) matchRule( //nolint:staticcheck if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() { - if metadata.InboundOptions.SniffEnabled { + if !preMatch && metadata.InboundOptions.SniffEnabled { newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), @@ -415,15 +420,28 @@ match: if !matched { break } - r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + if !preMatch { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + switch currentRule.Action().Type() { + case C.RuleActionTypeReject, C.RuleActionTypeResolve: + r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } + } switch action := currentRule.Action().(type) { case *rule.RuleActionSniff: - newBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) - if newErr != nil { - fatalErr = newErr - return + if !preMatch { + newBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) + if newErr != nil { + fatalErr = newErr + return + } + buffers = append(buffers, newBuffers...) + } else { + selectedRule = currentRule + selectedRuleIndex = currentRuleIndex + break match } - buffers = append(buffers, newBuffers...) case *rule.RuleActionResolve: fatalErr = r.actionResolve(ctx, metadata, action) if fatalErr != nil { @@ -436,7 +454,7 @@ match: } ruleIndex = currentRuleIndex } - if metadata.Destination.Addr.IsUnspecified() { + if !preMatch && metadata.Destination.Addr.IsUnspecified() { newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) if newErr != nil { fatalErr = newErr diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index e85fc76381..a157e94e52 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -2,7 +2,9 @@ package rule import ( "net/netip" + "os" "strings" + "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -10,6 +12,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-tun" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" ) @@ -22,10 +25,10 @@ func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, }, nil case C.RuleActionTypeReturn: - return &RuleActionReject{}, nil + return &RuleActionReturn{}, nil case C.RuleActionTypeReject: return &RuleActionReject{ - Method: string(action.RejectOptions.Method), + Method: action.RejectOptions.Method, }, nil case C.RuleActionTypeHijackDNS: return &RuleActionHijackDNS{}, nil @@ -58,7 +61,7 @@ func NewDNSRuleAction(action option.DNSRuleAction) adapter.RuleAction { return &RuleActionReturn{} case C.RuleActionTypeReject: return &RuleActionReject{ - Method: string(action.RejectOptions.Method), + Method: action.RejectOptions.Method, } default: panic(F.ToString("unknown rule action: ", action.Action)) @@ -118,6 +121,23 @@ func (r *RuleActionReject) String() string { return F.ToString("reject(", r.Method, ")") } +func (r *RuleActionReject) Error() error { + switch r.Method { + case C.RuleActionRejectMethodReset: + return os.ErrClosed + case C.RuleActionRejectMethodNetworkUnreachable: + return syscall.ENETUNREACH + case C.RuleActionRejectMethodHostUnreachable: + return syscall.EHOSTUNREACH + case C.RuleActionRejectMethodDefault, C.RuleActionRejectMethodPortUnreachable: + return syscall.ECONNREFUSED + case C.RuleActionRejectMethodDrop: + return tun.ErrDrop + default: + panic(F.ToString("unknown reject method: ", r.Method)) + } +} + type RuleActionHijackDNS struct{} func (r *RuleActionHijackDNS) Type() string { From 179e3cb2f5255b2b4171541ddbd5a62a15266efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 22 Oct 2024 22:01:28 +0800 Subject: [PATCH 0151/1272] Implement resolve(server) --- adapter/inbound.go | 14 ++++---- route/route.go | 2 +- route/route_dns.go | 87 ++++++++++++++++++++++++++++------------------ 3 files changed, 62 insertions(+), 41 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index f4d5802f31..300f57e3d9 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -50,12 +50,14 @@ type InboundContext struct { // Deprecated InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool - DestinationAddresses []netip.Addr - SourceGeoIPCode string - GeoIPCode string - ProcessInfo *process.Info - QueryType uint16 - FakeIP bool + DNSServer string + + DestinationAddresses []netip.Addr + SourceGeoIPCode string + GeoIPCode string + ProcessInfo *process.Info + QueryType uint16 + FakeIP bool // rule cache diff --git a/route/route.go b/route/route.go index cecd0f2ae9..56493bd176 100644 --- a/route/route.go +++ b/route/route.go @@ -584,7 +584,7 @@ func (r *Router) actionSniff( func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error { if metadata.Destination.IsFqdn() { - // TODO: check if WithContext is necessary + metadata.DNSServer = action.Server addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy) if err != nil { return err diff --git a/route/route_dns.go b/route/route_dns.go index 43eb61e6df..60aff6a9ff 100644 --- a/route/route_dns.go +++ b/route/route_dns.go @@ -185,6 +185,20 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS cached bool err error ) + printResult := func() { + if err != nil { + if errors.Is(err, dns.ErrResponseRejectedCached) { + r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)") + } else if errors.Is(err, dns.ErrResponseRejected) { + r.dnsLogger.DebugContext(ctx, "response rejected for ", domain) + } else { + r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) + } + } else if len(responseAddrs) == 0 { + r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") + err = dns.RCodeNameError + } + } responseAddrs, cached = r.dnsClient.LookupCache(ctx, domain, strategy) if cached { if len(responseAddrs) == 0 { @@ -196,46 +210,51 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS ctx, metadata := adapter.ExtendContext(ctx) metadata.Destination = M.Socksaddr{} metadata.Domain = domain - var ( - transport dns.Transport - options dns.QueryOptions - rule adapter.DNSRule - ruleIndex int - ) - ruleIndex = -1 - for { - dnsCtx := adapter.OverrideContext(ctx) - var addressLimit bool - transport, options, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) - if strategy != dns.DomainStrategyAsIS { - options.Strategy = strategy - } - if rule != nil && rule.WithAddressLimit() { - addressLimit = true - responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, options, func(responseAddrs []netip.Addr) bool { - metadata.DestinationAddresses = responseAddrs - return rule.MatchAddressLimit(metadata) - }) - } else { - addressLimit = false - responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, options) + if metadata.DNSServer != "" { + transport, loaded := r.transportMap[metadata.DNSServer] + if !loaded { + return nil, E.New("transport not found: ", metadata.DNSServer) } - if err != nil { - if errors.Is(err, dns.ErrResponseRejectedCached) { - r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)") - } else if errors.Is(err, dns.ErrResponseRejected) { - r.dnsLogger.DebugContext(ctx, "response rejected for ", domain) + if strategy == dns.DomainStrategyAsIS { + if transportDomainStrategy, loaded := r.transportDomainStrategy[transport]; loaded { + strategy = transportDomainStrategy } else { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) + strategy = r.defaultDomainStrategy } - } else if len(responseAddrs) == 0 { - r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") - err = dns.RCodeNameError } - if !addressLimit || err == nil { - break + responseAddrs, err = r.dnsClient.Lookup(ctx, transport, domain, dns.QueryOptions{Strategy: strategy}) + } else { + var ( + transport dns.Transport + options dns.QueryOptions + rule adapter.DNSRule + ruleIndex int + ) + ruleIndex = -1 + for { + dnsCtx := adapter.OverrideContext(ctx) + var addressLimit bool + transport, options, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) + if strategy != dns.DomainStrategyAsIS { + options.Strategy = strategy + } + if rule != nil && rule.WithAddressLimit() { + addressLimit = true + responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, options, func(responseAddrs []netip.Addr) bool { + metadata.DestinationAddresses = responseAddrs + return rule.MatchAddressLimit(metadata) + }) + } else { + addressLimit = false + responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, options) + } + if !addressLimit || err == nil { + break + } + printResult() } } + printResult() if len(responseAddrs) > 0 { r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " ")) } From 9f7683818f9ab861182bc2b35dd731506f5b01b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 23 Oct 2024 13:44:08 +0800 Subject: [PATCH 0152/1272] Implement dns-hijack --- cmd/sing-box/cmd_tools.go | 7 +- inbound/tun.go | 5 +- outbound/dns.go | 25 ++++--- route/dns.go | 93 +++++++++++++++++++++++++ route/route.go | 139 ++++++++++++++++++++++++++++---------- route/rule/rule_action.go | 72 +++++++------------- 6 files changed, 245 insertions(+), 96 deletions(-) create mode 100644 route/dns.go diff --git a/cmd/sing-box/cmd_tools.go b/cmd/sing-box/cmd_tools.go index c45f585576..86b9302e09 100644 --- a/cmd/sing-box/cmd_tools.go +++ b/cmd/sing-box/cmd_tools.go @@ -1,6 +1,9 @@ package main import ( + "errors" + "os" + "github.com/sagernet/sing-box" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" @@ -23,7 +26,9 @@ func init() { func createPreStartedClient() (*box.Box, error) { options, err := readConfigAndMerge() if err != nil { - return nil, err + if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" { + return nil, err + } } instance, err := box.New(box.Options{Options: options}) if err != nil { diff --git a/inbound/tun.go b/inbound/tun.go index f04ae71b3e..11b16428b9 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -36,8 +36,9 @@ type TUN struct { router adapter.Router logger log.ContextLogger // Deprecated - inboundOptions option.InboundOptions - tunOptions tun.Options + inboundOptions option.InboundOptions + tunOptions tun.Options + // Deprecated endpointIndependentNat bool udpTimeout time.Duration stack string diff --git a/outbound/dns.go b/outbound/dns.go index 08661a99aa..d9c92f19ec 100644 --- a/outbound/dns.go +++ b/outbound/dns.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "net" "os" + "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" @@ -50,14 +51,15 @@ func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter metadata.Destination = M.Socksaddr{} defer conn.Close() for { - err := d.handleConnection(ctx, conn, metadata) + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) if err != nil { return err } } } -func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net.Conn, metadata adapter.InboundContext) error { var queryLength uint16 err := binary.Read(conn, binary.BigEndian, &queryLength) if err != nil { @@ -79,7 +81,7 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap } metadataInQuery := metadata go func() error { - response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) if err != nil { return err } @@ -100,10 +102,14 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap // Deprecated func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) +} + +func NewDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn var counters []N.CountFunc - var cachedPackets []*N.PacketBuffer + cachedPackets = common.Reverse(cachedPackets) for { reader, counters = N.UnwrapCountPacketReader(reader, counters) if cachedReader, isCached := reader.(N.CachedPacketReader); isCached { @@ -115,7 +121,7 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada } if readWaiter, created := bufio.CreatePacketReadWaiter(reader); created { readWaiter.InitializeReadWaiter(N.ReadWaitOptions{}) - return d.newPacketConnection(ctx, conn, readWaiter, counters, cachedPackets, metadata) + return newDNSPacketConnection(ctx, router, conn, readWaiter, counters, cachedPackets, metadata) } break } @@ -161,7 +167,7 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada } metadataInQuery := metadata go func() error { - response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) if err != nil { cancel(err) return err @@ -186,7 +192,7 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada return group.Run(fastClose) } -func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { +func newDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { fastClose, cancel := common.ContextWithCancelCause(ctx) timeout := canceler.New(fastClose, cancel, C.DNSTimeout) var group task.Group @@ -206,11 +212,12 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa } err = message.Unpack(packet.Buffer.Bytes()) packet.Buffer.Release() + destination = packet.Destination + N.PutPacketBuffer(packet) if err != nil { cancel(err) return err } - destination = packet.Destination } else { buffer, destination, err = readWaiter.WaitReadPacket() if err != nil { @@ -230,7 +237,7 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa } metadataInQuery := metadata go func() error { - response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) if err != nil { cancel(err) return err diff --git a/route/dns.go b/route/dns.go new file mode 100644 index 0000000000..cc0760b851 --- /dev/null +++ b/route/dns.go @@ -0,0 +1,93 @@ +package route + +import ( + "context" + "errors" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-tun" + "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" + "github.com/sagernet/sing/common/udpnat2" + + mDNS "github.com/miekg/dns" +) + +func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + metadata.Destination = M.Socksaddr{} + for { + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := outbound.HandleStreamDNSRequest(ctx, r, conn, metadata) + if err != nil { + return err + } + } +} + +func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext) { + if uConn, isUDPNAT2 := conn.(*udpnat.Conn); isUDPNAT2 { + metadata.Destination = M.Socksaddr{} + for _, packet := range packetBuffers { + buffer := packet.Buffer + destination := packet.Destination + N.PutPacketBuffer(packet) + go ExchangeDNSPacket(ctx, r, uConn, buffer, metadata, destination) + } + uConn.SetHandler(&dnsHijacker{ + router: r, + conn: conn, + ctx: ctx, + metadata: metadata, + }) + return + } + err := outbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) + if err != nil && !E.IsClosedOrCanceled(err) { + r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) + } +} + +func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) { + err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination) + if err != nil && !errors.Is(err, tun.ErrDrop) && !E.IsClosedOrCanceled(err) { + router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) + } +} + +func exchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) error { + var message mDNS.Msg + err := message.Unpack(buffer.Bytes()) + buffer.Release() + if err != nil { + return E.Cause(err, "unpack request") + } + response, err := router.Exchange(adapter.WithContext(ctx, &metadata), &message) + if err != nil { + return err + } + responseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024) + if err != nil { + return err + } + err = conn.WritePacket(responseBuffer, destination) + responseBuffer.Release() + return err +} + +type dnsHijacker struct { + router *Router + conn N.PacketConn + ctx context.Context + metadata adapter.InboundContext +} + +func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) { + go ExchangeDNSPacket(h.ctx, h.router, h.conn, buffer, h.metadata, destination) +} diff --git a/route/route.go b/route/route.go index 56493bd176..ebffddddbf 100644 --- a/route/route.go +++ b/route/route.go @@ -88,7 +88,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, -1) + selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, conn, nil, -1) if err != nil { return err } @@ -109,6 +109,12 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad buf.ReleaseMulti(buffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error()) return nil + case *rule.RuleActionHijackDNS: + for _, buffer := range buffers { + conn = bufio.NewCachedConn(conn, buffer) + } + r.hijackDNSStream(ctx, conn, metadata) + return nil } } if selectedRule == nil || selectReturn { @@ -226,7 +232,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) }*/ - selectedRule, _, buffers, err := r.matchRule(ctx, &metadata, false, nil, conn, -1) + selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, nil, conn, -1) if err != nil { return err } @@ -238,32 +244,35 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m var loaded bool selectedOutbound, loaded = r.Outbound(action.Outbound) if !loaded { - buf.ReleaseMulti(buffers) + N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("outbound not found: ", action.Outbound) } metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping case *rule.RuleActionReturn: selectReturn = true case *rule.RuleActionReject: - buf.ReleaseMulti(buffers) + N.ReleaseMultiPacketBuffer(packetBuffers) N.CloseOnHandshakeFailure(conn, onClose, syscall.ECONNREFUSED) return nil + case *rule.RuleActionHijackDNS: + r.hijackDNSPacket(ctx, conn, packetBuffers, metadata) + return nil } } if selectedRule == nil || selectReturn { if r.defaultOutboundForPacketConnection == nil { - buf.ReleaseMulti(buffers) + N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("missing default outbound with UDP support") } selectedOutbound = r.defaultOutboundForPacketConnection } if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { - buf.ReleaseMulti(buffers) + N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) } - for _, buffer := range buffers { - // TODO: check if metadata.Destination == packet destination - conn = bufio.NewCachedPacketConn(conn, buffer, metadata.Destination) + for _, buffer := range packetBuffers { + conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) + N.PutPacketBuffer(buffer) } if r.clashServer != nil { trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, selectedRule) @@ -297,7 +306,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } func (r *Router) PreMatch(metadata adapter.InboundContext) error { - selectedRule, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil, -1) + selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil, -1) if err != nil { return err } @@ -314,7 +323,10 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error { func (r *Router) matchRule( ctx context.Context, metadata *adapter.InboundContext, preMatch bool, inputConn net.Conn, inputPacketConn N.PacketConn, ruleIndex int, -) (selectedRule adapter.Rule, selectedRuleIndex int, buffers []*buf.Buffer, fatalErr error) { +) ( + selectedRule adapter.Rule, selectedRuleIndex int, + buffers []*buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error, +) { if r.processSearcher != nil && metadata.ProcessInfo == nil { var originDestination netip.AddrPort if metadata.OriginDestination.IsValid() { @@ -376,7 +388,7 @@ func (r *Router) matchRule( //nolint:staticcheck if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() { if !preMatch && metadata.InboundOptions.SniffEnabled { - newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ + newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), }, inputConn, inputPacketConn) @@ -384,7 +396,11 @@ func (r *Router) matchRule( fatalErr = newErr return } - buffers = append(buffers, newBuffers...) + if newBuffer != nil { + buffers = []*buf.Buffer{newBuffer} + } else if len(newPackerBuffers) > 0 { + packetBuffers = newPackerBuffers + } } if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { fatalErr = r.actionResolve(ctx, metadata, &rule.RuleActionResolve{ @@ -421,22 +437,36 @@ match: break } if !preMatch { - r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) + } } else { switch currentRule.Action().Type() { case C.RuleActionTypeReject, C.RuleActionTypeResolve: - r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] => ", currentRule.Action()) + } } } switch action := currentRule.Action().(type) { case *rule.RuleActionSniff: if !preMatch { - newBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) if newErr != nil { fatalErr = newErr return } - buffers = append(buffers, newBuffers...) + if newBuffer != nil { + buffers = append(buffers, newBuffer) + } else if len(newPacketBuffers) > 0 { + packetBuffers = append(packetBuffers, newPacketBuffers...) + } } else { selectedRule = currentRule selectedRuleIndex = currentRuleIndex @@ -455,12 +485,16 @@ match: ruleIndex = currentRuleIndex } if !preMatch && metadata.Destination.Addr.IsUnspecified() { - newBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) if newErr != nil { fatalErr = newErr return } - buffers = append(buffers, newBuffers...) + if newBuffer != nil { + buffers = append(buffers, newBuffer) + } else if len(newPacketBuffers) > 0 { + packetBuffers = append(packetBuffers, newPacketBuffers...) + } } return } @@ -468,18 +502,31 @@ match: func (r *Router) actionSniff( ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff, inputConn net.Conn, inputPacketConn N.PacketConn, -) (buffers []*buf.Buffer, fatalErr error) { +) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) { if sniff.Skip(metadata) { return - } else if inputConn != nil && len(action.StreamSniffers) > 0 { - buffer := buf.NewPacket() + } else if inputConn != nil { + sniffBuffer := buf.NewPacket() + var streamSniffers []sniff.StreamSniffer + if len(action.StreamSniffers) > 0 { + streamSniffers = action.StreamSniffers + } else { + streamSniffers = []sniff.StreamSniffer{ + sniff.TLSClientHello, + sniff.HTTPHost, + sniff.StreamDomainNameQuery, + sniff.BitTorrent, + sniff.SSH, + sniff.RDP, + } + } err := sniff.PeekStream( ctx, metadata, inputConn, - buffer, + sniffBuffer, action.Timeout, - action.StreamSniffers..., + streamSniffers..., ) if err == nil { //goland:noinspection GoDeprecation @@ -497,15 +544,15 @@ func (r *Router) actionSniff( r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) } } - if !buffer.IsEmpty() { - buffers = append(buffers, buffer) + if !sniffBuffer.IsEmpty() { + buffer = sniffBuffer } else { - buffer.Release() + sniffBuffer.Release() } - } else if inputPacketConn != nil && len(action.PacketSniffers) > 0 { + } else if inputPacketConn != nil { for { var ( - buffer = buf.NewPacket() + sniffBuffer = buf.NewPacket() destination M.Socksaddr done = make(chan struct{}) err error @@ -516,7 +563,7 @@ func (r *Router) actionSniff( sniffTimeout = action.Timeout } inputPacketConn.SetReadDeadline(time.Now().Add(sniffTimeout)) - destination, err = inputPacketConn.ReadPacket(buffer) + destination, err = inputPacketConn.ReadPacket(sniffBuffer) inputPacketConn.SetReadDeadline(time.Time{}) close(done) }() @@ -528,7 +575,7 @@ func (r *Router) actionSniff( return } if err != nil { - buffer.Release() + sniffBuffer.Release() if !errors.Is(err, os.ErrDeadlineExceeded) { fatalErr = err return @@ -538,22 +585,40 @@ func (r *Router) actionSniff( if metadata.Destination.Addr.IsUnspecified() { metadata.Destination = destination } - if len(buffers) > 0 { + if len(packetBuffers) > 0 { err = sniff.PeekPacket( ctx, metadata, - buffer.Bytes(), + sniffBuffer.Bytes(), sniff.QUICClientHello, ) } else { + var packetSniffers []sniff.PacketSniffer + if len(action.PacketSniffers) > 0 { + packetSniffers = action.PacketSniffers + } else { + packetSniffers = []sniff.PacketSniffer{ + sniff.DomainNameQuery, + sniff.QUICClientHello, + sniff.STUNMessage, + sniff.UTP, + sniff.UDPTracker, + sniff.DTLSRecord, + } + } err = sniff.PeekPacket( ctx, metadata, - buffer.Bytes(), - action.PacketSniffers..., + sniffBuffer.Bytes(), + packetSniffers..., ) } - buffers = append(buffers, buffer) - if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(buffers) == 0 { + packetBuffer := N.NewPacketBuffer() + *packetBuffer = N.PacketBuffer{ + Buffer: sniffBuffer, + Destination: destination, + } + packetBuffers = append(packetBuffers, packetBuffer) + if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(packetBuffers) == 0 { r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") continue } diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index a157e94e52..57b7364794 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -162,53 +162,31 @@ func (r *RuleActionSniff) Type() string { } func (r *RuleActionSniff) build() error { - if len(r.StreamSniffers) > 0 || len(r.PacketSniffers) > 0 { - return nil - } - if len(r.snifferNames) > 0 { - for _, name := range r.snifferNames { - switch name { - case C.ProtocolTLS: - r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello) - case C.ProtocolHTTP: - r.StreamSniffers = append(r.StreamSniffers, sniff.HTTPHost) - case C.ProtocolQUIC: - r.PacketSniffers = append(r.PacketSniffers, sniff.QUICClientHello) - case C.ProtocolDNS: - r.StreamSniffers = append(r.StreamSniffers, sniff.StreamDomainNameQuery) - r.PacketSniffers = append(r.PacketSniffers, sniff.DomainNameQuery) - case C.ProtocolSTUN: - r.PacketSniffers = append(r.PacketSniffers, sniff.STUNMessage) - case C.ProtocolBitTorrent: - r.StreamSniffers = append(r.StreamSniffers, sniff.BitTorrent) - r.PacketSniffers = append(r.PacketSniffers, sniff.UTP) - r.PacketSniffers = append(r.PacketSniffers, sniff.UDPTracker) - case C.ProtocolDTLS: - r.PacketSniffers = append(r.PacketSniffers, sniff.DTLSRecord) - case C.ProtocolSSH: - r.StreamSniffers = append(r.StreamSniffers, sniff.SSH) - case C.ProtocolRDP: - r.StreamSniffers = append(r.StreamSniffers, sniff.RDP) - default: - return E.New("unknown sniffer: ", name) - } - } - } else { - r.StreamSniffers = []sniff.StreamSniffer{ - sniff.TLSClientHello, - sniff.HTTPHost, - sniff.StreamDomainNameQuery, - sniff.BitTorrent, - sniff.SSH, - sniff.RDP, - } - r.PacketSniffers = []sniff.PacketSniffer{ - sniff.DomainNameQuery, - sniff.QUICClientHello, - sniff.STUNMessage, - sniff.UTP, - sniff.UDPTracker, - sniff.DTLSRecord, + for _, name := range r.snifferNames { + switch name { + case C.ProtocolTLS: + r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello) + case C.ProtocolHTTP: + r.StreamSniffers = append(r.StreamSniffers, sniff.HTTPHost) + case C.ProtocolQUIC: + r.PacketSniffers = append(r.PacketSniffers, sniff.QUICClientHello) + case C.ProtocolDNS: + r.StreamSniffers = append(r.StreamSniffers, sniff.StreamDomainNameQuery) + r.PacketSniffers = append(r.PacketSniffers, sniff.DomainNameQuery) + case C.ProtocolSTUN: + r.PacketSniffers = append(r.PacketSniffers, sniff.STUNMessage) + case C.ProtocolBitTorrent: + r.StreamSniffers = append(r.StreamSniffers, sniff.BitTorrent) + r.PacketSniffers = append(r.PacketSniffers, sniff.UTP) + r.PacketSniffers = append(r.PacketSniffers, sniff.UDPTracker) + case C.ProtocolDTLS: + r.PacketSniffers = append(r.PacketSniffers, sniff.DTLSRecord) + case C.ProtocolSSH: + r.StreamSniffers = append(r.StreamSniffers, sniff.SSH) + case C.ProtocolRDP: + r.StreamSniffers = append(r.StreamSniffers, sniff.RDP) + default: + return E.New("unknown sniffer: ", name) } } return nil From e233fd4fe51a76b8f044982def1d269f9d618fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 2 Nov 2024 00:39:02 +0800 Subject: [PATCH 0153/1272] refactor: Modular inbounds/outbounds --- adapter/inbound.go | 7 + adapter/inbound/adapter.go | 21 ++ adapter/inbound/registry.go | 68 +++++ adapter/outbound.go | 9 + adapter/outbound/adapter.go | 45 ++++ {outbound => adapter/outbound}/default.go | 47 ---- adapter/outbound/registry.go | 68 +++++ box.go | 73 ++++-- cmd/sing-box/cmd.go | 4 +- cmd/sing-box/cmd_format.go | 3 +- cmd/sing-box/cmd_merge.go | 23 +- cmd/sing-box/cmd_run.go | 6 +- cmd/sing-box/cmd_tools.go | 2 +- common/dialer/default.go | 2 +- common/dialer/wireguard.go | 4 + common/dialer/wireguard_control.go | 11 - common/dialer/wiregurad_stub.go | 9 - common/listener/listener.go | 136 ++++++++++ .../listener/listener_go121.go | 2 +- common/listener/listener_go123.go | 16 ++ .../listener/listener_nongo121.go | 2 +- common/listener/listener_nongo123.go | 15 ++ common/listener/listener_tcp.go | 85 ++++++ common/listener/listener_udp.go | 153 +++++++++++ experimental/clashapi/api_meta_group.go | 10 +- experimental/clashapi/proxies.go | 6 +- experimental/libbox/command_group.go | 22 +- experimental/libbox/command_select.go | 4 +- experimental/libbox/command_urltest.go | 4 +- experimental/libbox/config.go | 19 +- experimental/libbox/service.go | 8 +- experimental/libbox/setup.go | 1 - go.mod | 2 - go.sum | 8 - inbound/builder.go | 54 ---- inbound/default.go | 209 --------------- inbound/default_tcp.go | 84 ------ inbound/default_tcp_go1.20.go | 18 -- inbound/default_tcp_nongo1.20.go | 15 -- inbound/default_udp.go | 208 --------------- inbound/direct.go | 111 -------- inbound/http.go | 119 --------- inbound/hysteria_stub.go | 20 -- inbound/mixed.go | 70 ----- inbound/naive_quic.go | 47 ---- inbound/naive_quic_stub.go | 11 - inbound/redirect.go | 45 ---- inbound/shadowsocks.go | 114 -------- inbound/socks.go | 52 ---- inbound/tuic_stub.go | 16 -- include/quic.go | 18 ++ include/quic_stub.go | 34 +++ include/registry.go | 95 +++++++ include/wireguard.go | 12 + include/wireguard_stub.go | 20 ++ option/inbound.go | 122 +++------ option/json.go | 71 ----- option/{config.go => options.go} | 7 +- option/outbound.go | 103 ++------ option/rule.go | 13 +- option/rule_action.go | 13 +- option/rule_dns.go | 13 +- option/rule_set.go | 13 +- option/simple.go | 2 +- option/tls_acme.go | 5 +- option/v2ray_transport.go | 5 +- outbound/block.go | 54 ---- outbound/builder.go | 65 ----- outbound/hysteria_stub.go | 20 -- outbound/lookback.go | 14 - outbound/shadowsocksr.go | 18 -- outbound/shadowsocksr_stub.go | 16 -- outbound/tor_embed.go | 15 -- outbound/tor_embed_mobile.go | 15 -- outbound/tor_external.go | 9 - outbound/tuic_stub.go | 16 -- outbound/wireguard_stub.go | 16 -- protocol/block/outbound.go | 42 +++ protocol/direct/inbound.go | 139 ++++++++++ .../direct/loopback_detect.go | 2 +- .../direct.go => protocol/direct/outbound.go | 48 ++-- outbound/dns.go => protocol/dns/handle.go | 47 +--- protocol/dns/outbound.go | 61 +++++ {outbound => protocol/group}/selector.go | 41 +-- {outbound => protocol/group}/urltest.go | 29 +- protocol/http/inbound.go | 122 +++++++++ outbound/http.go => protocol/http/outbound.go | 35 ++- .../hysteria/inbound.go | 61 +++-- .../hysteria/outbound.go | 44 ++-- .../hysteria2/inbound.go | 61 +++-- .../hysteria2/outbound.go | 44 ++-- protocol/mixed/inbound.go | 109 ++++++++ protocol/naive/inbound.go | 248 ++++++++++++++++++ .../naive/inbound_conn.go | 219 +--------------- protocol/naive/quic/inbound_init.go | 52 ++++ protocol/redirect/redirect.go | 65 +++++ {inbound => protocol/redirect}/tproxy.go | 79 ++++-- protocol/shadowsocks/inbound.go | 179 +++++++++++++ .../shadowsocks/inbound_multi.go | 83 +++--- .../shadowsocks/inbound_relay.go | 79 ++++-- .../shadowsocks/outbound.go | 45 ++-- .../shadowtls/inbound.go | 58 ++-- .../shadowtls/outbound.go | 30 +-- protocol/socks/inbound.go | 91 +++++++ .../socks.go => protocol/socks/outbound.go | 51 ++-- outbound/ssh.go => protocol/ssh/outbound.go | 42 ++- outbound/tor.go => protocol/tor/outbound.go | 41 ++- {outbound => protocol/tor}/proxy.go | 7 +- .../trojan.go => protocol/trojan/inbound.go | 88 ++++--- .../trojan.go => protocol/trojan/outbound.go | 39 ++- inbound/tuic.go => protocol/tuic/inbound.go | 61 +++-- outbound/tuic.go => protocol/tuic/outbound.go | 42 ++- inbound/tun.go => protocol/tun/inbound.go | 36 +-- inbound/vless.go => protocol/vless/inbound.go | 101 ++++--- .../vless.go => protocol/vless/outbound.go | 41 ++- inbound/vmess.go => protocol/vmess/inbound.go | 101 ++++--- .../vmess.go => protocol/vmess/outbound.go | 41 ++- protocol/wireguard/init.go | 10 + .../wireguard/outbound.go | 62 +++-- route/dns.go | 6 +- route/route.go | 2 +- route/router.go | 22 +- test/brutal_test.go | 16 +- test/direct_test.go | 4 +- test/domain_inbound_test.go | 6 +- test/ech_test.go | 12 +- test/http_test.go | 4 +- test/hysteria2_test.go | 10 +- test/hysteria_test.go | 10 +- test/inbound_detour_test.go | 4 +- test/mux_cool_test.go | 10 +- test/mux_test.go | 8 +- test/naive_test.go | 6 +- test/shadowsocks_legacy_test.go | 4 +- test/shadowsocks_test.go | 18 +- test/shadowtls_test.go | 14 +- test/ss_plugin_test.go | 4 +- test/tfo_test.go | 4 +- test/tls_test.go | 4 +- test/trojan_test.go | 12 +- test/tuic_test.go | 10 +- test/v2ray_api_test.go | 4 +- test/v2ray_grpc_test.go | 6 +- test/v2ray_transport_test.go | 16 +- test/v2ray_ws_test.go | 6 +- test/vmess_test.go | 10 +- test/wireguard_test.go | 4 +- test/wrapper_test.go | 2 +- transport/trojan/mux.go | 9 +- transport/trojan/service.go | 8 +- transport/wireguard/client_bind.go | 11 +- transport/wireguard/resolve.go | 2 +- 152 files changed, 3113 insertions(+), 2923 deletions(-) create mode 100644 adapter/inbound/adapter.go create mode 100644 adapter/inbound/registry.go create mode 100644 adapter/outbound/adapter.go rename {outbound => adapter/outbound}/default.go (87%) create mode 100644 adapter/outbound/registry.go delete mode 100644 common/dialer/wireguard_control.go delete mode 100644 common/dialer/wiregurad_stub.go create mode 100644 common/listener/listener.go rename inbound/default_tcp_go1.21.go => common/listener/listener_go121.go (90%) create mode 100644 common/listener/listener_go123.go rename inbound/default_tcp_nongo1.21.go => common/listener/listener_nongo121.go (87%) create mode 100644 common/listener/listener_nongo123.go create mode 100644 common/listener/listener_tcp.go create mode 100644 common/listener/listener_udp.go delete mode 100644 inbound/builder.go delete mode 100644 inbound/default.go delete mode 100644 inbound/default_tcp.go delete mode 100644 inbound/default_tcp_go1.20.go delete mode 100644 inbound/default_tcp_nongo1.20.go delete mode 100644 inbound/default_udp.go delete mode 100644 inbound/direct.go delete mode 100644 inbound/http.go delete mode 100644 inbound/hysteria_stub.go delete mode 100644 inbound/mixed.go delete mode 100644 inbound/naive_quic.go delete mode 100644 inbound/naive_quic_stub.go delete mode 100644 inbound/redirect.go delete mode 100644 inbound/shadowsocks.go delete mode 100644 inbound/socks.go delete mode 100644 inbound/tuic_stub.go create mode 100644 include/registry.go create mode 100644 include/wireguard.go create mode 100644 include/wireguard_stub.go delete mode 100644 option/json.go rename option/{config.go => options.go} (84%) delete mode 100644 outbound/block.go delete mode 100644 outbound/builder.go delete mode 100644 outbound/hysteria_stub.go delete mode 100644 outbound/lookback.go delete mode 100644 outbound/shadowsocksr.go delete mode 100644 outbound/shadowsocksr_stub.go delete mode 100644 outbound/tor_embed.go delete mode 100644 outbound/tor_embed_mobile.go delete mode 100644 outbound/tor_external.go delete mode 100644 outbound/tuic_stub.go delete mode 100644 outbound/wireguard_stub.go create mode 100644 protocol/block/outbound.go create mode 100644 protocol/direct/inbound.go rename outbound/direct_loopback_detect.go => protocol/direct/loopback_detect.go (99%) rename outbound/direct.go => protocol/direct/outbound.go (76%) rename outbound/dns.go => protocol/dns/handle.go (83%) create mode 100644 protocol/dns/outbound.go rename {outbound => protocol/group}/selector.go (82%) rename {outbound => protocol/group}/urltest.go (94%) create mode 100644 protocol/http/inbound.go rename outbound/http.go => protocol/http/outbound.go (56%) rename inbound/hysteria.go => protocol/hysteria/inbound.go (71%) rename outbound/hysteria.go => protocol/hysteria/outbound.go (76%) rename inbound/hysteria2.go => protocol/hysteria2/inbound.go (74%) rename outbound/hysteria2.go => protocol/hysteria2/outbound.go (69%) create mode 100644 protocol/mixed/inbound.go create mode 100644 protocol/naive/inbound.go rename inbound/naive.go => protocol/naive/inbound_conn.go (58%) create mode 100644 protocol/naive/quic/inbound_init.go create mode 100644 protocol/redirect/redirect.go rename {inbound => protocol/redirect}/tproxy.go (60%) create mode 100644 protocol/shadowsocks/inbound.go rename inbound/shadowsocks_multi.go => protocol/shadowsocks/inbound_multi.go (59%) rename inbound/shadowsocks_relay.go => protocol/shadowsocks/inbound_relay.go (57%) rename outbound/shadowsocks.go => protocol/shadowsocks/outbound.go (80%) rename inbound/shadowtls.go => protocol/shadowtls/inbound.go (62%) rename outbound/shadowtls.go => protocol/shadowtls/outbound.go (74%) create mode 100644 protocol/socks/inbound.go rename outbound/socks.go => protocol/socks/outbound.go (65%) rename outbound/ssh.go => protocol/ssh/outbound.go (80%) rename outbound/tor.go => protocol/tor/outbound.go (83%) rename {outbound => protocol/tor}/proxy.go (94%) rename inbound/trojan.go => protocol/trojan/inbound.go (68%) rename outbound/trojan.go => protocol/trojan/outbound.go (79%) rename inbound/tuic.go => protocol/tuic/inbound.go (68%) rename outbound/tuic.go => protocol/tuic/outbound.go (76%) rename inbound/tun.go => protocol/tun/inbound.go (92%) rename inbound/vless.go => protocol/vless/inbound.go (60%) rename outbound/vless.go => protocol/vless/outbound.go (84%) rename inbound/vmess.go => protocol/vmess/inbound.go (62%) rename outbound/vmess.go => protocol/vmess/outbound.go (84%) create mode 100644 protocol/wireguard/init.go rename outbound/wireguard.go => protocol/wireguard/outbound.go (75%) diff --git a/adapter/inbound.go b/adapter/inbound.go index 300f57e3d9..ca3e9e5900 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -5,6 +5,7 @@ import ( "net/netip" "github.com/sagernet/sing-box/common/process" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" M "github.com/sagernet/sing/common/metadata" ) @@ -25,6 +26,11 @@ type UDPInjectableInbound interface { PacketConnectionHandlerEx } +type InboundRegistry interface { + option.InboundOptionsRegistry + CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error) +} + type InboundContext struct { Inbound string InboundType string @@ -44,6 +50,7 @@ type InboundContext struct { // cache + // Deprecated: implement in rule action InboundDetour string LastInbound string OriginDestination M.Socksaddr diff --git a/adapter/inbound/adapter.go b/adapter/inbound/adapter.go new file mode 100644 index 0000000000..1426104afb --- /dev/null +++ b/adapter/inbound/adapter.go @@ -0,0 +1,21 @@ +package inbound + +type Adapter struct { + inboundType string + inboundTag string +} + +func NewAdapter(inboundType string, inboundTag string) Adapter { + return Adapter{ + inboundType: inboundType, + inboundTag: inboundTag, + } +} + +func (a *Adapter) Type() string { + return a.inboundType +} + +func (a *Adapter) Tag() string { + return a.inboundTag +} diff --git a/adapter/inbound/registry.go b/adapter/inbound/registry.go new file mode 100644 index 0000000000..9f678c90e7 --- /dev/null +++ b/adapter/inbound/registry.go @@ -0,0 +1,68 @@ +package inbound + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) { + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options))) + }) +} + +var _ adapter.InboundRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructors map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructors: make(map[string]constructorFunc), + } +} + +func (r *Registry) CreateOptions(outboundType string) (any, bool) { + r.access.Lock() + defer r.access.Unlock() + optionsConstructor, loaded := r.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) { + r.access.Lock() + defer r.access.Unlock() + constructor, loaded := r.constructors[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + r.access.Lock() + defer r.access.Unlock() + r.optionsType[outboundType] = optionsConstructor + r.constructors[outboundType] = constructor +} diff --git a/adapter/outbound.go b/adapter/outbound.go index 312cdf3ed0..df11ed6132 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -1,6 +1,10 @@ package adapter import ( + "context" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" N "github.com/sagernet/sing/common/network" ) @@ -13,3 +17,8 @@ type Outbound interface { Dependencies() []string N.Dialer } + +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/outbound/adapter.go b/adapter/outbound/adapter.go new file mode 100644 index 0000000000..481bb6197d --- /dev/null +++ b/adapter/outbound/adapter.go @@ -0,0 +1,45 @@ +package outbound + +import ( + "github.com/sagernet/sing-box/option" +) + +type Adapter struct { + protocol string + network []string + tag string + dependencies []string +} + +func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter { + return Adapter{ + protocol: protocol, + network: network, + tag: tag, + dependencies: dependencies, + } +} + +func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter { + var dependencies []string + if dialOptions.Detour != "" { + dependencies = []string{dialOptions.Detour} + } + return NewAdapter(protocol, network, tag, dependencies) +} + +func (a *Adapter) Type() string { + return a.protocol +} + +func (a *Adapter) Tag() string { + return a.tag +} + +func (a *Adapter) Network() []string { + return a.network +} + +func (a *Adapter) Dependencies() []string { + return a.dependencies +} diff --git a/outbound/default.go b/adapter/outbound/default.go similarity index 87% rename from outbound/default.go rename to adapter/outbound/default.go index a34ac97af1..78b9bfd8d6 100644 --- a/outbound/default.go +++ b/adapter/outbound/default.go @@ -9,8 +9,6 @@ import ( "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-dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" @@ -21,42 +19,6 @@ import ( N "github.com/sagernet/sing/common/network" ) -type myOutboundAdapter struct { - protocol string - network []string - router adapter.Router - logger log.ContextLogger - tag string - dependencies []string -} - -func (a *myOutboundAdapter) Type() string { - return a.protocol -} - -func (a *myOutboundAdapter) Tag() string { - return a.tag -} - -func (a *myOutboundAdapter) Network() []string { - return a.network -} - -func (a *myOutboundAdapter) Dependencies() []string { - return a.dependencies -} - -func (a *myOutboundAdapter) NewError(ctx context.Context, err error) { - NewError(a.logger, ctx, err) -} - -func withDialerDependency(options option.DialerOptions) []string { - if options.Detour != "" { - return []string{options.Detour} - } - return nil -} - func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error { ctx = adapter.WithContext(ctx, &metadata) var outConn net.Conn @@ -233,12 +195,3 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro } return bufio.CopyConn(ctx, conn, serverConn) } - -func NewError(logger log.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/adapter/outbound/registry.go b/adapter/outbound/registry.go new file mode 100644 index 0000000000..f25631cf14 --- /dev/null +++ b/adapter/outbound/registry.go @@ -0,0 +1,68 @@ +package outbound + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) { + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options))) + }) +} + +var _ adapter.OutboundRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructors map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructors: make(map[string]constructorFunc), + } +} + +func (r *Registry) CreateOptions(outboundType string) (any, bool) { + r.access.Lock() + defer r.access.Unlock() + optionsConstructor, loaded := r.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) { + r.access.Lock() + defer r.access.Unlock() + constructor, loaded := r.constructors[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + r.access.Lock() + defer r.access.Unlock() + r.optionsType[outboundType] = optionsConstructor + r.constructors[outboundType] = constructor +} diff --git a/box.go b/box.go index 716b1b093c..bc714e5a32 100644 --- a/box.go +++ b/box.go @@ -14,10 +14,9 @@ import ( "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/inbound" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/route" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -44,16 +43,37 @@ type Box struct { type Options struct { option.Options Context context.Context - PlatformInterface platform.Interface PlatformLogWriter log.PlatformWriter } +func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context { + if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || + service.FromContext[adapter.InboundRegistry](ctx) == nil { + ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry) + ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry) + } + if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil || + service.FromContext[adapter.OutboundRegistry](ctx) == nil { + ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry) + ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry) + } + return ctx +} + func New(options Options) (*Box, error) { createdAt := time.Now() ctx := options.Context if ctx == nil { ctx = context.Background() } + inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) + if inboundRegistry == nil { + return nil, E.New("missing inbound registry in context") + } + outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) + if outboundRegistry == nil { + return nil, E.New("missing outbound registry in context") + } ctx = service.ContextWithDefaultRegistry(ctx) ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) @@ -70,8 +90,9 @@ func New(options Options) (*Box, error) { if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { needV2RayAPI = true } + platformInterface := service.FromContext[platform.Interface](ctx) var defaultLogWriter io.Writer - if options.PlatformInterface != nil { + if platformInterface != nil { defaultLogWriter = io.Discard } logFactory, err := log.New(log.Options{ @@ -92,64 +113,70 @@ func New(options Options) (*Box, error) { common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP), options.Inbounds, - options.PlatformInterface, ) if err != nil { return nil, E.Cause(err, "parse route options") } - inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) - outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) for i, inboundOptions := range options.Inbounds { - var in adapter.Inbound + var currentInbound adapter.Inbound var tag string if inboundOptions.Tag != "" { tag = inboundOptions.Tag } else { tag = F.ToString(i) } - in, err = inbound.New( + currentInbound, err = inboundRegistry.CreateInbound( ctx, router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), tag, - inboundOptions, - options.PlatformInterface, + inboundOptions.Type, + inboundOptions.Options, ) if err != nil { return nil, E.Cause(err, "parse inbound[", i, "]") } - inbounds = append(inbounds, in) + inbounds = append(inbounds, currentInbound) } for i, outboundOptions := range options.Outbounds { - var out adapter.Outbound + var currentOutbound adapter.Outbound var tag string if outboundOptions.Tag != "" { tag = outboundOptions.Tag } else { tag = F.ToString(i) } - out, err = outbound.New( - ctx, + outboundCtx := ctx + if tag != "" { + // TODO: remove this + outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{ + Outbound: tag, + }) + } + currentOutbound, err = outboundRegistry.CreateOutbound( + outboundCtx, router, logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), tag, - outboundOptions) + outboundOptions.Type, + outboundOptions.Options, + ) if err != nil { return nil, E.Cause(err, "parse outbound[", i, "]") } - outbounds = append(outbounds, out) + outbounds = append(outbounds, currentOutbound) } err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { - out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"}) - common.Must(oErr) - outbounds = append(outbounds, out) - return out + defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{}) + common.Must(cErr) + outbounds = append(outbounds, defaultOutbound) + return defaultOutbound }) if err != nil { return nil, err } - if options.PlatformInterface != nil { - err = options.PlatformInterface.Initialize(ctx, router) + if platformInterface != nil { + err = platformInterface.Initialize(ctx, router) if err != nil { return nil, E.Cause(err, "initialize platform interface") } diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go index a2d4a00b35..dc7a830965 100644 --- a/cmd/sing-box/cmd.go +++ b/cmd/sing-box/cmd.go @@ -7,8 +7,9 @@ import ( "strconv" "time" + "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/experimental/deprecated" - _ "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" @@ -68,4 +69,5 @@ func preRun(cmd *cobra.Command, args []string) { configPaths = append(configPaths, "config.json") } globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())) + globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry()) } diff --git a/cmd/sing-box/cmd_format.go b/cmd/sing-box/cmd_format.go index fc47c5a8dd..9856c76336 100644 --- a/cmd/sing-box/cmd_format.go +++ b/cmd/sing-box/cmd_format.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "os" "path/filepath" @@ -38,7 +39,7 @@ func format() error { return err } for _, optionsEntry := range optionsList { - optionsEntry.options, err = badjson.Omitempty(optionsEntry.options) + optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options) if err != nil { return err } diff --git a/cmd/sing-box/cmd_merge.go b/cmd/sing-box/cmd_merge.go index 10dd38a1db..fa194ed342 100644 --- a/cmd/sing-box/cmd_merge.go +++ b/cmd/sing-box/cmd_merge.go @@ -68,29 +68,19 @@ func merge(outputPath string) error { } func mergePathResources(options *option.Options) error { - for index, inbound := range options.Inbounds { - rawOptions, err := inbound.RawOptions() - if err != nil { - return err - } - if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions { + for _, inbound := range options.Inbounds { + if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions { tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions())) } - options.Inbounds[index] = inbound } - for index, outbound := range options.Outbounds { - rawOptions, err := outbound.RawOptions() - if err != nil { - return err - } + for _, outbound := range options.Outbounds { switch outbound.Type { case C.TypeSSH: - outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions) + mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions)) } - if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions { + if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions { tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions())) } - options.Outbounds[index] = outbound } return nil } @@ -138,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun return options } -func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions { +func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) { if options.PrivateKeyPath != "" { if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil { options.PrivateKey = trimStringArray(strings.Split(string(content), "\n")) } } - return options } func trimStringArray(array []string) []string { diff --git a/cmd/sing-box/cmd_run.go b/cmd/sing-box/cmd_run.go index 6850cd19e3..f31db9dc82 100644 --- a/cmd/sing-box/cmd_run.go +++ b/cmd/sing-box/cmd_run.go @@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) { if err != nil { return nil, E.Cause(err, "read config at ", path) } - options, err := json.UnmarshalExtended[option.Options](configContent) + options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent) if err != nil { return nil, E.Cause(err, "decode config at ", path) } @@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) { } var mergedMessage json.RawMessage for _, options := range optionsList { - mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false) + mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false) if err != nil { return option.Options{}, E.Cause(err, "merge config at ", options.path) } } var mergedOptions option.Options - err = mergedOptions.UnmarshalJSON(mergedMessage) + err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage) if err != nil { return option.Options{}, E.Cause(err, "unmarshal merged config") } diff --git a/cmd/sing-box/cmd_tools.go b/cmd/sing-box/cmd_tools.go index 86b9302e09..f4c8736ec1 100644 --- a/cmd/sing-box/cmd_tools.go +++ b/cmd/sing-box/cmd_tools.go @@ -30,7 +30,7 @@ func createPreStartedClient() (*box.Box, error) { return nil, err } } - instance, err := box.New(box.Options{Options: options}) + instance, err := box.New(box.Options{Context: globalCtx, Options: options}) if err != nil { return nil, E.Cause(err, "create service") } diff --git a/common/dialer/default.go b/common/dialer/default.go index f4fd99fbca..02678961a7 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -125,7 +125,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi setMultiPathTCP(&dialer4) } if options.IsWireGuardListener { - for _, controlFn := range wgControlFns { + for _, controlFn := range WgControlFns { listener.Control = control.Append(listener.Control, controlFn) } } diff --git a/common/dialer/wireguard.go b/common/dialer/wireguard.go index 195133c650..fbd323d800 100644 --- a/common/dialer/wireguard.go +++ b/common/dialer/wireguard.go @@ -2,8 +2,12 @@ package dialer import ( "net" + + "github.com/sagernet/sing/common/control" ) type WireGuardListener interface { ListenPacketCompat(network, address string) (net.PacketConn, error) } + +var WgControlFns []control.Func diff --git a/common/dialer/wireguard_control.go b/common/dialer/wireguard_control.go deleted file mode 100644 index def86411a5..0000000000 --- a/common/dialer/wireguard_control.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build with_wireguard - -package dialer - -import ( - "github.com/sagernet/wireguard-go/conn" -) - -var _ WireGuardListener = (conn.Listener)(nil) - -var wgControlFns = conn.ControlFns diff --git a/common/dialer/wiregurad_stub.go b/common/dialer/wiregurad_stub.go deleted file mode 100644 index d30c223a95..0000000000 --- a/common/dialer/wiregurad_stub.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !with_wireguard - -package dialer - -import ( - "github.com/sagernet/sing/common/control" -) - -var wgControlFns []control.Func diff --git a/common/listener/listener.go b/common/listener/listener.go new file mode 100644 index 0000000000..b42b0434b2 --- /dev/null +++ b/common/listener/listener.go @@ -0,0 +1,136 @@ +package listener + +import ( + "context" + "net" + "sync/atomic" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/settings" + "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" +) + +type Listener struct { + ctx context.Context + logger logger.ContextLogger + network []string + listenOptions option.ListenOptions + connHandler adapter.ConnectionHandlerEx + packetHandler adapter.PacketHandlerEx + oobPacketHandler adapter.OOBPacketHandlerEx + threadUnsafePacketWriter bool + disablePacketOutput bool + setSystemProxy bool + systemProxySOCKS bool + + tcpListener net.Listener + systemProxy settings.SystemProxy + udpConn *net.UDPConn + udpAddr M.Socksaddr + packetOutbound chan *N.PacketBuffer + packetOutboundClosed chan struct{} + shutdown atomic.Bool +} + +type Options struct { + Context context.Context + Logger logger.ContextLogger + Network []string + Listen option.ListenOptions + ConnectionHandler adapter.ConnectionHandlerEx + PacketHandler adapter.PacketHandlerEx + OOBPacketHandler adapter.OOBPacketHandlerEx + ThreadUnsafePacketWriter bool + DisablePacketOutput bool + SetSystemProxy bool + SystemProxySOCKS bool +} + +func New( + options Options, +) *Listener { + return &Listener{ + ctx: options.Context, + logger: options.Logger, + network: options.Network, + listenOptions: options.Listen, + connHandler: options.ConnectionHandler, + packetHandler: options.PacketHandler, + oobPacketHandler: options.OOBPacketHandler, + threadUnsafePacketWriter: options.ThreadUnsafePacketWriter, + disablePacketOutput: options.DisablePacketOutput, + setSystemProxy: options.SetSystemProxy, + systemProxySOCKS: options.SystemProxySOCKS, + } +} + +func (l *Listener) Start() error { + if common.Contains(l.network, N.NetworkTCP) { + _, err := l.ListenTCP() + if err != nil { + return err + } + go l.loopTCPIn() + } + if common.Contains(l.network, N.NetworkUDP) { + _, err := l.ListenUDP() + if err != nil { + return err + } + l.packetOutboundClosed = make(chan struct{}) + l.packetOutbound = make(chan *N.PacketBuffer, 64) + go l.loopUDPIn() + if !l.disablePacketOutput { + go l.loopUDPOut() + } + } + if l.setSystemProxy { + listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port + var listenAddrString string + listenAddr := l.listenOptions.Listen.Build() + if listenAddr.IsUnspecified() { + listenAddrString = "127.0.0.1" + } else { + listenAddrString = listenAddr.String() + } + systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS) + if err != nil { + return E.Cause(err, "initialize system proxy") + } + err = systemProxy.Enable() + if err != nil { + return E.Cause(err, "set system proxy") + } + l.systemProxy = systemProxy + } + return nil +} + +func (l *Listener) Close() error { + l.shutdown.Store(true) + var err error + if l.systemProxy != nil && l.systemProxy.IsEnabled() { + err = l.systemProxy.Disable() + } + return E.Errors(err, common.Close( + l.tcpListener, + common.PtrOrNil(l.udpConn), + )) +} + +func (l *Listener) TCPListener() net.Listener { + return l.tcpListener +} + +func (l *Listener) UDPConn() *net.UDPConn { + return l.udpConn +} + +func (l *Listener) ListenOptions() option.ListenOptions { + return l.listenOptions +} diff --git a/inbound/default_tcp_go1.21.go b/common/listener/listener_go121.go similarity index 90% rename from inbound/default_tcp_go1.21.go rename to common/listener/listener_go121.go index 906818cbe2..5af1b05a30 100644 --- a/inbound/default_tcp_go1.21.go +++ b/common/listener/listener_go121.go @@ -1,6 +1,6 @@ //go:build go1.21 -package inbound +package listener import "net" diff --git a/common/listener/listener_go123.go b/common/listener/listener_go123.go new file mode 100644 index 0000000000..2e1f4cf43a --- /dev/null +++ b/common/listener/listener_go123.go @@ -0,0 +1,16 @@ +//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/inbound/default_tcp_nongo1.21.go b/common/listener/listener_nongo121.go similarity index 87% rename from inbound/default_tcp_nongo1.21.go rename to common/listener/listener_nongo121.go index d19adb1957..36073afe33 100644 --- a/inbound/default_tcp_nongo1.21.go +++ b/common/listener/listener_nongo121.go @@ -1,6 +1,6 @@ //go:build !go1.21 -package inbound +package listener import "net" diff --git a/common/listener/listener_nongo123.go b/common/listener/listener_nongo123.go new file mode 100644 index 0000000000..e1582981c2 --- /dev/null +++ b/common/listener/listener_nongo123.go @@ -0,0 +1,15 @@ +//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 new file mode 100644 index 0000000000..02cef3f04d --- /dev/null +++ b/common/listener/listener_tcp.go @@ -0,0 +1,85 @@ +package listener + +import ( + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/metacubex/tfo-go" +) + +func (l *Listener) ListenTCP() (net.Listener, error) { + var err error + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + var tcpListener net.Listener + var listenConfig net.ListenConfig + if l.listenOptions.TCPKeepAlive >= 0 { + keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) + if keepIdle == 0 { + keepIdle = C.TCPKeepAliveInitial + } + keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval) + if keepInterval == 0 { + keepInterval = C.TCPKeepAliveInterval + } + setKeepAliveConfig(&listenConfig, keepIdle, keepInterval) + } + if l.listenOptions.TCPMultiPath { + if !go121Available { + return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") + } + setMultiPathTCP(&listenConfig) + } + if l.listenOptions.TCPFastOpen { + var tfoConfig tfo.ListenConfig + tfoConfig.ListenConfig = listenConfig + tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } else { + tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } + if err == nil { + l.logger.Info("tcp server started at ", tcpListener.Addr()) + } + //nolint:staticcheck + if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader { + return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") + } + l.tcpListener = tcpListener + return tcpListener, err +} + +func (l *Listener) loopTCPIn() { + tcpListener := l.tcpListener + var metadata adapter.InboundContext + for { + conn, err := tcpListener.Accept() + if err != nil { + //nolint:staticcheck + if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() { + l.logger.Error(err) + continue + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.tcpListener.Close() + l.logger.Error("tcp listener closed: ", err) + continue + } + //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) + l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + go l.connHandler.NewConnectionEx(ctx, conn, metadata, nil) + } +} diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go new file mode 100644 index 0000000000..0c3220a7f1 --- /dev/null +++ b/common/listener/listener_udp.go @@ -0,0 +1,153 @@ +package listener + +import ( + "net" + "os" + + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func (l *Listener) ListenUDP() (net.PacketConn, error) { + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + var lc net.ListenConfig + var udpFragment bool + if l.listenOptions.UDPFragment != nil { + udpFragment = *l.listenOptions.UDPFragment + } else { + udpFragment = l.listenOptions.UDPFragmentDefault + } + if !udpFragment { + lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) + } + udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) + if err != nil { + return nil, err + } + l.udpConn = udpConn.(*net.UDPConn) + l.udpAddr = bindAddr + l.logger.Info("udp server started at ", udpConn.LocalAddr()) + return udpConn, err +} + +func (l *Listener) UDPAddr() M.Socksaddr { + return l.udpAddr +} + +func (l *Listener) PacketWriter() N.PacketWriter { + return (*packetWriter)(l) +} + +func (l *Listener) loopUDPIn() { + defer close(l.packetOutboundClosed) + var buffer *buf.Buffer + if !l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + defer buffer.Release() + buffer.IncRef() + defer buffer.DecRef() + } + if l.oobPacketHandler != nil { + oob := make([]byte, 1024) + for { + if l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + } else { + buffer.Reset() + } + n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) + if err != nil { + if l.threadUnsafePacketWriter { + buffer.Release() + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener closed: ", err) + return + } + buffer.Truncate(n) + l.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) + } + } else { + for { + if l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + } else { + buffer.Reset() + } + n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) + if err != nil { + if l.threadUnsafePacketWriter { + buffer.Release() + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener closed: ", err) + return + } + buffer.Truncate(n) + l.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) + } + } +} + +func (l *Listener) loopUDPOut() { + for { + select { + case packet := <-l.packetOutbound: + destination := packet.Destination.AddrPort() + _, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination) + packet.Buffer.Release() + N.PutPacketBuffer(packet) + if err != nil { + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener write back: ", destination, ": ", err) + return + } + continue + case <-l.packetOutboundClosed: + } + for { + select { + case packet := <-l.packetOutbound: + packet.Buffer.Release() + N.PutPacketBuffer(packet) + default: + return + } + } + } +} + +type packetWriter Listener + +func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + packet := N.NewPacketBuffer() + packet.Buffer = buffer + packet.Destination = destination + select { + case w.packetOutbound <- packet: + return nil + default: + buffer.Release() + N.PutPacketBuffer(packet) + if w.shutdown.Load() { + return os.ErrClosed + } + w.logger.Trace("dropped packet to ", destination) + return nil + } +} + +func (w *packetWriter) WriteIsThreadUnsafe() { +} diff --git a/experimental/clashapi/api_meta_group.go b/experimental/clashapi/api_meta_group.go index 396dee7f60..531311f4fe 100644 --- a/experimental/clashapi/api_meta_group.go +++ b/experimental/clashapi/api_meta_group.go @@ -10,7 +10,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/batch" "github.com/sagernet/sing/common/json/badjson" @@ -59,7 +59,7 @@ func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) { func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) - group, ok := proxy.(adapter.OutboundGroup) + outboundGroup, ok := proxy.(adapter.OutboundGroup) if !ok { render.Status(r, http.StatusNotFound) render.JSON(w, r, ErrNotFound) @@ -82,10 +82,10 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) defer cancel() var result map[string]uint16 - if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup { + if urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup { result, err = urlTestGroup.URLTest(ctx) } else { - outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound { + outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { itOutbound, _ := server.router.Outbound(it) return itOutbound })) @@ -95,7 +95,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) var resultAccess sync.Mutex for _, detour := range outbounds { tag := detour.Tag() - realTag := outbound.RealTag(detour) + realTag := group.RealTag(detour) if checked[realTag] { continue } diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index 7a807c1fae..4a9564ee86 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -11,7 +11,7 @@ import ( "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/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json/badjson" @@ -168,7 +168,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { } proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) - selector, ok := proxy.(*outbound.Selector) + selector, ok := proxy.(*group.Selector) if !ok { render.Status(r, http.StatusBadRequest) render.JSON(w, r, newError("Must be a Selector")) @@ -204,7 +204,7 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) delay, err := urltest.URLTest(ctx, url, proxy) defer func() { - realTag := outbound.RealTag(proxy) + realTag := group.RealTag(proxy) if err != nil { server.urlTestHistory.DeleteURLTestHistory(realTag) } else { diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go index 3a8d2a0749..0915f56b83 100644 --- a/experimental/libbox/command_group.go +++ b/experimental/libbox/command_group.go @@ -9,7 +9,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "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" @@ -118,14 +118,14 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { } var groups []OutboundGroup for _, iGroup := range iGroups { - var group OutboundGroup - group.Tag = iGroup.Tag() - group.Type = iGroup.Type() - _, group.Selectable = iGroup.(*outbound.Selector) - group.Selected = iGroup.Now() + 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(group.Tag); loaded { - group.IsExpand = isExpand + if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded { + outboundGroup.IsExpand = isExpand } } @@ -142,12 +142,12 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { item.URLTestTime = history.Time.Unix() item.URLTestDelay = int32(history.Delay) } - group.ItemList = append(group.ItemList, &item) + outboundGroup.ItemList = append(outboundGroup.ItemList, &item) } - if len(group.ItemList) < 2 { + if len(outboundGroup.ItemList) < 2 { continue } - groups = append(groups, group) + groups = append(groups, outboundGroup) } return varbin.Write(writer, binary.BigEndian, groups) } diff --git a/experimental/libbox/command_select.go b/experimental/libbox/command_select.go index e1e67e6068..f352005d11 100644 --- a/experimental/libbox/command_select.go +++ b/experimental/libbox/command_select.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "net" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/varbin" ) @@ -47,7 +47,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error { if !isLoaded { return writeError(conn, E.New("selector not found: ", groupTag)) } - selector, isSelector := outboundGroup.(*outbound.Selector) + selector, isSelector := outboundGroup.(*group.Selector) if !isSelector { return writeError(conn, E.New("outbound is not a selector: ", groupTag)) } diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go index 6feda3f8ef..c72ded8a3c 100644 --- a/experimental/libbox/command_urltest.go +++ b/experimental/libbox/command_urltest.go @@ -7,7 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "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" @@ -49,7 +49,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error { if !isOutboundGroup { return writeError(conn, E.New("outbound is not a group: ", groupTag)) } - urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest) + urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest) if isURLTest { go urlTest.CheckOutbounds() } else { diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index c8d0693acb..cc8057f49d 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/experimental/libbox/platform" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" @@ -17,10 +18,11 @@ import ( "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" ) -func parseConfig(configContent string) (option.Options, error) { - options, err := json.UnmarshalExtended[option.Options]([]byte(configContent)) +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") } @@ -28,16 +30,17 @@ func parseConfig(configContent string) (option.Options, error) { } func CheckConfig(configContent string) error { - options, err := parseConfig(configContent) + ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + options, err := parseConfig(ctx, configContent) if err != nil { return err } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) defer cancel() + ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil)) instance, err := box.New(box.Options{ - Context: ctx, - Options: options, - PlatformInterface: (*platformInterfaceStub)(nil), + Context: ctx, + Options: options, }) if err == nil { instance.Close() @@ -140,7 +143,7 @@ func (s *platformInterfaceStub) SendNotification(notification *platform.Notifica } func FormatConfig(configContent string) (*StringBox, error) { - options, err := parseConfig(configContent) + options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent) if err != nil { return nil, err } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 9bfa8a70d7..f6f56b6f6f 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -17,6 +17,7 @@ import ( "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/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" @@ -41,21 +42,22 @@ type BoxService struct { } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { - options, err := parseConfig(configContent) + ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + options, err := parseConfig(ctx, configContent) if err != nil { return nil, err } runtimeDebug.FreeOSMemory() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} + ctx = service.ContextWith[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ Context: ctx, Options: options, - PlatformInterface: platformWrapper, PlatformLogWriter: platformWrapper, }) if err != nil { diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index 950cbe4edc..184d525012 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -10,7 +10,6 @@ import ( "github.com/sagernet/sing-box/common/humanize" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/locale" - _ "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" ) diff --git a/go.mod b/go.mod index ad18132885..9b60b615f5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/sagernet/sing-box go 1.20 require ( - berty.tech/go-libtor v1.0.385 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 @@ -17,7 +16,6 @@ require ( github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.62 - github.com/ooni/go-libtor v1.1.8 github.com/oschwald/maxminddb-golang v1.12.0 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 a7ec3829c6..0c075a08b3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= -berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= @@ -11,7 +9,6 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= 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= @@ -88,8 +85,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= -github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= @@ -155,7 +150,6 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -177,7 +171,6 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= @@ -191,7 +184,6 @@ golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/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-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/inbound/builder.go b/inbound/builder.go deleted file mode 100644 index ddfd361dbd..0000000000 --- a/inbound/builder.go +++ /dev/null @@ -1,54 +0,0 @@ -package inbound - -import ( - "context" - - "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/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) { - if options.Type == "" { - return nil, E.New("missing inbound type") - } - switch options.Type { - case C.TypeTun: - return NewTun(ctx, router, logger, tag, options.TunOptions, platformInterface) - case C.TypeRedirect: - return NewRedirect(ctx, router, logger, tag, options.RedirectOptions), nil - case C.TypeTProxy: - return NewTProxy(ctx, router, logger, tag, options.TProxyOptions), nil - case C.TypeDirect: - return NewDirect(ctx, router, logger, tag, options.DirectOptions), nil - case C.TypeSOCKS: - return NewSocks(ctx, router, logger, tag, options.SocksOptions), nil - case C.TypeHTTP: - return NewHTTP(ctx, router, logger, tag, options.HTTPOptions) - case C.TypeMixed: - return NewMixed(ctx, router, logger, tag, options.MixedOptions), nil - case C.TypeShadowsocks: - return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions) - case C.TypeVMess: - return NewVMess(ctx, router, logger, tag, options.VMessOptions) - case C.TypeTrojan: - return NewTrojan(ctx, router, logger, tag, options.TrojanOptions) - case C.TypeNaive: - return NewNaive(ctx, router, logger, tag, options.NaiveOptions) - case C.TypeHysteria: - return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions) - case C.TypeShadowTLS: - return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions) - case C.TypeVLESS: - return NewVLESS(ctx, router, logger, tag, options.VLESSOptions) - case C.TypeTUIC: - return NewTUIC(ctx, router, logger, tag, options.TUICOptions) - case C.TypeHysteria2: - return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options) - default: - return nil, E.New("unknown inbound type: ", options.Type) - } -} diff --git a/inbound/default.go b/inbound/default.go deleted file mode 100644 index 880dd26f61..0000000000 --- a/inbound/default.go +++ /dev/null @@ -1,209 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/settings" - 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/atomic" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Inbound = (*myInboundAdapter)(nil) - -type myInboundAdapter struct { - protocol string - network []string - ctx context.Context - router adapter.ConnectionRouterEx - logger log.ContextLogger - tag string - listenOptions option.ListenOptions - connHandler adapter.ConnectionHandlerEx - packetHandler adapter.PacketHandlerEx - oobPacketHandler adapter.OOBPacketHandlerEx - packetUpstream any - - // http mixed - - setSystemProxy bool - systemProxy settings.SystemProxy - - // internal - - tcpListener net.Listener - udpConn *net.UDPConn - udpAddr M.Socksaddr - packetOutboundClosed chan struct{} - packetOutbound chan *myInboundPacket - - inShutdown atomic.Bool -} - -func (a *myInboundAdapter) Type() string { - return a.protocol -} - -func (a *myInboundAdapter) Tag() string { - return a.tag -} - -func (a *myInboundAdapter) Start() error { - var err error - if common.Contains(a.network, N.NetworkTCP) { - _, err = a.ListenTCP() - if err != nil { - return err - } - go a.loopTCPIn() - } - if common.Contains(a.network, N.NetworkUDP) { - _, err = a.ListenUDP() - if err != nil { - return err - } - a.packetOutboundClosed = make(chan struct{}) - a.packetOutbound = make(chan *myInboundPacket) - if a.oobPacketHandler != nil { - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { - go a.loopUDPOOBIn() - } else { - go a.loopUDPOOBInThreadSafe() - } - } else { - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { - go a.loopUDPIn() - } else { - go a.loopUDPInThreadSafe() - } - go a.loopUDPOut() - } - } - if a.setSystemProxy { - listenPort := M.SocksaddrFromNet(a.tcpListener.Addr()).Port - var listenAddrString string - listenAddr := a.listenOptions.Listen.Build() - if listenAddr.IsUnspecified() { - listenAddrString = "127.0.0.1" - } else { - listenAddrString = listenAddr.String() - } - var systemProxy settings.SystemProxy - systemProxy, err = settings.NewSystemProxy(a.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), a.protocol == C.TypeMixed) - if err != nil { - return E.Cause(err, "initialize system proxy") - } - err = systemProxy.Enable() - if err != nil { - return E.Cause(err, "set system proxy") - } - a.systemProxy = systemProxy - } - return nil -} - -func (a *myInboundAdapter) Close() error { - a.inShutdown.Store(true) - var err error - if a.systemProxy != nil && a.systemProxy.IsEnabled() { - err = a.systemProxy.Disable() - } - return E.Errors(err, common.Close( - a.tcpListener, - common.PtrOrNil(a.udpConn), - )) -} - -func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamHandler(metadata, a.newConnection, a.streamPacketConnection, a) -} - -func (a *myInboundAdapter) upstreamContextHandler() adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamContextHandler(a.newConnection, a.newPacketConnection, a) -} - -func (a *myInboundAdapter) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) streamPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) upstreamHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { - return adapter.NewUpstreamHandlerEx(metadata, a.newConnectionEx, a.streamPacketConnectionEx) -} - -func (a *myInboundAdapter) upstreamContextHandlerEx() adapter.UpstreamHandlerAdapterEx { - return adapter.NewUpstreamContextHandlerEx(a.newConnectionEx, a.newPacketConnectionEx) -} - -func (a *myInboundAdapter) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - a.router.RouteConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - ctx = log.ContextWithNewID(ctx) - a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) streamPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Source.IsValid() { - metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap() - } - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { - metadata.OriginDestination = M.SocksaddrFromNet(tcpConn.LocalAddr()).Unwrap() - } - return metadata -} - -// Deprecated: don't use -func (a *myInboundAdapter) newError(err error) { - a.logger.Error(err) -} - -// Deprecated: don't use -func (a *myInboundAdapter) NewError(ctx context.Context, err error) { - NewError(a.logger, ctx, err) -} - -// Deprecated: don't use -func NewError(logger log.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/inbound/default_tcp.go b/inbound/default_tcp.go deleted file mode 100644 index d38f96fe6e..0000000000 --- a/inbound/default_tcp.go +++ /dev/null @@ -1,84 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func (a *myInboundAdapter) ListenTCP() (net.Listener, error) { - var err error - bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) - var tcpListener net.Listener - var listenConfig net.ListenConfig - // TODO: Add an option to customize the keep alive period - listenConfig.KeepAlive = C.TCPKeepAliveInitial - listenConfig.Control = control.Append(listenConfig.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval)) - if a.listenOptions.TCPMultiPath { - if !go121Available { - return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") - } - setMultiPathTCP(&listenConfig) - } - if a.listenOptions.TCPFastOpen { - if !go120Available { - return nil, E.New("TCP Fast Open requires go1.20, please recompile your binary.") - } - tcpListener, err = listenTFO(listenConfig, a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } else { - tcpListener, err = listenConfig.Listen(a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } - if err == nil { - a.logger.Info("tcp server started at ", tcpListener.Addr()) - } - if a.listenOptions.ProxyProtocol || a.listenOptions.ProxyProtocolAcceptNoHeader { - return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") - } - a.tcpListener = tcpListener - return tcpListener, err -} - -func (a *myInboundAdapter) loopTCPIn() { - tcpListener := a.tcpListener - for { - conn, err := tcpListener.Accept() - if err != nil { - //goland:noinspection GoDeprecation - //nolint:staticcheck - if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() { - a.logger.Error(err) - continue - } - if a.inShutdown.Load() && E.IsClosed(err) { - return - } - a.tcpListener.Close() - a.logger.Error("serve error: ", err) - continue - } - go a.injectTCP(conn, adapter.InboundContext{}) - } -} - -func (a *myInboundAdapter) injectTCP(conn net.Conn, metadata adapter.InboundContext) { - ctx := log.ContextWithNewID(a.ctx) - metadata = a.createMetadata(conn, metadata) - a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - a.connHandler.NewConnectionEx(ctx, conn, metadata, nil) -} - -func (a *myInboundAdapter) routeTCP(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - metadata := a.createMetadata(conn, adapter.InboundContext{ - Source: source, - Destination: destination, - }) - a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - a.connHandler.NewConnectionEx(ctx, conn, metadata, onClose) -} diff --git a/inbound/default_tcp_go1.20.go b/inbound/default_tcp_go1.20.go deleted file mode 100644 index 23949b067e..0000000000 --- a/inbound/default_tcp_go1.20.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build go1.20 - -package inbound - -import ( - "context" - "net" - - "github.com/metacubex/tfo-go" -) - -const go120Available = true - -func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { - var tfoConfig tfo.ListenConfig - tfoConfig.ListenConfig = listenConfig - return tfoConfig.Listen(ctx, network, address) -} diff --git a/inbound/default_tcp_nongo1.20.go b/inbound/default_tcp_nongo1.20.go deleted file mode 100644 index e7a026bc85..0000000000 --- a/inbound/default_tcp_nongo1.20.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !go1.20 - -package inbound - -import ( - "context" - "net" - "os" -) - -const go120Available = false - -func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { - return nil, os.ErrInvalid -} diff --git a/inbound/default_udp.go b/inbound/default_udp.go deleted file mode 100644 index 6bcde79dae..0000000000 --- a/inbound/default_udp.go +++ /dev/null @@ -1,208 +0,0 @@ -package inbound - -import ( - "net" - "os" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func (a *myInboundAdapter) ListenUDP() (net.PacketConn, error) { - bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) - var lc net.ListenConfig - var udpFragment bool - if a.listenOptions.UDPFragment != nil { - udpFragment = *a.listenOptions.UDPFragment - } else { - udpFragment = a.listenOptions.UDPFragmentDefault - } - if !udpFragment { - lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) - } - udpConn, err := lc.ListenPacket(a.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) - if err != nil { - return nil, err - } - a.udpConn = udpConn.(*net.UDPConn) - a.udpAddr = bindAddr - a.logger.Info("udp server started at ", udpConn.LocalAddr()) - return udpConn, err -} - -func (a *myInboundAdapter) loopUDPIn() { - defer close(a.packetOutboundClosed) - buffer := buf.NewPacket() - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - for { - buffer.Reset() - n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return - } - buffer.Truncate(n) - a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPOOBIn() { - defer close(a.packetOutboundClosed) - buffer := buf.NewPacket() - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - oob := make([]byte, 1024) - for { - buffer.Reset() - n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - return - } - buffer.Truncate(n) - a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPInThreadSafe() { - defer close(a.packetOutboundClosed) - for { - buffer := buf.NewPacket() - n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - buffer.Release() - return - } - buffer.Truncate(n) - a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPOOBInThreadSafe() { - defer close(a.packetOutboundClosed) - oob := make([]byte, 1024) - for { - buffer := buf.NewPacket() - n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - buffer.Release() - return - } - buffer.Truncate(n) - a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPOut() { - for { - select { - case packet := <-a.packetOutbound: - err := a.writePacket(packet.buffer, packet.destination) - if err != nil && !E.IsClosed(err) { - a.logger.Error(E.New("write back udp: ", err)) - } - continue - case <-a.packetOutboundClosed: - } - for { - select { - case packet := <-a.packetOutbound: - packet.buffer.Release() - default: - return - } - } - } -} - -func (a *myInboundAdapter) packetConn() N.PacketConn { - return (*myInboundPacketAdapter)(a) -} - -func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - metadata.OriginDestination = a.udpAddr - return metadata -} - -func (a *myInboundAdapter) createPacketMetadataEx(source M.Socksaddr, destination M.Socksaddr) adapter.InboundContext { - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = source - metadata.Destination = destination - metadata.OriginDestination = a.udpAddr - return metadata -} - -func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - defer buffer.Release() - return common.Error(a.udpConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort())) -} - -type myInboundPacketAdapter myInboundAdapter - -func (s *myInboundPacketAdapter) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { - n, addr, err := s.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return M.Socksaddr{}, err - } - buffer.Truncate(n) - return M.SocksaddrFromNetIP(addr), nil -} - -func (s *myInboundPacketAdapter) WriteIsThreadUnsafe() { -} - -type myInboundPacket struct { - buffer *buf.Buffer - destination M.Socksaddr -} - -func (s *myInboundPacketAdapter) Upstream() any { - return s.udpConn -} - -func (s *myInboundPacketAdapter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - select { - case s.packetOutbound <- &myInboundPacket{buffer, destination}: - return nil - case <-s.packetOutboundClosed: - return os.ErrClosed - } -} - -func (s *myInboundPacketAdapter) Close() error { - return s.udpConn.Close() -} - -func (s *myInboundPacketAdapter) LocalAddr() net.Addr { - return s.udpConn.LocalAddr() -} - -func (s *myInboundPacketAdapter) SetDeadline(t time.Time) error { - return s.udpConn.SetDeadline(t) -} - -func (s *myInboundPacketAdapter) SetReadDeadline(t time.Time) error { - return s.udpConn.SetReadDeadline(t) -} - -func (s *myInboundPacketAdapter) SetWriteDeadline(t time.Time) error { - return s.udpConn.SetWriteDeadline(t) -} diff --git a/inbound/direct.go b/inbound/direct.go deleted file mode 100644 index b9a99f12cc..0000000000 --- a/inbound/direct.go +++ /dev/null @@ -1,111 +0,0 @@ -package inbound - -import ( - "context" - "net" - "time" - - "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/buf" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/udpnat2" -) - -var _ adapter.Inbound = (*Direct)(nil) - -type Direct struct { - myInboundAdapter - udpNat *udpnat.Service - overrideOption int - overrideDestination M.Socksaddr -} - -func NewDirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) *Direct { - options.UDPFragmentDefault = true - inbound := &Direct{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeDirect, - network: options.Network.Build(), - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - if options.OverrideAddress != "" && options.OverridePort != 0 { - inbound.overrideOption = 1 - inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverrideAddress != "" { - inbound.overrideOption = 2 - inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverridePort != 0 { - inbound.overrideOption = 3 - inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} - } - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - inbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false) - inbound.connHandler = inbound - inbound.packetHandler = inbound - return inbound -} - -func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - switch d.overrideOption { - case 1: - metadata.Destination = d.overrideDestination - case 2: - destination := d.overrideDestination - destination.Port = metadata.Destination.Port - metadata.Destination = destination - case 3: - metadata.Destination.Port = d.overrideDestination.Port - } - d.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return d.router.RouteConnection(ctx, conn, metadata) -} - -func (d *Direct) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - var destination M.Socksaddr - switch d.overrideOption { - case 1: - destination = d.overrideDestination - case 2: - destination = d.overrideDestination - destination.Port = source.Port - case 3: - destination = source - destination.Port = d.overrideDestination.Port - } - d.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil) -} - -func (d *Direct) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - d.newConnectionEx(ctx, conn, metadata, onClose) -} - -func (d *Direct) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - d.newPacketConnectionEx(ctx, conn, d.createPacketMetadataEx(source, destination), onClose) -} - -func (d *Direct) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { - return true, d.ctx, &directPacketWriter{d.packetConn(), source}, nil -} - -type directPacketWriter struct { - writer N.PacketWriter - source M.Socksaddr -} - -func (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { - return w.writer.WritePacket(buffer, w.source) -} diff --git a/inbound/http.go b/inbound/http.go deleted file mode 100644 index 20c8f6903d..0000000000 --- a/inbound/http.go +++ /dev/null @@ -1,119 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - "github.com/sagernet/sing-box/common/uot" - 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/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/http" -) - -var ( - _ adapter.Inbound = (*HTTP)(nil) - _ adapter.TCPInjectableInbound = (*HTTP)(nil) -) - -type HTTP struct { - myInboundAdapter - authenticator *auth.Authenticator - tlsConfig tls.ServerConfig -} - -func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (*HTTP, error) { - inbound := &HTTP{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHTTP, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - setSystemProxy: options.SetSystemProxy, - }, - authenticator: auth.NewAuthenticator(options.Users), - } - if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - inbound.tlsConfig = tlsConfig - } - inbound.connHandler = inbound - return inbound, nil -} - -func (h *HTTP) Start() error { - if h.tlsConfig != nil { - err := h.tlsConfig.Start() - if err != nil { - return E.Cause(err, "create TLS config") - } - } - return h.myInboundAdapter.Start() -} - -func (h *HTTP) Close() error { - return common.Close( - &h.myInboundAdapter, - h.tlsConfig, - ) -} - -func (h *HTTP) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.newConnection(ctx, conn, metadata, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} - -func (h *HTTP) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - var err error - if h.tlsConfig != nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) - if err != nil { - return err - } - } - return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) -} - -func (a *myInboundAdapter) upstreamUserHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { - return adapter.NewUpstreamHandlerEx(metadata, a.newUserConnection, a.streamUserPacketConnection) -} - -func (a *myInboundAdapter) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - user, loaded := auth.UserFromContext[string](ctx) - if !loaded { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - a.router.RouteConnectionEx(ctx, conn, metadata, onClose) - return - } - metadata.User = user - a.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - a.router.RouteConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - user, loaded := auth.UserFromContext[string](ctx) - if !loaded { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) - return - } - metadata.User = user - a.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) -} diff --git a/inbound/hysteria_stub.go b/inbound/hysteria_stub.go deleted file mode 100644 index fab86bb56f..0000000000 --- a/inbound/hysteria_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/inbound/mixed.go b/inbound/mixed.go deleted file mode 100644 index 81f6a43a09..0000000000 --- a/inbound/mixed.go +++ /dev/null @@ -1,70 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/http" - "github.com/sagernet/sing/protocol/socks" - "github.com/sagernet/sing/protocol/socks/socks4" - "github.com/sagernet/sing/protocol/socks/socks5" -) - -var ( - _ adapter.Inbound = (*Mixed)(nil) - _ adapter.TCPInjectableInbound = (*Mixed)(nil) -) - -type Mixed struct { - myInboundAdapter - authenticator *auth.Authenticator -} - -func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) *Mixed { - inbound := &Mixed{ - myInboundAdapter{ - protocol: C.TypeMixed, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - setSystemProxy: options.SetSystemProxy, - }, - auth.NewAuthenticator(options.Users), - } - inbound.connHandler = inbound - return inbound -} - -func (h *Mixed) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.newConnection(ctx, conn, metadata, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} - -func (h *Mixed) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - reader := std_bufio.NewReader(conn) - headerBytes, err := reader.Peek(1) - if err != nil { - return E.Cause(err, "peek first byte") - } - switch headerBytes[0] { - case socks4.Version, socks5.Version: - return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) - default: - return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) - } -} diff --git a/inbound/naive_quic.go b/inbound/naive_quic.go deleted file mode 100644 index 9f99bf278b..0000000000 --- a/inbound/naive_quic.go +++ /dev/null @@ -1,47 +0,0 @@ -//go:build with_quic - -package inbound - -import ( - "github.com/sagernet/quic-go" - "github.com/sagernet/quic-go/http3" - "github.com/sagernet/sing-quic" - E "github.com/sagernet/sing/common/exceptions" -) - -func (n *Naive) configureHTTP3Listener() error { - err := qtls.ConfigureHTTP3(n.tlsConfig) - if err != nil { - return err - } - - udpConn, err := n.ListenUDP() - if err != nil { - return err - } - - quicListener, err := qtls.ListenEarly(udpConn, n.tlsConfig, &quic.Config{ - MaxIncomingStreams: 1 << 60, - Allow0RTT: true, - }) - if err != nil { - udpConn.Close() - return err - } - - h3Server := &http3.Server{ - Port: int(n.listenOptions.ListenPort), - Handler: n, - } - - go func() { - sErr := h3Server.ServeListener(quicListener) - udpConn.Close() - if sErr != nil && !E.IsClosedOrCanceled(sErr) { - n.logger.Error("http3 server serve error: ", sErr) - } - }() - - n.h3Server = h3Server - return nil -} diff --git a/inbound/naive_quic_stub.go b/inbound/naive_quic_stub.go deleted file mode 100644 index 90f697e46d..0000000000 --- a/inbound/naive_quic_stub.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - C "github.com/sagernet/sing-box/constant" -) - -func (n *Naive) configureHTTP3Listener() error { - return C.ErrQUICNotIncluded -} diff --git a/inbound/redirect.go b/inbound/redirect.go deleted file mode 100644 index c4c6faf341..0000000000 --- a/inbound/redirect.go +++ /dev/null @@ -1,45 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/redir" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type Redirect struct { - myInboundAdapter -} - -func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) *Redirect { - redirect := &Redirect{ - myInboundAdapter{ - protocol: C.TypeRedirect, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - redirect.connHandler = redirect - return redirect -} - -func (r *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - destination, err := redir.GetOriginalDestination(conn) - if err != nil { - conn.Close() - r.logger.ErrorContext(ctx, "process connection from ", conn.RemoteAddr(), ": get redirect destination: ", err) - return - } - metadata.Destination = M.SocksaddrFromNetIP(destination) - r.newConnectionEx(ctx, conn, metadata, onClose) -} diff --git a/inbound/shadowsocks.go b/inbound/shadowsocks.go deleted file mode 100644 index 3fff231d8f..0000000000 --- a/inbound/shadowsocks.go +++ /dev/null @@ -1,114 +0,0 @@ -package inbound - -import ( - "context" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/mux" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-shadowsocks" - "github.com/sagernet/sing-shadowsocks/shadowaead" - "github.com/sagernet/sing-shadowsocks/shadowaead_2022" - "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" - "github.com/sagernet/sing/common/ntp" -) - -func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { - if len(options.Users) > 0 && len(options.Destinations) > 0 { - return nil, E.New("users and destinations options must not be combined") - } - if len(options.Users) > 0 { - return newShadowsocksMulti(ctx, router, logger, tag, options) - } else if len(options.Destinations) > 0 { - return newShadowsocksRelay(ctx, router, logger, tag, options) - } else { - return newShadowsocks(ctx, router, logger, tag, options) - } -} - -var ( - _ adapter.Inbound = (*Shadowsocks)(nil) - _ adapter.TCPInjectableInbound = (*Shadowsocks)(nil) -) - -type Shadowsocks struct { - myInboundAdapter - service shadowsocks.Service -} - -func newShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Shadowsocks, error) { - inbound := &Shadowsocks{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - - inbound.connHandler = inbound - inbound.packetHandler = inbound - var err error - inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) - if err != nil { - return nil, err - } - - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - switch { - case options.Method == shadowsocks.MethodNone: - inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) - case common.Contains(shadowaead.List, options.Method): - inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) - case common.Contains(shadowaead_2022.List, options.Method): - inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx)) - default: - err = E.New("unsupported method: ", options.Method) - } - inbound.packetUpstream = inbound.service - return inbound, err -} - -func (h *Shadowsocks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} - -func (h *Shadowsocks) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) - if err != nil { - h.logger.Error(E.Cause(err, "process packet from ", source)) - } -} - -func (h *Shadowsocks) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) -} - -func (h *Shadowsocks) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) -} diff --git a/inbound/socks.go b/inbound/socks.go deleted file mode 100644 index 04b0a77dc7..0000000000 --- a/inbound/socks.go +++ /dev/null @@ -1,52 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/socks" -) - -var ( - _ adapter.Inbound = (*Socks)(nil) - _ adapter.TCPInjectableInbound = (*Socks)(nil) -) - -type Socks struct { - myInboundAdapter - authenticator *auth.Authenticator -} - -func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) *Socks { - inbound := &Socks{ - myInboundAdapter{ - protocol: C.TypeSOCKS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - auth.NewAuthenticator(options.Users), - } - inbound.connHandler = inbound - return inbound -} - -func (h *Socks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} diff --git a/inbound/tuic_stub.go b/inbound/tuic_stub.go deleted file mode 100644 index bfd402ab45..0000000000 --- a/inbound/tuic_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/include/quic.go b/include/quic.go index 1bcc0fbc90..980b458113 100644 --- a/include/quic.go +++ b/include/quic.go @@ -3,6 +3,24 @@ package include import ( + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/hysteria" + "github.com/sagernet/sing-box/protocol/hysteria2" + _ "github.com/sagernet/sing-box/protocol/naive/quic" + "github.com/sagernet/sing-box/protocol/tuic" _ "github.com/sagernet/sing-box/transport/v2rayquic" _ "github.com/sagernet/sing-dns/quic" ) + +func registerQUICInbounds(registry *inbound.Registry) { + hysteria.RegisterInbound(registry) + tuic.RegisterInbound(registry) + hysteria2.RegisterInbound(registry) +} + +func registerQUICOutbounds(registry *outbound.Registry) { + hysteria.RegisterOutbound(registry) + tuic.RegisterOutbound(registry) + hysteria2.RegisterOutbound(registry) +} diff --git a/include/quic_stub.go b/include/quic_stub.go index 43aa58d996..66c0859038 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -4,11 +4,18 @@ package include import ( "context" + "io" + "net/http" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "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-box/protocol/naive" "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/logger" @@ -29,3 +36,30 @@ func init() { }, ) } + +func registerQUICInbounds(registry *inbound.Registry) { + inbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + inbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + 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) { + return nil, C.ErrQUICNotIncluded + } +} + +func registerQUICOutbounds(registry *outbound.Registry) { + outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) + outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) + outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) +} diff --git a/include/registry.go b/include/registry.go new file mode 100644 index 0000000000..03fb33f245 --- /dev/null +++ b/include/registry.go @@ -0,0 +1,95 @@ +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "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" + "github.com/sagernet/sing-box/protocol/block" + "github.com/sagernet/sing-box/protocol/direct" + "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" + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing-box/protocol/redirect" + "github.com/sagernet/sing-box/protocol/shadowsocks" + "github.com/sagernet/sing-box/protocol/shadowtls" + "github.com/sagernet/sing-box/protocol/socks" + "github.com/sagernet/sing-box/protocol/ssh" + "github.com/sagernet/sing-box/protocol/tor" + "github.com/sagernet/sing-box/protocol/trojan" + "github.com/sagernet/sing-box/protocol/tun" + "github.com/sagernet/sing-box/protocol/vless" + "github.com/sagernet/sing-box/protocol/vmess" + E "github.com/sagernet/sing/common/exceptions" +) + +func InboundRegistry() *inbound.Registry { + registry := inbound.NewRegistry() + + tun.RegisterInbound(registry) + redirect.RegisterRedirect(registry) + redirect.RegisterTProxy(registry) + direct.RegisterInbound(registry) + + socks.RegisterInbound(registry) + http.RegisterInbound(registry) + mixed.RegisterInbound(registry) + + shadowsocks.RegisterInbound(registry) + vmess.RegisterInbound(registry) + trojan.RegisterInbound(registry) + naive.RegisterInbound(registry) + shadowtls.RegisterInbound(registry) + vless.RegisterInbound(registry) + + registerQUICInbounds(registry) + registerStubForRemovedInbounds(registry) + + return registry +} + +func OutboundRegistry() *outbound.Registry { + registry := outbound.NewRegistry() + + direct.RegisterOutbound(registry) + + block.RegisterOutbound(registry) + dns.RegisterOutbound(registry) + + group.RegisterSelector(registry) + group.RegisterURLTest(registry) + + socks.RegisterOutbound(registry) + http.RegisterOutbound(registry) + shadowsocks.RegisterOutbound(registry) + vmess.RegisterOutbound(registry) + trojan.RegisterOutbound(registry) + tor.RegisterOutbound(registry) + ssh.RegisterOutbound(registry) + shadowtls.RegisterOutbound(registry) + vless.RegisterOutbound(registry) + + registerQUICOutbounds(registry) + registerWireGuardOutbound(registry) + registerStubForRemovedOutbounds(registry) + + return registry +} + +func registerStubForRemovedInbounds(registry *inbound.Registry) { + inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { + return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") + }) +} + +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") + }) +} diff --git a/include/wireguard.go b/include/wireguard.go new file mode 100644 index 0000000000..dfc3a242a5 --- /dev/null +++ b/include/wireguard.go @@ -0,0 +1,12 @@ +//go:build with_wireguard + +package include + +import ( + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/wireguard" +) + +func registerWireGuardOutbound(registry *outbound.Registry) { + wireguard.RegisterOutbound(registry) +} diff --git a/include/wireguard_stub.go b/include/wireguard_stub.go new file mode 100644 index 0000000000..a9e84522bb --- /dev/null +++ b/include/wireguard_stub.go @@ -0,0 +1,20 @@ +//go:build !with_wireguard + +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 registerWireGuardOutbound(registry *outbound.Registry) { + outbound.Register[option.WireGuardOutboundOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { + return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) + }) +} diff --git a/option/inbound.go b/option/inbound.go index d38799048f..651d02846c 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -1,100 +1,49 @@ package option import ( + "context" "time" - C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/service" ) +type InboundOptionsRegistry interface { + CreateOptions(outboundType string) (any, bool) +} + type _Inbound struct { - Type string `json:"type"` - Tag string `json:"tag,omitempty"` - TunOptions TunInboundOptions `json:"-"` - RedirectOptions RedirectInboundOptions `json:"-"` - TProxyOptions TProxyInboundOptions `json:"-"` - DirectOptions DirectInboundOptions `json:"-"` - SocksOptions SocksInboundOptions `json:"-"` - HTTPOptions HTTPMixedInboundOptions `json:"-"` - MixedOptions HTTPMixedInboundOptions `json:"-"` - ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` - VMessOptions VMessInboundOptions `json:"-"` - TrojanOptions TrojanInboundOptions `json:"-"` - NaiveOptions NaiveInboundOptions `json:"-"` - HysteriaOptions HysteriaInboundOptions `json:"-"` - ShadowTLSOptions ShadowTLSInboundOptions `json:"-"` - VLESSOptions VLESSInboundOptions `json:"-"` - TUICOptions TUICInboundOptions `json:"-"` - Hysteria2Options Hysteria2InboundOptions `json:"-"` + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` } type Inbound _Inbound -func (h *Inbound) RawOptions() (any, error) { - var rawOptionsPtr any - switch h.Type { - case C.TypeTun: - rawOptionsPtr = &h.TunOptions - case C.TypeRedirect: - rawOptionsPtr = &h.RedirectOptions - case C.TypeTProxy: - rawOptionsPtr = &h.TProxyOptions - case C.TypeDirect: - rawOptionsPtr = &h.DirectOptions - case C.TypeSOCKS: - rawOptionsPtr = &h.SocksOptions - case C.TypeHTTP: - rawOptionsPtr = &h.HTTPOptions - case C.TypeMixed: - rawOptionsPtr = &h.MixedOptions - case C.TypeShadowsocks: - rawOptionsPtr = &h.ShadowsocksOptions - case C.TypeVMess: - rawOptionsPtr = &h.VMessOptions - case C.TypeTrojan: - rawOptionsPtr = &h.TrojanOptions - case C.TypeNaive: - rawOptionsPtr = &h.NaiveOptions - case C.TypeHysteria: - rawOptionsPtr = &h.HysteriaOptions - case C.TypeShadowTLS: - rawOptionsPtr = &h.ShadowTLSOptions - case C.TypeVLESS: - rawOptionsPtr = &h.VLESSOptions - case C.TypeTUIC: - rawOptionsPtr = &h.TUICOptions - case C.TypeHysteria2: - rawOptionsPtr = &h.Hysteria2Options - case "": - return nil, E.New("missing inbound type") - default: - return nil, E.New("unknown inbound type: ", h.Type) - } - return rawOptionsPtr, nil +func (h *Inbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Inbound)(h), h.Options) } -func (h Inbound) MarshalJSON() ([]byte, error) { - rawOptions, err := h.RawOptions() - if err != nil { - return nil, err - } - return MarshallObjects((_Inbound)(h), rawOptions) -} - -func (h *Inbound) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Inbound)(h)) +func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.Unmarshal(content, (*_Inbound)(h)) if err != nil { return err } - rawOptions, err := h.RawOptions() - if err != nil { - return err + registry := service.FromContext[InboundOptionsRegistry](ctx) + if registry == nil { + return E.New("missing inbound options registry in context") } - err = UnmarshallExcluded(bytes, (*_Inbound)(h), rawOptions) + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown inbound type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Inbound)(h), options) if err != nil { return err } + h.Options = options return nil } @@ -105,19 +54,24 @@ type InboundOptions struct { SniffTimeout 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 { - Listen *ListenAddress `json:"listen,omitempty"` - ListenPort uint16 `json:"listen_port,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - ProxyProtocol bool `json:"proxy_protocol,omitempty"` - ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"` - Detour string `json:"detour,omitempty"` + Listen *ListenAddress `json:"listen,omitempty"` + ListenPort uint16 `json:"listen_port,omitempty"` + TCPKeepAlive Duration `json:"tcp_keep_alive,omitempty"` + TCPKeepAliveInterval Duration `json:"tcp_keep_alive_interval,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + + // Deprecated: removed + ProxyProtocol bool `json:"proxy_protocol,omitempty"` + // Deprecated: removed + ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"` InboundOptions } diff --git a/option/json.go b/option/json.go deleted file mode 100644 index 775141d5aa..0000000000 --- a/option/json.go +++ /dev/null @@ -1,71 +0,0 @@ -package option - -import ( - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/json" - "github.com/sagernet/sing/common/json/badjson" -) - -func ToMap(v any) (*badjson.JSONObject, error) { - inputContent, err := json.Marshal(v) - if err != nil { - return nil, err - } - var content badjson.JSONObject - err = content.UnmarshalJSON(inputContent) - if err != nil { - return nil, err - } - return &content, nil -} - -func MergeObjects(objects ...any) (*badjson.JSONObject, error) { - var content badjson.JSONObject - for _, object := range objects { - objectMap, err := ToMap(object) - if err != nil { - return nil, err - } - content.PutAll(objectMap) - } - return &content, nil -} - -func MarshallObjects(objects ...any) ([]byte, error) { - objects = common.FilterNotNil(objects) - if len(objects) == 1 { - return json.Marshal(objects[0]) - } - content, err := MergeObjects(objects...) - if err != nil { - return nil, err - } - return content.MarshalJSON() -} - -func UnmarshallExcluded(inputContent []byte, parentObject any, object any) error { - parentContent, err := ToMap(parentObject) - if err != nil { - return err - } - var content badjson.JSONObject - err = content.UnmarshalJSON(inputContent) - if err != nil { - return err - } - for _, key := range parentContent.Keys() { - content.Remove(key) - } - if object == nil { - if content.IsEmpty() { - return nil - } - return E.New("unexpected key: ", content.Keys()[0]) - } - inputContent, err = content.MarshalJSON() - if err != nil { - return err - } - return json.UnmarshalDisallowUnknownFields(inputContent, object) -} diff --git a/option/config.go b/option/options.go similarity index 84% rename from option/config.go rename to option/options.go index 3f5d7602c0..13a16c08b5 100644 --- a/option/config.go +++ b/option/options.go @@ -2,6 +2,7 @@ package option import ( "bytes" + "context" "github.com/sagernet/sing/common/json" ) @@ -20,8 +21,8 @@ type _Options struct { type Options _Options -func (o *Options) UnmarshalJSON(content []byte) error { - decoder := json.NewDecoder(bytes.NewReader(content)) +func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) error { + decoder := json.NewDecoderContext(ctx, bytes.NewReader(content)) decoder.DisallowUnknownFields() err := decoder.Decode((*_Options)(o)) if err != nil { @@ -38,3 +39,5 @@ type LogOptions struct { Timestamp bool `json:"timestamp,omitempty"` DisableColor bool `json:"-"` } + +type StubOptions struct{} diff --git a/option/outbound.go b/option/outbound.go index 6c943cd9af..00a20aa577 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -1,104 +1,49 @@ package option import ( - C "github.com/sagernet/sing-box/constant" + "context" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/service" ) +type OutboundOptionsRegistry interface { + CreateOptions(outboundType string) (any, bool) +} + type _Outbound struct { - Type string `json:"type"` - Tag string `json:"tag,omitempty"` - DirectOptions DirectOutboundOptions `json:"-"` - SocksOptions SocksOutboundOptions `json:"-"` - HTTPOptions HTTPOutboundOptions `json:"-"` - ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"` - VMessOptions VMessOutboundOptions `json:"-"` - TrojanOptions TrojanOutboundOptions `json:"-"` - WireGuardOptions WireGuardOutboundOptions `json:"-"` - HysteriaOptions HysteriaOutboundOptions `json:"-"` - TorOptions TorOutboundOptions `json:"-"` - SSHOptions SSHOutboundOptions `json:"-"` - ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"` - ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"` - VLESSOptions VLESSOutboundOptions `json:"-"` - TUICOptions TUICOutboundOptions `json:"-"` - Hysteria2Options Hysteria2OutboundOptions `json:"-"` - SelectorOptions SelectorOutboundOptions `json:"-"` - URLTestOptions URLTestOutboundOptions `json:"-"` + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` } type Outbound _Outbound -func (h *Outbound) RawOptions() (any, error) { - var rawOptionsPtr any - switch h.Type { - case C.TypeDirect: - rawOptionsPtr = &h.DirectOptions - case C.TypeBlock, C.TypeDNS: - rawOptionsPtr = nil - case C.TypeSOCKS: - rawOptionsPtr = &h.SocksOptions - case C.TypeHTTP: - rawOptionsPtr = &h.HTTPOptions - case C.TypeShadowsocks: - rawOptionsPtr = &h.ShadowsocksOptions - case C.TypeVMess: - rawOptionsPtr = &h.VMessOptions - case C.TypeTrojan: - rawOptionsPtr = &h.TrojanOptions - case C.TypeWireGuard: - rawOptionsPtr = &h.WireGuardOptions - case C.TypeHysteria: - rawOptionsPtr = &h.HysteriaOptions - case C.TypeTor: - rawOptionsPtr = &h.TorOptions - case C.TypeSSH: - rawOptionsPtr = &h.SSHOptions - case C.TypeShadowTLS: - rawOptionsPtr = &h.ShadowTLSOptions - case C.TypeShadowsocksR: - rawOptionsPtr = &h.ShadowsocksROptions - case C.TypeVLESS: - rawOptionsPtr = &h.VLESSOptions - case C.TypeTUIC: - rawOptionsPtr = &h.TUICOptions - case C.TypeHysteria2: - rawOptionsPtr = &h.Hysteria2Options - case C.TypeSelector: - rawOptionsPtr = &h.SelectorOptions - case C.TypeURLTest: - rawOptionsPtr = &h.URLTestOptions - case "": - return nil, E.New("missing outbound type") - default: - return nil, E.New("unknown outbound type: ", h.Type) - } - return rawOptionsPtr, nil +func (h *Outbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Outbound)(h), h.Options) } -func (h *Outbound) MarshalJSON() ([]byte, error) { - rawOptions, err := h.RawOptions() - if err != nil { - return nil, err - } - return MarshallObjects((*_Outbound)(h), rawOptions) -} - -func (h *Outbound) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Outbound)(h)) +func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.Unmarshal(content, (*_Outbound)(h)) if err != nil { return err } - rawOptions, err := h.RawOptions() - if err != nil { - return err + registry := service.FromContext[OutboundOptionsRegistry](ctx) + if registry == nil { + return E.New("missing outbound options registry in context") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown outbound type: ", h.Type) } - err = UnmarshallExcluded(bytes, (*_Outbound)(h), rawOptions) + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Outbound)(h), options) if err != nil { return err } + h.Options = options return nil } diff --git a/option/rule.go b/option/rule.go index 0b11cbdda1..07e6ddbec4 100644 --- a/option/rule.go +++ b/option/rule.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _Rule struct { @@ -28,7 +29,7 @@ func (r Rule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_Rule)(r), v) + return badjson.MarshallObjects((_Rule)(r), v) } func (r *Rule) UnmarshalJSON(bytes []byte) error { @@ -46,7 +47,7 @@ func (r *Rule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_Rule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_Rule)(r), v) if err != nil { return err } @@ -109,7 +110,7 @@ type DefaultRule struct { } func (r *DefaultRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r.RawDefaultRule, r.RuleAction) + return badjson.MarshallObjects(r.RawDefaultRule, r.RuleAction) } func (r *DefaultRule) UnmarshalJSON(data []byte) error { @@ -117,7 +118,7 @@ func (r *DefaultRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) + return badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) } func (r *DefaultRule) IsValid() bool { @@ -139,7 +140,7 @@ type LogicalRule struct { } func (r *LogicalRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r._LogicalRule, r.RuleAction) + return badjson.MarshallObjects(r._LogicalRule, r.RuleAction) } func (r *LogicalRule) UnmarshalJSON(data []byte) error { @@ -147,7 +148,7 @@ func (r *LogicalRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) + return badjson.UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) } func (r *LogicalRule) IsValid() bool { diff --git a/option/rule_action.go b/option/rule_action.go index f446d81d5f..e752a2beaf 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _RuleAction struct { @@ -36,9 +37,9 @@ func (r RuleAction) MarshalJSON() ([]byte, error) { return nil, E.New("unknown rule action: " + r.Action) } if v == nil { - return MarshallObjects((_RuleAction)(r)) + return badjson.MarshallObjects((_RuleAction)(r)) } - return MarshallObjects((_RuleAction)(r), v) + return badjson.MarshallObjects((_RuleAction)(r), v) } func (r *RuleAction) UnmarshalJSON(data []byte) error { @@ -68,7 +69,7 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { // check unknown fields return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{}) } - return UnmarshallExcluded(data, (*_RuleAction)(r), v) + return badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v) } type _DNSRuleAction struct { @@ -95,9 +96,9 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) { return nil, E.New("unknown DNS rule action: " + r.Action) } if v == nil { - return MarshallObjects((_DNSRuleAction)(r)) + return badjson.MarshallObjects((_DNSRuleAction)(r)) } - return MarshallObjects((_DNSRuleAction)(r), v) + return badjson.MarshallObjects((_DNSRuleAction)(r), v) } func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { @@ -121,7 +122,7 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { // check unknown fields return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{}) } - return UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) + return badjson.UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) } type RouteActionOptions struct { diff --git a/option/rule_dns.go b/option/rule_dns.go index b328c45c01..8c4b6ab83b 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _DNSRule struct { @@ -28,7 +29,7 @@ func (r DNSRule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_DNSRule)(r), v) + return badjson.MarshallObjects((_DNSRule)(r), v) } func (r *DNSRule) UnmarshalJSON(bytes []byte) error { @@ -46,7 +47,7 @@ func (r *DNSRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_DNSRule)(r), v) if err != nil { return err } @@ -111,7 +112,7 @@ type DefaultDNSRule struct { } func (r *DefaultDNSRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) + return badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) } func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { @@ -119,7 +120,7 @@ func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) } func (r *DefaultDNSRule) IsValid() bool { @@ -141,7 +142,7 @@ type LogicalDNSRule struct { } func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) + return badjson.MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) } func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { @@ -149,7 +150,7 @@ func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) } func (r *LogicalDNSRule) IsValid() bool { diff --git a/option/rule_set.go b/option/rule_set.go index e0f10bf180..3bf9aa5c48 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -9,6 +9,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" "go4.org/netipx" ) @@ -37,7 +38,7 @@ func (r RuleSet) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule-set type: " + r.Type) } - return MarshallObjects((_RuleSet)(r), v) + return badjson.MarshallObjects((_RuleSet)(r), v) } func (r *RuleSet) UnmarshalJSON(bytes []byte) error { @@ -71,7 +72,7 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { } else { r.Format = "" } - err = UnmarshallExcluded(bytes, (*_RuleSet)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v) if err != nil { return err } @@ -107,7 +108,7 @@ func (r HeadlessRule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_HeadlessRule)(r), v) + return badjson.MarshallObjects((_HeadlessRule)(r), v) } func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error { @@ -125,7 +126,7 @@ func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v) if err != nil { return err } @@ -203,7 +204,7 @@ func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule-set version: ", r.Version) } - return MarshallObjects((_PlainRuleSetCompat)(r), v) + return badjson.MarshallObjects((_PlainRuleSetCompat)(r), v) } func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { @@ -220,7 +221,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule-set version: ", r.Version) } - err = UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v) if err != nil { return err } diff --git a/option/simple.go b/option/simple.go index ba9d6bf190..78171ce4c2 100644 --- a/option/simple.go +++ b/option/simple.go @@ -14,7 +14,7 @@ type HTTPMixedInboundOptions struct { InboundTLSOptionsContainer } -type SocksOutboundOptions struct { +type SOCKSOutboundOptions struct { DialerOptions ServerOptions Version string `json:"version,omitempty"` diff --git a/option/tls_acme.go b/option/tls_acme.go index 17d515e20b..9c2e081fa8 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type InboundACMEOptions struct { @@ -45,7 +46,7 @@ func (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown provider type: " + o.Provider) } - return MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v) + return badjson.MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v) } func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { @@ -62,7 +63,7 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown provider type: " + o.Provider) } - err = UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v) + err = badjson.UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v) if err != nil { return err } diff --git a/option/v2ray_transport.go b/option/v2ray_transport.go index fcd81f9462..f87b175d39 100644 --- a/option/v2ray_transport.go +++ b/option/v2ray_transport.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _V2RayTransportOptions struct { @@ -35,7 +36,7 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown transport type: " + o.Type) } - return MarshallObjects((_V2RayTransportOptions)(o), v) + return badjson.MarshallObjects((_V2RayTransportOptions)(o), v) } func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { @@ -58,7 +59,7 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown transport type: " + o.Type) } - err = UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v) + err = badjson.UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v) if err != nil { return err } diff --git a/outbound/block.go b/outbound/block.go deleted file mode 100644 index b6ccefe2be..0000000000 --- a/outbound/block.go +++ /dev/null @@ -1,54 +0,0 @@ -package outbound - -import ( - "context" - "io" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Outbound = (*Block)(nil) - -type Block struct { - myOutboundAdapter -} - -func NewBlock(logger log.ContextLogger, tag string) *Block { - return &Block{ - myOutboundAdapter{ - protocol: C.TypeBlock, - network: []string{N.NetworkTCP, N.NetworkUDP}, - logger: logger, - tag: tag, - }, - } -} - -func (h *Block) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - h.logger.InfoContext(ctx, "blocked connection to ", destination) - return nil, io.EOF -} - -func (h *Block) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - h.logger.InfoContext(ctx, "blocked packet connection to ", destination) - return nil, io.EOF -} - -// Deprecated -func (h *Block) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - conn.Close() - h.logger.InfoContext(ctx, "blocked connection to ", metadata.Destination) - return nil -} - -// Deprecated -func (h *Block) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - conn.Close() - h.logger.InfoContext(ctx, "blocked packet connection to ", metadata.Destination) - return nil -} diff --git a/outbound/builder.go b/outbound/builder.go deleted file mode 100644 index d895b56dda..0000000000 --- a/outbound/builder.go +++ /dev/null @@ -1,65 +0,0 @@ -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Outbound) (adapter.Outbound, error) { - if tag != "" { - ctx = adapter.WithContext(ctx, &adapter.InboundContext{ - Outbound: tag, - }) - } - if options.Type == "" { - return nil, E.New("missing outbound type") - } - ctx = ContextWithTag(ctx, tag) - switch options.Type { - case C.TypeDirect: - return NewDirect(router, logger, tag, options.DirectOptions) - case C.TypeBlock: - return NewBlock(logger, tag), nil - case C.TypeDNS: - return NewDNS(router, tag), nil - case C.TypeSOCKS: - return NewSocks(router, logger, tag, options.SocksOptions) - case C.TypeHTTP: - return NewHTTP(ctx, router, logger, tag, options.HTTPOptions) - case C.TypeShadowsocks: - return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions) - case C.TypeVMess: - return NewVMess(ctx, router, logger, tag, options.VMessOptions) - case C.TypeTrojan: - return NewTrojan(ctx, router, logger, tag, options.TrojanOptions) - case C.TypeWireGuard: - return NewWireGuard(ctx, router, logger, tag, options.WireGuardOptions) - case C.TypeHysteria: - return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions) - case C.TypeTor: - return NewTor(ctx, router, logger, tag, options.TorOptions) - case C.TypeSSH: - return NewSSH(ctx, router, logger, tag, options.SSHOptions) - case C.TypeShadowTLS: - return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions) - case C.TypeShadowsocksR: - return NewShadowsocksR(ctx, router, logger, tag, options.ShadowsocksROptions) - case C.TypeVLESS: - return NewVLESS(ctx, router, logger, tag, options.VLESSOptions) - case C.TypeTUIC: - return NewTUIC(ctx, router, logger, tag, options.TUICOptions) - case C.TypeHysteria2: - return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options) - case C.TypeSelector: - return NewSelector(ctx, router, logger, tag, options.SelectorOptions) - case C.TypeURLTest: - return NewURLTest(ctx, router, logger, tag, options.URLTestOptions) - default: - return nil, E.New("unknown outbound type: ", options.Type) - } -} diff --git a/outbound/hysteria_stub.go b/outbound/hysteria_stub.go deleted file mode 100644 index 84db5305bc..0000000000 --- a/outbound/hysteria_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !with_quic - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/outbound/lookback.go b/outbound/lookback.go deleted file mode 100644 index aeb7451dba..0000000000 --- a/outbound/lookback.go +++ /dev/null @@ -1,14 +0,0 @@ -package outbound - -import "context" - -type outboundTagKey struct{} - -func ContextWithTag(ctx context.Context, outboundTag string) context.Context { - return context.WithValue(ctx, outboundTagKey{}, outboundTag) -} - -func TagFromContext(ctx context.Context) (string, bool) { - value, loaded := ctx.Value(outboundTagKey{}).(string) - return value, loaded -} diff --git a/outbound/shadowsocksr.go b/outbound/shadowsocksr.go deleted file mode 100644 index 615a71e4e4..0000000000 --- a/outbound/shadowsocksr.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build with_shadowsocksr - -package outbound - -import ( - "context" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -var _ int = "ShadowsocksR is deprecated and removed in sing-box 1.6.0" - -func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { - return nil, os.ErrInvalid -} diff --git a/outbound/shadowsocksr_stub.go b/outbound/shadowsocksr_stub.go deleted file mode 100644 index 94971da0bc..0000000000 --- a/outbound/shadowsocksr_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_shadowsocksr - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewShadowsocksR(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") -} diff --git a/outbound/tor_embed.go b/outbound/tor_embed.go deleted file mode 100644 index d80b49ae66..0000000000 --- a/outbound/tor_embed.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_embedded_tor && !(android || ios) - -package outbound - -import ( - "berty.tech/go-libtor" - "github.com/cretz/bine/tor" -) - -func newConfig() tor.StartConf { - return tor.StartConf{ - ProcessCreator: libtor.Creator, - UseEmbeddedControlConn: true, - } -} diff --git a/outbound/tor_embed_mobile.go b/outbound/tor_embed_mobile.go deleted file mode 100644 index 0900d8c938..0000000000 --- a/outbound/tor_embed_mobile.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_embedded_tor && (android || ios) - -package outbound - -import ( - "github.com/cretz/bine/tor" - "github.com/ooni/go-libtor" -) - -func newConfig() tor.StartConf { - return tor.StartConf{ - ProcessCreator: libtor.Creator, - UseEmbeddedControlConn: true, - } -} diff --git a/outbound/tor_external.go b/outbound/tor_external.go deleted file mode 100644 index 6bce95d1d9..0000000000 --- a/outbound/tor_external.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !with_embedded_tor - -package outbound - -import "github.com/cretz/bine/tor" - -func newConfig() tor.StartConf { - return tor.StartConf{} -} diff --git a/outbound/tuic_stub.go b/outbound/tuic_stub.go deleted file mode 100644 index a6372c9ee3..0000000000 --- a/outbound/tuic_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_quic - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/outbound/wireguard_stub.go b/outbound/wireguard_stub.go deleted file mode 100644 index 3a8b0e87a5..0000000000 --- a/outbound/wireguard_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_wireguard - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { - return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) -} diff --git a/protocol/block/outbound.go b/protocol/block/outbound.go new file mode 100644 index 0000000000..75bc7797e0 --- /dev/null +++ b/protocol/block/outbound.go @@ -0,0 +1,42 @@ +package block + +import ( + "context" + "net" + "syscall" + + "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" + "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.StubOptions](registry, C.TypeBlock, New) +} + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger +} + +func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, _ option.StubOptions) (adapter.Outbound, error) { + return &Outbound{ + Adapter: outbound.NewAdapter(C.TypeBlock, []string{N.NetworkTCP, N.NetworkUDP}, tag, nil), + logger: logger, + }, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + h.logger.InfoContext(ctx, "blocked connection to ", destination) + return nil, syscall.EPERM +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + h.logger.InfoContext(ctx, "blocked packet connection to ", destination) + return nil, syscall.EPERM +} diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go new file mode 100644 index 0000000000..568a72cbd0 --- /dev/null +++ b/protocol/direct/inbound.go @@ -0,0 +1,139 @@ +package direct + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat2" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.DirectInboundOptions](registry, C.TypeDirect, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + udpNat *udpnat.Service + overrideOption int + overrideDestination M.Socksaddr +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) (adapter.Inbound, error) { + options.UDPFragmentDefault = true + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeDirect, tag), + ctx: ctx, + router: router, + logger: logger, + } + if options.OverrideAddress != "" && options.OverridePort != 0 { + inbound.overrideOption = 1 + inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) + } else if options.OverrideAddress != "" { + inbound.overrideOption = 2 + inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) + } else if options.OverridePort != 0 { + inbound.overrideOption = 3 + inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + inbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false) + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + }) + return inbound, nil +} + +func (i *Inbound) Start() error { + return i.listener.Start() +} + +func (i *Inbound) Close() error { + return i.listener.Close() +} + +func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + switch i.overrideOption { + case 1: + metadata.Destination = i.overrideDestination + case 2: + destination := i.overrideDestination + destination.Port = metadata.Destination.Port + metadata.Destination = destination + case 3: + metadata.Destination.Port = i.overrideDestination.Port + } + i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + return i.router.RouteConnection(ctx, conn, metadata) +} + +func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + var destination M.Socksaddr + switch i.overrideOption { + case 1: + destination = i.overrideDestination + case 2: + destination = i.overrideDestination + destination.Port = source.Port + case 3: + destination = source + destination.Port = i.overrideDestination.Port + } + i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil) +} + +func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + i.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + i.logger.InfoContext(ctx, "inbound packet connection from ", source) + i.logger.InfoContext(ctx, "inbound packet connection to ", destination) + var metadata adapter.InboundContext + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = i.listener.UDPAddr() + i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (i *Inbound) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + return true, log.ContextWithNewID(i.ctx), &directPacketWriter{i.listener.PacketWriter(), source}, nil +} + +type directPacketWriter struct { + writer N.PacketWriter + source M.Socksaddr +} + +func (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { + return w.writer.WritePacket(buffer, w.source) +} diff --git a/outbound/direct_loopback_detect.go b/protocol/direct/loopback_detect.go similarity index 99% rename from outbound/direct_loopback_detect.go rename to protocol/direct/loopback_detect.go index 1469b9d017..5a184e692a 100644 --- a/outbound/direct_loopback_detect.go +++ b/protocol/direct/loopback_detect.go @@ -1,4 +1,4 @@ -package outbound +package direct import ( "net" diff --git a/outbound/direct.go b/protocol/direct/outbound.go similarity index 76% rename from outbound/direct.go rename to protocol/direct/outbound.go index 415a72f370..32c1ed8fda 100644 --- a/outbound/direct.go +++ b/protocol/direct/outbound.go @@ -1,4 +1,4 @@ -package outbound +package direct import ( "context" @@ -7,6 +7,7 @@ import ( "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/log" @@ -14,17 +15,20 @@ import ( "github.com/sagernet/sing-dns" "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" ) -var ( - _ adapter.Outbound = (*Direct)(nil) - _ N.ParallelDialer = (*Direct)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.DirectOutboundOptions](registry, C.TypeDirect, NewOutbound) +} + +var _ N.ParallelDialer = (*Outbound)(nil) -type Direct struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer domainStrategy dns.DomainStrategy fallbackDelay time.Duration @@ -33,21 +37,15 @@ type Direct struct { // loopBack *loopBackDetector } -func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &Direct{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeDirect, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.DialerOptions), + logger: logger, domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), dialer: outboundDialer, @@ -69,9 +67,9 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti return outbound, nil } -func (h *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch h.overrideOption { case 1: @@ -98,9 +96,9 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M. return h.dialer.DialContext(ctx, network, destination) } -func (h *Direct) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { +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 + metadata.Outbound = h.Tag() metadata.Destination = destination switch h.overrideOption { case 1, 2: @@ -125,9 +123,9 @@ func (h *Direct) DialParallel(ctx context.Context, network string, destination M return N.DialParallel(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.fallbackDelay) } -func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination originDestination := destination switch h.overrideOption { @@ -156,14 +154,14 @@ func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net return conn, nil } -/*func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +/*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { return E.New("reject loopback connection to ", metadata.Destination) } return NewConnection(ctx, h, conn, metadata) } -func (h *Direct) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { if h.loopBack.CheckPacketConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { return E.New("reject loopback packet connection to ", metadata.Destination) } diff --git a/outbound/dns.go b/protocol/dns/handle.go similarity index 83% rename from outbound/dns.go rename to protocol/dns/handle.go index d9c92f19ec..23ed1c0c79 100644 --- a/outbound/dns.go +++ b/protocol/dns/handle.go @@ -1,11 +1,9 @@ -package outbound +package dns import ( "context" "encoding/binary" "net" - "os" - "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" @@ -21,44 +19,6 @@ import ( mDNS "github.com/miekg/dns" ) -var _ adapter.Outbound = (*DNS)(nil) - -type DNS struct { - myOutboundAdapter -} - -func NewDNS(router adapter.Router, tag string) *DNS { - return &DNS{ - myOutboundAdapter{ - protocol: C.TypeDNS, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - tag: tag, - }, - } -} - -func (d *DNS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - return nil, os.ErrInvalid -} - -func (d *DNS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, os.ErrInvalid -} - -// Deprecated -func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - metadata.Destination = M.Socksaddr{} - defer conn.Close() - for { - conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) - err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) - if err != nil { - return err - } - } -} - func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net.Conn, metadata adapter.InboundContext) error { var queryLength uint16 err := binary.Read(conn, binary.BigEndian, &queryLength) @@ -100,11 +60,6 @@ func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net return nil } -// Deprecated -func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) -} - func NewDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn diff --git a/protocol/dns/outbound.go b/protocol/dns/outbound.go new file mode 100644 index 0000000000..7ce9fde2f2 --- /dev/null +++ b/protocol/dns/outbound.go @@ -0,0 +1,61 @@ +package dns + +import ( + "context" + "net" + "os" + "time" + + "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" + "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.StubOptions](registry, C.TypeDNS, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + router adapter.Router + logger logger.ContextLogger +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) { + return &Outbound{ + Adapter: outbound.NewAdapter(C.TypeDNS, []string{N.NetworkTCP, N.NetworkUDP}, tag, nil), + router: router, + logger: logger, + }, nil +} + +func (d *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return nil, os.ErrInvalid +} + +func (d *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +// Deprecated +func (d *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + metadata.Destination = M.Socksaddr{} + defer conn.Close() + for { + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) + if err != nil { + return err + } + } +} + +// Deprecated +func (d *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) +} diff --git a/outbound/selector.go b/protocol/group/selector.go similarity index 82% rename from outbound/selector.go rename to protocol/group/selector.go index 64e6a2f936..8ade27a943 100644 --- a/outbound/selector.go +++ b/protocol/group/selector.go @@ -1,28 +1,33 @@ -package outbound +package group import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/interrupt" 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" "github.com/sagernet/sing/service" ) -var ( - _ adapter.Outbound = (*Selector)(nil) - _ adapter.OutboundGroup = (*Selector)(nil) -) +func RegisterSelector(registry *outbound.Registry) { + outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector) +} + +var _ adapter.OutboundGroup = (*Selector)(nil) type Selector struct { - myOutboundAdapter + outbound.Adapter ctx context.Context + router adapter.Router + logger logger.ContextLogger tags []string defaultTag string outbounds map[string]adapter.Outbound @@ -31,16 +36,12 @@ type Selector struct { interruptExternalConnections bool } -func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (*Selector, error) { +func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (adapter.Outbound, error) { outbound := &Selector{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSelector, - router: router, - logger: logger, - tag: tag, - dependencies: options.Outbounds, - }, + Adapter: outbound.NewAdapter(C.TypeSelector, nil, tag, options.Outbounds), ctx: ctx, + router: router, + logger: logger, tags: options.Outbounds, defaultTag: options.Default, outbounds: make(map[string]adapter.Outbound), @@ -69,10 +70,10 @@ func (s *Selector) Start() error { s.outbounds[tag] = detour } - if s.tag != "" { + if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { - selected := cacheFile.LoadSelected(s.tag) + selected := cacheFile.LoadSelected(s.Tag()) if selected != "" { detour, loaded := s.outbounds[selected] if loaded { @@ -117,10 +118,10 @@ func (s *Selector) SelectOutbound(tag string) bool { return true } s.selected = detour - if s.tag != "" { + if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { - err := cacheFile.StoreSelected(s.tag, tag) + err := cacheFile.StoreSelected(s.Tag(), tag) if err != nil { s.logger.Error("store selected: ", err) } @@ -153,7 +154,7 @@ func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata ad if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { return legacyHandler.NewConnection(ctx, conn, metadata) } else { - return NewConnection(ctx, s.selected, conn, metadata) + return outbound.NewConnection(ctx, s.selected, conn, metadata) } } @@ -164,7 +165,7 @@ func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, m if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { return legacyHandler.NewPacketConnection(ctx, conn, metadata) } else { - return NewPacketConnection(ctx, s.selected, conn, metadata) + return outbound.NewPacketConnection(ctx, s.selected, conn, metadata) } } diff --git a/outbound/urltest.go b/protocol/group/urltest.go similarity index 94% rename from outbound/urltest.go rename to protocol/group/urltest.go index 564a0ddc3a..ccdf809d9b 100644 --- a/outbound/urltest.go +++ b/protocol/group/urltest.go @@ -1,4 +1,4 @@ -package outbound +package group import ( "context" @@ -7,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/interrupt" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" @@ -22,15 +23,20 @@ import ( "github.com/sagernet/sing/service/pause" ) +func RegisterURLTest(registry *outbound.Registry) { + outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest) +} + var ( - _ adapter.Outbound = (*URLTest)(nil) _ adapter.OutboundGroup = (*URLTest)(nil) _ adapter.InterfaceUpdateListener = (*URLTest)(nil) ) type URLTest struct { - myOutboundAdapter + outbound.Adapter ctx context.Context + router adapter.Router + logger log.ContextLogger tags []string link string interval time.Duration @@ -40,17 +46,12 @@ type URLTest struct { interruptExternalConnections bool } -func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) { +func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (adapter.Outbound, error) { outbound := &URLTest{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeURLTest, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - logger: logger, - tag: tag, - dependencies: options.Outbounds, - }, + Adapter: outbound.NewAdapter(C.TypeURLTest, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.Outbounds), ctx: ctx, + router: router, + logger: logger, tags: options.Outbounds, link: options.URL, interval: time.Duration(options.Interval), @@ -171,14 +172,14 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne // Deprecated func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return NewConnection(ctx, s, conn, metadata) + return outbound.NewConnection(ctx, s, conn, metadata) } // TODO // Deprecated func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return NewPacketConnection(ctx, s, conn, metadata) + return outbound.NewPacketConnection(ctx, s, conn, metadata) } func (s *URLTest) InterfaceUpdated() { diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go new file mode 100644 index 0000000000..87ed9a1089 --- /dev/null +++ b/protocol/http/inbound.go @@ -0,0 +1,122 @@ +package http + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + 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/auth" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/http" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeHTTP, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator + tlsConfig tls.ServerConfig +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHTTP, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + SetSystemProxy: options.SetSystemProxy, + SystemProxySOCKS: false, + }) + return inbound, nil +} + +func (h *Inbound) Start() error { + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return common.Close( + &h.listener, + h.tlsConfig, + ) +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + var err error + if h.tlsConfig != nil { + conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) + if err != nil { + return err + } + } + return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/outbound/http.go b/protocol/http/outbound.go similarity index 56% rename from outbound/http.go rename to protocol/http/outbound.go index 6f15afb5dc..4c930591b1 100644 --- a/outbound/http.go +++ b/protocol/http/outbound.go @@ -1,4 +1,4 @@ -package outbound +package http import ( "context" @@ -6,25 +6,30 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/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/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" sHTTP "github.com/sagernet/sing/protocol/http" ) -var _ adapter.Outbound = (*HTTP)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.HTTPOutboundOptions](registry, C.TypeHTTP, NewOutbound) +} -type HTTP struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *sHTTP.Client } -func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err @@ -33,16 +38,10 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - return &HTTP{ - myOutboundAdapter{ - protocol: C.TypeHTTP, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - sHTTP.NewClient(sHTTP.Options{ + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHTTP, []string{N.NetworkTCP}, tag, options.DialerOptions), + logger: logger, + client: sHTTP.NewClient(sHTTP.Options{ Dialer: detour, Server: options.ServerOptions.Build(), Username: options.Username, @@ -53,14 +52,14 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge }, nil } -func (h *HTTP) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination h.logger.InfoContext(ctx, "outbound connection to ", destination) return h.client.DialContext(ctx, network, destination) } -func (h *HTTP) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/inbound/hysteria.go b/protocol/hysteria/inbound.go similarity index 71% rename from inbound/hysteria.go rename to protocol/hysteria/inbound.go index 427d7df430..f1f6da3ef6 100644 --- a/inbound/hysteria.go +++ b/protocol/hysteria/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package hysteria import ( "context" @@ -8,7 +6,9 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/humanize" + "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" @@ -20,16 +20,21 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Inbound = (*Hysteria)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, NewInbound) +} -type Hysteria struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig service *hysteria.Service[int] userNameList []string } -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -38,16 +43,15 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - inbound := &Hysteria{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHysteria, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHysteria, tag), + router: router, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var sendBps, receiveBps uint64 @@ -113,9 +117,12 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL return inbound, nil } -func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -127,9 +134,13 @@ func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata ad return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -141,23 +152,23 @@ func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, m return h.router.RoutePacketConnection(ctx, conn, metadata) } -func (h *Hysteria) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.service.Start(packetConn) } -func (h *Hysteria) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, common.PtrOrNil(h.service), ) diff --git a/outbound/hysteria.go b/protocol/hysteria/outbound.go similarity index 76% rename from outbound/hysteria.go rename to protocol/hysteria/outbound.go index f3c30739cf..6df32eaf71 100644 --- a/outbound/hysteria.go +++ b/protocol/hysteria/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package hysteria import ( "context" @@ -8,31 +6,39 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/tuic" "github.com/sagernet/sing-quic/hysteria" "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" ) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, NewOutbound) +} + var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) + _ adapter.Outbound = (*tuic.Outbound)(nil) + _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil) ) -type Hysteria struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *hysteria.Client } -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (*Hysteria, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -88,20 +94,14 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - return &Hysteria{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeHysteria, - network: networkList, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - client: client, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria, networkList, tag, options.DialerOptions), + logger: logger, + client: client, }, nil } -func (h *Hysteria) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +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) @@ -117,15 +117,15 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination } } -func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx, destination) } -func (h *Hysteria) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } -func (h *Hysteria) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/inbound/hysteria2.go b/protocol/hysteria2/inbound.go similarity index 74% rename from inbound/hysteria2.go rename to protocol/hysteria2/inbound.go index c13e9531f5..cbf811097f 100644 --- a/inbound/hysteria2.go +++ b/protocol/hysteria2/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package hysteria2 import ( "context" @@ -11,6 +9,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "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" @@ -23,16 +23,21 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Inbound = (*Hysteria2)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, NewInbound) +} -type Hysteria2 struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig service *hysteria2.Service[int] userNameList []string } -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (*Hysteria2, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -76,16 +81,15 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) } } - inbound := &Hysteria2{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHysteria2, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHysteria2, tag), + router: router, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var udpTimeout time.Duration @@ -124,9 +128,12 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context return inbound, nil } -func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -138,9 +145,13 @@ func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata a return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Hysteria2) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -152,23 +163,23 @@ func (h *Hysteria2) newPacketConnection(ctx context.Context, conn N.PacketConn, return h.router.RoutePacketConnection(ctx, conn, metadata) } -func (h *Hysteria2) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.service.Start(packetConn) } -func (h *Hysteria2) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, common.PtrOrNil(h.service), ) diff --git a/outbound/hysteria2.go b/protocol/hysteria2/outbound.go similarity index 69% rename from outbound/hysteria2.go rename to protocol/hysteria2/outbound.go index 5e46f6a866..5ebc6c91f3 100644 --- a/outbound/hysteria2.go +++ b/protocol/hysteria2/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package hysteria2 import ( "context" @@ -8,31 +6,39 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/tuic" "github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing-quic/hysteria2" "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" ) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, NewOutbound) +} + var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) + _ adapter.Outbound = (*tuic.Outbound)(nil) + _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil) ) -type Hysteria2 struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *hysteria2.Client } -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (*Hysteria2, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -74,20 +80,14 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context if err != nil { return nil, err } - return &Hysteria2{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeHysteria2, - network: networkList, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - client: client, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria2, networkList, tag, options.DialerOptions), + logger: logger, + client: client, }, nil } -func (h *Hysteria2) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +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) @@ -103,15 +103,15 @@ func (h *Hysteria2) DialContext(ctx context.Context, network string, destination } } -func (h *Hysteria2) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx) } -func (h *Hysteria2) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } -func (h *Hysteria2) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go new file mode 100644 index 0000000000..e57b791fec --- /dev/null +++ b/protocol/mixed/inbound.go @@ -0,0 +1,109 @@ +package mixed + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/http" + "github.com/sagernet/sing/protocol/socks" + "github.com/sagernet/sing/protocol/socks/socks4" + "github.com/sagernet/sing/protocol/socks/socks5" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeMixed, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeMixed, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + SetSystemProxy: options.SetSystemProxy, + SystemProxySOCKS: true, + }) + return inbound, nil +} + +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + reader := std_bufio.NewReader(conn) + headerBytes, err := reader.Peek(1) + if err != nil { + return E.Cause(err, "peek first byte") + } + switch headerBytes[0] { + case socks4.Version, socks5.Version: + return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) + default: + return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) + } +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go new file mode 100644 index 0000000000..1a561aeaf2 --- /dev/null +++ b/protocol/naive/inbound.go @@ -0,0 +1,248 @@ +package naive + +import ( + "context" + "io" + "math/rand" + "net" + "net/http" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/v2rayhttp" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + 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" +) + +var ConfigureHTTP3ListenerFunc func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + network []string + networkIsDefault bool + authenticator *auth.Authenticator + tlsConfig tls.ServerConfig + httpServer *http.Server + h3Server io.Closer +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeNaive, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), + networkIsDefault: options.Network == "", + network: options.Network.Build(), + authenticator: auth.NewAuthenticator(options.Users), + } + if common.Contains(inbound.network, N.NetworkUDP) { + if options.TLS == nil || !options.TLS.Enabled { + return nil, E.New("TLS is required for QUIC server") + } + } + if len(options.Users) == 0 { + return nil, E.New("missing users") + } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + return inbound, nil +} + +func (n *Inbound) Start() error { + var tlsConfig *tls.STDConfig + if n.tlsConfig != nil { + err := n.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + tlsConfig, err = n.tlsConfig.Config() + if err != nil { + return err + } + } + if common.Contains(n.network, N.NetworkTCP) { + tcpListener, err := n.listener.ListenTCP() + if err != nil { + return err + } + n.httpServer = &http.Server{ + Handler: n, + TLSConfig: tlsConfig, + BaseContext: func(listener net.Listener) context.Context { + return n.ctx + }, + } + go func() { + var sErr error + if tlsConfig != nil { + sErr = n.httpServer.ServeTLS(tcpListener, "", "") + } else { + sErr = n.httpServer.Serve(tcpListener) + } + if sErr != nil && !E.IsClosedOrCanceled(sErr) { + n.logger.Error("http server serve error: ", sErr) + } + }() + } + + if common.Contains(n.network, N.NetworkUDP) { + http3Server, err := ConfigureHTTP3ListenerFunc(n.listener, n, n.tlsConfig, n.logger) + if err == nil { + n.h3Server = http3Server + } else if len(n.network) > 1 { + n.logger.Warn(E.Cause(err, "naive http3 disabled")) + } else { + return err + } + } + + return nil +} + +func (n *Inbound) Close() error { + return common.Close( + &n.listener, + common.PtrOrNil(n.httpServer), + n.h3Server, + n.tlsConfig, + ) +} + +func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + ctx := log.ContextWithNewID(request.Context()) + if request.Method != "CONNECT" { + rejectHTTP(writer, http.StatusBadRequest) + n.badRequest(ctx, request, E.New("not CONNECT request")) + return + } else if request.Header.Get("Padding") == "" { + rejectHTTP(writer, http.StatusBadRequest) + n.badRequest(ctx, request, E.New("missing naive padding")) + return + } + userName, password, authOk := sHttp.ParseBasicAuth(request.Header.Get("Proxy-Authorization")) + if authOk { + authOk = n.authenticator.Verify(userName, password) + } + if !authOk { + rejectHTTP(writer, http.StatusProxyAuthRequired) + n.badRequest(ctx, request, E.New("authorization failed")) + return + } + writer.Header().Set("Padding", generateNaivePaddingHeader()) + writer.WriteHeader(http.StatusOK) + writer.(http.Flusher).Flush() + + hostPort := request.URL.Host + if hostPort == "" { + hostPort = request.Host + } + source := sHttp.SourceAddress(request) + destination := M.ParseSocksaddr(hostPort) + + if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { + conn, _, err := hijacker.Hijack() + if err != nil { + n.badRequest(ctx, request, E.New("hijack failed")) + return + } + n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination) + } else { + n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) + } +} + +func (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) { + if userName != "" { + n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) + n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) + } else { + n.logger.InfoContext(ctx, "inbound connection from ", source) + n.logger.InfoContext(ctx, "inbound connection to ", destination) + } + var metadata adapter.InboundContext + metadata.Inbound = n.Tag() + metadata.InboundType = n.Type() + metadata.InboundDetour = n.listener.ListenOptions().Detour + metadata.InboundOptions = n.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + metadata.User = userName + if !waitForClose { + n.router.RouteConnectionEx(ctx, conn, metadata, nil) + } else { + done := make(chan struct{}) + wrapper := v2rayhttp.NewHTTP2Wrapper(conn) + n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) + <-done + wrapper.CloseWrapper() + } +} + +func (n *Inbound) badRequest(ctx context.Context, request *http.Request, err error) { + n.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) +} + +func rejectHTTP(writer http.ResponseWriter, statusCode int) { + hijacker, ok := writer.(http.Hijacker) + if !ok { + writer.WriteHeader(statusCode) + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + writer.WriteHeader(statusCode) + return + } + if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { + tcpConn.SetLinger(0) + } + conn.Close() +} + +func generateNaivePaddingHeader() string { + paddingLen := rand.Intn(32) + 30 + padding := make([]byte, paddingLen) + bits := rand.Uint64() + for i := 0; i < 16; i++ { + // Codes that won't be Huffman coded. + padding[i] = "!#$()+<>?@[]^`{}"[bits&15] + bits >>= 4 + } + for i := 16; i < paddingLen; i++ { + padding[i] = '~' + } + return string(padding) +} diff --git a/inbound/naive.go b/protocol/naive/inbound_conn.go similarity index 58% rename from inbound/naive.go rename to protocol/naive/inbound_conn.go index 498e823cec..16944cbaff 100644 --- a/inbound/naive.go +++ b/protocol/naive/inbound_conn.go @@ -1,7 +1,6 @@ -package inbound +package naive import ( - "context" "encoding/binary" "io" "math/rand" @@ -11,228 +10,12 @@ import ( "strings" "time" - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" "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" "github.com/sagernet/sing/common/rw" - sHttp "github.com/sagernet/sing/protocol/http" ) -var _ adapter.Inbound = (*Naive)(nil) - -type Naive struct { - myInboundAdapter - authenticator *auth.Authenticator - tlsConfig tls.ServerConfig - httpServer *http.Server - h3Server any -} - -func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (*Naive, error) { - inbound := &Naive{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeNaive, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - authenticator: auth.NewAuthenticator(options.Users), - } - if common.Contains(inbound.network, N.NetworkUDP) { - if options.TLS == nil || !options.TLS.Enabled { - return nil, E.New("TLS is required for QUIC server") - } - } - if len(options.Users) == 0 { - return nil, E.New("missing users") - } - if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - inbound.tlsConfig = tlsConfig - } - return inbound, nil -} - -func (n *Naive) Start() error { - var tlsConfig *tls.STDConfig - if n.tlsConfig != nil { - err := n.tlsConfig.Start() - if err != nil { - return E.Cause(err, "create TLS config") - } - tlsConfig, err = n.tlsConfig.Config() - if err != nil { - return err - } - } - - if common.Contains(n.network, N.NetworkTCP) { - tcpListener, err := n.ListenTCP() - if err != nil { - return err - } - n.httpServer = &http.Server{ - Handler: n, - TLSConfig: tlsConfig, - BaseContext: func(listener net.Listener) context.Context { - return n.ctx - }, - } - go func() { - var sErr error - if tlsConfig != nil { - sErr = n.httpServer.ServeTLS(tcpListener, "", "") - } else { - sErr = n.httpServer.Serve(tcpListener) - } - if sErr != nil && !E.IsClosedOrCanceled(sErr) { - n.logger.Error("http server serve error: ", sErr) - } - }() - } - - if common.Contains(n.network, N.NetworkUDP) { - err := n.configureHTTP3Listener() - if !C.WithQUIC && len(n.network) > 1 { - n.logger.Warn(E.Cause(err, "naive http3 disabled")) - } else if err != nil { - return err - } - } - - return nil -} - -func (n *Naive) Close() error { - return common.Close( - &n.myInboundAdapter, - common.PtrOrNil(n.httpServer), - n.h3Server, - n.tlsConfig, - ) -} - -func (n *Naive) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - ctx := log.ContextWithNewID(request.Context()) - if request.Method != "CONNECT" { - rejectHTTP(writer, http.StatusBadRequest) - n.badRequest(ctx, request, E.New("not CONNECT request")) - return - } else if request.Header.Get("Padding") == "" { - rejectHTTP(writer, http.StatusBadRequest) - n.badRequest(ctx, request, E.New("missing naive padding")) - return - } - userName, password, authOk := sHttp.ParseBasicAuth(request.Header.Get("Proxy-Authorization")) - if authOk { - authOk = n.authenticator.Verify(userName, password) - } - if !authOk { - rejectHTTP(writer, http.StatusProxyAuthRequired) - n.badRequest(ctx, request, E.New("authorization failed")) - return - } - writer.Header().Set("Padding", generateNaivePaddingHeader()) - writer.WriteHeader(http.StatusOK) - writer.(http.Flusher).Flush() - - hostPort := request.URL.Host - if hostPort == "" { - hostPort = request.Host - } - source := sHttp.SourceAddress(request) - destination := M.ParseSocksaddr(hostPort) - - if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { - conn, _, err := hijacker.Hijack() - if err != nil { - n.badRequest(ctx, request, E.New("hijack failed")) - return - } - n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination) - } else { - n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) - } -} - -func (n *Naive) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) { - if userName != "" { - n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) - n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) - } else { - n.logger.InfoContext(ctx, "inbound connection from ", source) - n.logger.InfoContext(ctx, "inbound connection to ", destination) - } - metadata := n.createMetadata(conn, adapter.InboundContext{ - Source: source, - Destination: destination, - User: userName, - }) - if !waitForClose { - n.router.RouteConnectionEx(ctx, conn, metadata, nil) - } else { - done := make(chan struct{}) - wrapper := v2rayhttp.NewHTTP2Wrapper(conn) - n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) { - close(done) - })) - <-done - wrapper.CloseWrapper() - } -} - -func (n *Naive) badRequest(ctx context.Context, request *http.Request, err error) { - n.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) -} - -func rejectHTTP(writer http.ResponseWriter, statusCode int) { - hijacker, ok := writer.(http.Hijacker) - if !ok { - writer.WriteHeader(statusCode) - return - } - conn, _, err := hijacker.Hijack() - if err != nil { - writer.WriteHeader(statusCode) - return - } - if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { - tcpConn.SetLinger(0) - } - conn.Close() -} - -func generateNaivePaddingHeader() string { - paddingLen := rand.Intn(32) + 30 - padding := make([]byte, paddingLen) - bits := rand.Uint64() - for i := 0; i < 16; i++ { - // Codes that won't be Huffman coded. - padding[i] = "!#$()+<>?@[]^`{}"[bits&15] - bits >>= 4 - } - for i := 16; i < paddingLen; i++ { - padding[i] = '~' - } - return string(padding) -} - const kFirstPaddings = 8 type naiveH1Conn struct { diff --git a/protocol/naive/quic/inbound_init.go b/protocol/naive/quic/inbound_init.go new file mode 100644 index 0000000000..f495c860f8 --- /dev/null +++ b/protocol/naive/quic/inbound_init.go @@ -0,0 +1,52 @@ +package quic + +import ( + "io" + "net/http" + + "github.com/sagernet/quic-go" + "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/protocol/naive" + "github.com/sagernet/sing-quic" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +func init() { + naive.ConfigureHTTP3ListenerFunc = func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) { + err := qtls.ConfigureHTTP3(tlsConfig) + if err != nil { + return nil, err + } + + udpConn, err := listener.ListenUDP() + if err != nil { + return nil, err + } + + quicListener, err := qtls.ListenEarly(udpConn, tlsConfig, &quic.Config{ + MaxIncomingStreams: 1 << 60, + Allow0RTT: true, + }) + if err != nil { + udpConn.Close() + return nil, err + } + + h3Server := &http3.Server{ + Handler: handler, + } + + go func() { + sErr := h3Server.ServeListener(quicListener) + udpConn.Close() + if sErr != nil && !E.IsClosedOrCanceled(sErr) { + logger.Error("http3 server closed: ", sErr) + } + }() + + return quicListener, nil + } +} diff --git a/protocol/redirect/redirect.go b/protocol/redirect/redirect.go new file mode 100644 index 0000000000..71e1fced1a --- /dev/null +++ b/protocol/redirect/redirect.go @@ -0,0 +1,65 @@ +package redirect + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/redir" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterRedirect(registry *inbound.Registry) { + inbound.Register[option.RedirectInboundOptions](registry, C.TypeRedirect, NewRedirect) +} + +type Redirect struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener +} + +func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) (adapter.Inbound, error) { + redirect := &Redirect{ + Adapter: inbound.NewAdapter(C.TypeRedirect, tag), + router: router, + logger: logger, + } + redirect.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: redirect, + }) + return redirect, nil +} + +func (h *Redirect) Start() error { + return h.listener.Start() +} + +func (h *Redirect) Close() error { + return h.listener.Close() +} + +func (h *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + destination, err := redir.GetOriginalDestination(conn) + if err != nil { + conn.Close() + h.logger.ErrorContext(ctx, "process connection from ", conn.RemoteAddr(), ": get redirect destination: ", err) + return + } + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.Destination = M.SocksaddrFromNetIP(destination) + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/inbound/tproxy.go b/protocol/redirect/tproxy.go similarity index 60% rename from inbound/tproxy.go rename to protocol/redirect/tproxy.go index 40653c797b..dee40ec5a8 100644 --- a/inbound/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -1,4 +1,4 @@ -package inbound +package redirect import ( "context" @@ -8,6 +8,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/redir" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -21,22 +23,25 @@ import ( "github.com/sagernet/sing/common/udpnat2" ) +func RegisterTProxy(registry *inbound.Registry) { + inbound.Register[option.TProxyInboundOptions](registry, C.TypeTProxy, NewTProxy) +} + type TProxy struct { - myInboundAdapter - udpNat *udpnat.Service + inbound.Adapter + ctx context.Context + router adapter.Router + logger log.ContextLogger + listener *listener.Listener + udpNat *udpnat.Service } -func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) *TProxy { +func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) (adapter.Inbound, error) { tproxy := &TProxy{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTProxy, - network: options.Network.Build(), - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + Adapter: inbound.NewAdapter(C.TypeTProxy, tag), + ctx: ctx, + router: router, + logger: logger, } var udpTimeout time.Duration if options.UDPTimeout != 0 { @@ -44,28 +49,34 @@ func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLog } else { udpTimeout = C.UDPTimeout } - tproxy.connHandler = tproxy - tproxy.oobPacketHandler = tproxy tproxy.udpNat = udpnat.New(tproxy, tproxy.preparePacketConnection, udpTimeout, false) - return tproxy + tproxy.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: tproxy, + OOBPacketHandler: tproxy, + }) + return tproxy, nil } func (t *TProxy) Start() error { - err := t.myInboundAdapter.Start() + err := t.listener.Start() if err != nil { return err } - if t.tcpListener != nil { - err = control.Conn(common.MustCast[syscall.Conn](t.tcpListener), func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(t.tcpListener.Addr()).Addr.Is6()) + if listener := t.listener.TCPListener(); listener != nil { + err = control.Conn(common.MustCast[syscall.Conn](listener), func(fd uintptr) error { + return redir.TProxy(fd, M.SocksaddrFromNet(listener.Addr()).Addr.Is6()) }) if err != nil { return E.Cause(err, "configure tproxy TCP listener") } } - if t.udpConn != nil { - err = control.Conn(t.udpConn, func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(t.udpConn.LocalAddr()).Addr.Is6()) + if conn := t.listener.UDPConn(); conn != nil { + err = control.Conn(conn, func(fd uintptr) error { + return redir.TProxy(fd, M.SocksaddrFromNet(conn.LocalAddr()).Addr.Is6()) }) if err != nil { return E.Cause(err, "configure tproxy UDP listener") @@ -74,13 +85,26 @@ func (t *TProxy) Start() error { return nil } +func (t *TProxy) Close() error { + return t.listener.Close() +} + func (t *TProxy) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - t.newConnectionEx(ctx, conn, metadata, onClose) + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } func (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - t.newPacketConnectionEx(ctx, conn, t.createPacketMetadataEx(source, destination), onClose) + t.logger.InfoContext(ctx, "inbound packet connection from ", source) + t.logger.InfoContext(ctx, "inbound packet connection to ", destination) + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = t.listener.UDPAddr() + t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } func (t *TProxy) NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) { @@ -100,8 +124,9 @@ type tproxyPacketWriter struct { } func (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { - writer := &tproxyPacketWriter{ctx: t.ctx, source: source.AddrPort(), destination: destination} - return true, t.ctx, writer, func(it error) { + ctx := log.ContextWithNewID(t.ctx) + writer := &tproxyPacketWriter{ctx: ctx, source: source.AddrPort(), destination: destination} + return true, ctx, writer, func(it error) { common.Close(common.PtrOrNil(writer.conn)) } } diff --git a/protocol/shadowsocks/inbound.go b/protocol/shadowsocks/inbound.go new file mode 100644 index 0000000000..b23516d995 --- /dev/null +++ b/protocol/shadowsocks/inbound.go @@ -0,0 +1,179 @@ +package shadowsocks + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/mux" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-shadowsocks" + "github.com/sagernet/sing-shadowsocks/shadowaead" + "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "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" + "github.com/sagernet/sing/common/ntp" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocks, NewInbound) +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { + if len(options.Users) > 0 && len(options.Destinations) > 0 { + return nil, E.New("users and destinations options must not be combined") + } + if len(options.Users) > 0 { + return newMultiInbound(ctx, router, logger, tag, options) + } else if len(options.Destinations) > 0 { + return newRelayInbound(ctx, router, logger, tag, options) + } else { + return newInbound(ctx, router, logger, tag, options) + } +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service shadowsocks.Service +} + +func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + } + var err error + inbound.router, err = mux.NewRouterWithOptions(router, logger, common.PtrValueOrDefault(options.Multiplex)) + if err != nil { + return nil, err + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + switch { + case options.Method == shadowsocks.MethodNone: + inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) + case common.Contains(shadowaead.List, options.Method): + inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) + case common.Contains(shadowaead_2022.List, options.Method): + inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx)) + default: + err = E.New("unsupported method: ", options.Method) + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) + return inbound, err +} + +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) +} + +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + ctx = log.ContextWithNewID(ctx) + h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +var _ N.PacketConn = (*stubPacketConn)(nil) + +type stubPacketConn struct { + N.PacketWriter +} + +func (c *stubPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + panic("stub!") +} + +func (c *stubPacketConn) Close() error { + return nil +} + +func (c *stubPacketConn) LocalAddr() net.Addr { + panic("stub!") +} + +func (c *stubPacketConn) SetDeadline(t time.Time) error { + panic("stub!") +} + +func (c *stubPacketConn) SetReadDeadline(t time.Time) error { + panic("stub!") +} + +func (c *stubPacketConn) SetWriteDeadline(t time.Time) error { + panic("stub!") +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} + +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) +} diff --git a/inbound/shadowsocks_multi.go b/protocol/shadowsocks/inbound_multi.go similarity index 59% rename from inbound/shadowsocks_multi.go rename to protocol/shadowsocks/inbound_multi.go index 2953419490..0e1efedfc2 100644 --- a/inbound/shadowsocks_multi.go +++ b/protocol/shadowsocks/inbound_multi.go @@ -1,4 +1,4 @@ -package inbound +package shadowsocks import ( "context" @@ -7,6 +7,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" @@ -20,36 +22,31 @@ import ( "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "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/ntp" ) -var ( - _ adapter.Inbound = (*ShadowsocksMulti)(nil) - _ adapter.TCPInjectableInbound = (*ShadowsocksMulti)(nil) -) +var _ adapter.TCPInjectableInbound = (*MultiInbound)(nil) -type ShadowsocksMulti struct { - myInboundAdapter - service shadowsocks.MultiService[int] - users []option.ShadowsocksUser +type MultiInbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service shadowsocks.MultiService[int] + users []option.ShadowsocksUser } -func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksMulti, error) { - inbound := &ShadowsocksMulti{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, +func newMultiInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*MultiInbound, error) { + inbound := &MultiInbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, } - inbound.connHandler = inbound - inbound.packetHandler = inbound var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) if err != nil { @@ -91,12 +88,28 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log. return nil, err } inbound.service = service - inbound.packetUpstream = service inbound.users = options.Users + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) return inbound, err } -func (h *ShadowsocksMulti) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *MultiInbound) Start() error { + return h.listener.Start() +} + +func (h *MultiInbound) Close() error { + return h.listener.Close() +} + +func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -104,14 +117,14 @@ func (h *ShadowsocksMulti) NewConnectionEx(ctx context.Context, conn net.Conn, m } } -func (h *ShadowsocksMulti) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) +func (h *MultiInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) if err != nil { h.logger.Error(E.Cause(err, "process packet from ", source)) } } -func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -123,10 +136,12 @@ func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, met metadata.User = user } h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) } -func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -140,5 +155,13 @@ func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +func (h *MultiInbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) } diff --git a/inbound/shadowsocks_relay.go b/protocol/shadowsocks/inbound_relay.go similarity index 57% rename from inbound/shadowsocks_relay.go rename to protocol/shadowsocks/inbound_relay.go index 02246a3f3c..5818ca29f1 100644 --- a/inbound/shadowsocks_relay.go +++ b/protocol/shadowsocks/inbound_relay.go @@ -1,4 +1,4 @@ -package inbound +package shadowsocks import ( "context" @@ -7,6 +7,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" @@ -18,36 +20,31 @@ import ( "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*ShadowsocksRelay)(nil) - _ adapter.TCPInjectableInbound = (*ShadowsocksRelay)(nil) -) +var _ adapter.TCPInjectableInbound = (*RelayInbound)(nil) -type ShadowsocksRelay struct { - myInboundAdapter +type RelayInbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener service *shadowaead_2022.RelayService[int] destinations []option.ShadowsocksDestination } -func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksRelay, error) { - inbound := &ShadowsocksRelay{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, +func newRelayInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*RelayInbound, error) { + inbound := &RelayInbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, destinations: options.Destinations, } - inbound.connHandler = inbound - inbound.packetHandler = inbound var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) if err != nil { @@ -77,11 +74,27 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log. return nil, err } inbound.service = service - inbound.packetUpstream = service + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) return inbound, err } -func (h *ShadowsocksRelay) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *RelayInbound) Start() error { + return h.listener.Start() +} + +func (h *RelayInbound) Close() error { + return h.listener.Close() +} + +func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -89,14 +102,14 @@ func (h *ShadowsocksRelay) NewConnectionEx(ctx context.Context, conn net.Conn, m } } -func (h *ShadowsocksRelay) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) +func (h *RelayInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) if err != nil { h.logger.Error(E.Cause(err, "process packet from ", source)) } } -func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { destinationIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -108,10 +121,12 @@ func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, met metadata.User = destination } h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) } -func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *RelayInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { destinationIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -125,5 +140,13 @@ func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +func (h *RelayInbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) } diff --git a/outbound/shadowsocks.go b/protocol/shadowsocks/outbound.go similarity index 80% rename from outbound/shadowsocks.go rename to protocol/shadowsocks/outbound.go index 153542742a..73b3838503 100644 --- a/outbound/shadowsocks.go +++ b/protocol/shadowsocks/outbound.go @@ -1,10 +1,11 @@ -package outbound +package shadowsocks import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" C "github.com/sagernet/sing-box/constant" @@ -15,15 +16,19 @@ 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" "github.com/sagernet/sing/common/uot" ) -var _ adapter.Outbound = (*Shadowsocks)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound) +} -type Shadowsocks struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer method shadowsocks.Method serverAddr M.Socksaddr @@ -32,7 +37,7 @@ type Shadowsocks struct { multiplexDialer *mux.Client } -func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (adapter.Outbound, error) { method, err := shadowsocks.CreateMethod(ctx, options.Method, shadowsocks.MethodOptions{ Password: options.Password, }) @@ -43,15 +48,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte if err != nil { return nil, err } - outbound := &Shadowsocks{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, method: method, serverAddr: options.ServerOptions.Build(), @@ -78,9 +77,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte return outbound, nil } -func (h *Shadowsocks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination if h.multiplexDialer == nil { switch N.NetworkName(network) { @@ -106,9 +105,9 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati } } -func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination if h.multiplexDialer == nil { if h.uotClient != nil { @@ -125,24 +124,24 @@ func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) } } -func (h *Shadowsocks) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } return } -func (h *Shadowsocks) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer)) } var _ N.Dialer = (*shadowsocksDialer)(nil) -type shadowsocksDialer Shadowsocks +type shadowsocksDialer Outbound func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -170,7 +169,7 @@ func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, des func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) if err != nil { diff --git a/inbound/shadowtls.go b/protocol/shadowtls/inbound.go similarity index 62% rename from inbound/shadowtls.go rename to protocol/shadowtls/inbound.go index ca14228635..6887e838a5 100644 --- a/inbound/shadowtls.go +++ b/protocol/shadowtls/inbound.go @@ -1,11 +1,13 @@ -package inbound +package shadowtls import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -13,25 +15,27 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" ) -type ShadowTLS struct { - myInboundAdapter - service *shadowtls.Service +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.ShadowTLSInboundOptions](registry, C.TypeShadowTLS, NewInbound) } -func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (*ShadowTLS, error) { - inbound := &ShadowTLS{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowTLS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, +type Inbound struct { + inbound.Adapter + router adapter.Router + logger logger.ContextLogger + listener *listener.Listener + service *shadowtls.Service +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeShadowTLS, tag), + router: router, + logger: logger, } if options.Version == 0 { @@ -68,22 +72,36 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context }, HandshakeForServerName: handshakeForServerName, StrictMode: options.StrictMode, - Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, inbound), + Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, nil), Logger: logger, }) if err != nil { return nil, err } inbound.service = service - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if userName, _ := auth.UserFromContext[string](ctx); userName != "" { metadata.User = userName h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) @@ -93,7 +111,7 @@ func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata a return h.router.RouteConnection(ctx, conn, metadata) } -func (h *ShadowTLS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { diff --git a/outbound/shadowtls.go b/protocol/shadowtls/outbound.go similarity index 74% rename from outbound/shadowtls.go rename to protocol/shadowtls/outbound.go index ff1b9d6c45..7d46a8f682 100644 --- a/outbound/shadowtls.go +++ b/protocol/shadowtls/outbound.go @@ -1,4 +1,4 @@ -package outbound +package shadowtls import ( "context" @@ -6,6 +6,7 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -17,23 +18,18 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Outbound = (*ShadowTLS)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.ShadowTLSOutboundOptions](registry, C.TypeShadowTLS, NewOutbound) +} -type ShadowTLS struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter client *shadowtls.Client } -func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) { - outbound := &ShadowTLS{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowTLS, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowTLS, []string{N.NetworkTCP}, tag, options.DialerOptions), } if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -91,9 +87,9 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context return outbound, nil } -func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -103,6 +99,6 @@ func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination } } -func (h *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go new file mode 100644 index 0000000000..29649a88df --- /dev/null +++ b/protocol/socks/inbound.go @@ -0,0 +1,91 @@ +package socks + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/socks" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.SocksInboundOptions](registry, C.TypeSOCKS, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeSOCKS, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) + return inbound, nil +} + +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +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, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/outbound/socks.go b/protocol/socks/outbound.go similarity index 65% rename from outbound/socks.go rename to protocol/socks/outbound.go index 575d6eb3dd..0194800a7c 100644 --- a/outbound/socks.go +++ b/protocol/socks/outbound.go @@ -1,10 +1,11 @@ -package outbound +package socks import ( "context" "net" "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" @@ -12,22 +13,29 @@ import ( "github.com/sagernet/sing-dns" "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" "github.com/sagernet/sing/protocol/socks" ) -var _ adapter.Outbound = (*Socks)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.SOCKSOutboundOptions](registry, C.TypeSOCKS, NewOutbound) +} + +var _ adapter.Outbound = (*Outbound)(nil) -type Socks struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + router adapter.Router + logger logger.ContextLogger client *socks.Client resolve bool uotClient *uot.Client } -func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SOCKSOutboundOptions) (adapter.Outbound, error) { var version socks.Version var err error if options.Version != "" { @@ -42,15 +50,10 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio if err != nil { return nil, err } - outbound := &Socks{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSOCKS, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, options.Network.Build(), tag, options.DialerOptions), + router: router, + logger: logger, client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), resolve: version == socks.Version4, } @@ -64,9 +67,9 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio return outbound, nil } -func (h *Socks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -90,9 +93,9 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S return h.client.DialContext(ctx, network, destination) } -func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination if h.uotClient != nil { h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) @@ -115,20 +118,20 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. // TODO // Deprecated -func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.resolve { - return NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) + return outbound.NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) } else { - return NewConnection(ctx, h, conn, metadata) + return outbound.NewConnection(ctx, h, conn, metadata) } } // TODO // Deprecated -func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { if h.resolve { - return NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) + return outbound.NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) } else { - return NewPacketConnection(ctx, h, conn, metadata) + return outbound.NewPacketConnection(ctx, h, conn, metadata) } } diff --git a/outbound/ssh.go b/protocol/ssh/outbound.go similarity index 80% rename from outbound/ssh.go rename to protocol/ssh/outbound.go index 28abe9a5e3..62a2a8d9b5 100644 --- a/outbound/ssh.go +++ b/protocol/ssh/outbound.go @@ -1,4 +1,4 @@ -package outbound +package ssh import ( "bytes" @@ -12,26 +12,30 @@ import ( "sync" "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" "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" "golang.org/x/crypto/ssh" ) -var ( - _ adapter.Outbound = (*SSH)(nil) - _ adapter.InterfaceUpdateListener = (*SSH)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.SSHOutboundOptions](registry, C.TypeSSH, NewOutbound) +} -type SSH struct { - myOutboundAdapter +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter ctx context.Context + logger logger.ContextLogger dialer N.Dialer serverAddr M.Socksaddr user string @@ -44,21 +48,15 @@ type SSH struct { client *ssh.Client } -func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &SSH{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSSH, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSSH, []string{N.NetworkTCP}, tag, options.DialerOptions), ctx: ctx, + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), user: options.User, @@ -122,7 +120,7 @@ func randomVersion() string { return version } -func (s *SSH) connect() (*ssh.Client, error) { +func (s *Outbound) connect() (*ssh.Client, error) { if s.client != nil { return s.client, nil } @@ -179,16 +177,16 @@ func (s *SSH) connect() (*ssh.Client, error) { return client, nil } -func (s *SSH) InterfaceUpdated() { +func (s *Outbound) InterfaceUpdated() { common.Close(s.clientConn) return } -func (s *SSH) Close() error { +func (s *Outbound) Close() error { return common.Close(s.clientConn) } -func (s *SSH) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (s *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { client, err := s.connect() if err != nil { return nil, err @@ -196,6 +194,6 @@ func (s *SSH) DialContext(ctx context.Context, network string, destination M.Soc return client.Dial(network, destination.String()) } -func (s *SSH) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/outbound/tor.go b/protocol/tor/outbound.go similarity index 83% rename from outbound/tor.go rename to protocol/tor/outbound.go index ccc0c0cfe5..89a295b822 100644 --- a/outbound/tor.go +++ b/protocol/tor/outbound.go @@ -1,4 +1,4 @@ -package outbound +package tor import ( "context" @@ -8,6 +8,7 @@ import ( "strings" "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" @@ -15,6 +16,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" @@ -24,11 +26,14 @@ import ( "github.com/cretz/bine/tor" ) -var _ adapter.Outbound = (*Tor)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TorOutboundOptions](registry, C.TypeTor, NewOutbound) +} -type Tor struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter ctx context.Context + logger logger.ContextLogger proxy *ProxyListener startConf *tor.StartConf options map[string]string @@ -37,8 +42,8 @@ type Tor struct { socksClient *socks.Client } -func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (*Tor, error) { - startConf := newConfig() +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (adapter.Outbound, error) { + var startConf tor.StartConf startConf.DataDir = os.ExpandEnv(options.DataDirectory) startConf.TempDataDirBase = os.TempDir() startConf.ExtraArgs = options.ExtraArgs @@ -74,23 +79,17 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger if err != nil { return nil, err } - return &Tor{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTor, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, []string{N.NetworkTCP}, tag, options.DialerOptions), ctx: ctx, + logger: logger, proxy: NewProxyListener(ctx, logger, outboundDialer), startConf: &startConf, options: options.Options, }, nil } -func (t *Tor) Start() error { +func (t *Outbound) Start() error { err := t.start() if err != nil { t.Close() @@ -106,7 +105,7 @@ var torLogEvents = []control.EventCode{ control.EventCodeLogWarn, } -func (t *Tor) start() error { +func (t *Outbound) start() error { torInstance, err := tor.Start(t.ctx, t.startConf) if err != nil { return E.New(strings.ToLower(err.Error())) @@ -168,7 +167,7 @@ func (t *Tor) start() error { return nil } -func (t *Tor) recvLoop() { +func (t *Outbound) recvLoop() { for rawEvent := range t.events { switch event := rawEvent.(type) { case *control.LogEvent: @@ -191,7 +190,7 @@ func (t *Tor) recvLoop() { } } -func (t *Tor) Close() error { +func (t *Outbound) Close() error { err := common.Close( common.PtrOrNil(t.proxy), common.PtrOrNil(t.instance), @@ -203,11 +202,11 @@ func (t *Tor) Close() error { return err } -func (t *Tor) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (t *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { t.logger.InfoContext(ctx, "outbound connection to ", destination) return t.socksClient.DialContext(ctx, network, destination) } -func (t *Tor) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (t *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/outbound/proxy.go b/protocol/tor/proxy.go similarity index 94% rename from outbound/proxy.go rename to protocol/tor/proxy.go index 38c1845392..ef60bd1f7e 100644 --- a/outbound/proxy.go +++ b/protocol/tor/proxy.go @@ -1,4 +1,4 @@ -package outbound +package tor import ( "context" @@ -7,6 +7,7 @@ import ( "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" @@ -106,7 +107,7 @@ func (l *ProxyListener) NewConnection(ctx context.Context, conn net.Conn, upstre metadata.Network = N.NetworkTCP metadata.Destination = upstreamMetadata.Destination l.logger.InfoContext(ctx, "proxy connection to ", metadata.Destination) - return NewConnection(ctx, l.dialer, conn, metadata) + return outbound.NewConnection(ctx, l.dialer, conn, metadata) } func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { @@ -114,5 +115,5 @@ func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketCo metadata.Network = N.NetworkUDP metadata.Destination = upstreamMetadata.Destination l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.Destination) - return NewPacketConnection(ctx, l.dialer, conn, metadata) + return outbound.NewPacketConnection(ctx, l.dialer, conn, metadata) } diff --git a/inbound/trojan.go b/protocol/trojan/inbound.go similarity index 68% rename from inbound/trojan.go rename to protocol/trojan/inbound.go index ce003dda70..010ae8baf2 100644 --- a/inbound/trojan.go +++ b/protocol/trojan/inbound.go @@ -1,4 +1,4 @@ -package inbound +package trojan import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -21,13 +23,17 @@ import ( N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*Trojan)(nil) - _ adapter.TCPInjectableInbound = (*Trojan)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TrojanInboundOptions](registry, C.TypeTrojan, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) -type Trojan struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener service *trojan.Service[int] users []option.TrojanUser tlsConfig tls.ServerConfig @@ -36,18 +42,12 @@ type Trojan struct { transport adapter.V2RayServerTransport } -func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (*Trojan, error) { - inbound := &Trojan{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTrojan, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeTrojan, tag), + router: router, + logger: logger, + users: options.Users, } if options.TLS != nil { tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) @@ -80,7 +80,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog } fallbackHandler = adapter.NewUpstreamContextHandler(inbound.fallbackConnection, nil, nil) } - service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), fallbackHandler) + service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, nil), fallbackHandler, logger) err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.TrojanUser) int { return index }), common.Map(options.Users, func(it option.TrojanUser) string { @@ -90,7 +90,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*trojanTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -100,11 +100,17 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } inbound.service = service - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *Trojan) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { @@ -112,10 +118,10 @@ func (h *Trojan) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -127,7 +133,7 @@ func (h *Trojan) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -141,15 +147,15 @@ func (h *Trojan) Start() error { return nil } -func (h *Trojan) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, h.transport, ) } -func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -160,7 +166,7 @@ func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adap return h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *Trojan) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -168,7 +174,7 @@ func (h *Trojan) NewConnectionEx(ctx context.Context, conn net.Conn, metadata ad } } -func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -183,7 +189,7 @@ func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adap return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Trojan) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var fallbackAddr M.Socksaddr if len(h.fallbackAddrTLSNextProto) > 0 { if tlsConn, loaded := common.Cast[tls.Conn](conn); loaded { @@ -206,7 +212,7 @@ func (h *Trojan) fallbackConnection(ctx context.Context, conn net.Conn, metadata return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Trojan) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -221,10 +227,18 @@ func (h *Trojan) newPacketConnection(ctx context.Context, conn N.PacketConn, met return h.router.RoutePacketConnection(ctx, conn, metadata) } -var _ adapter.V2RayServerTransportHandler = (*trojanTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) -type trojanTransportHandler Trojan +type inboundTransportHandler Inbound -func (t *trojanTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - (*Trojan)(t).routeTCP(ctx, conn, source, destination, onClose) +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) } diff --git a/outbound/trojan.go b/protocol/trojan/outbound.go similarity index 79% rename from outbound/trojan.go rename to protocol/trojan/outbound.go index ee0b2a4b9e..f64c48c3c5 100644 --- a/outbound/trojan.go +++ b/protocol/trojan/outbound.go @@ -1,10 +1,11 @@ -package outbound +package trojan import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -16,14 +17,18 @@ 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" ) -var _ adapter.Outbound = (*Trojan)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound) +} -type Trojan struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer serverAddr M.Socksaddr key [56]byte @@ -32,20 +37,14 @@ type Trojan struct { transport adapter.V2RayClientTransport } -func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &Trojan{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTrojan, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTrojan, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), key: trojan.Key(options.Password), @@ -69,7 +68,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return outbound, nil } -func (h *Trojan) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -89,7 +88,7 @@ func (h *Trojan) DialContext(ctx context.Context, network string, destination M. } } -func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*trojanDialer)(h).ListenPacket(ctx, destination) @@ -99,7 +98,7 @@ func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net } } -func (h *Trojan) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } @@ -109,15 +108,15 @@ func (h *Trojan) InterfaceUpdated() { return } -func (h *Trojan) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -type trojanDialer Trojan +type trojanDialer Outbound func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error diff --git a/inbound/tuic.go b/protocol/tuic/inbound.go similarity index 68% rename from inbound/tuic.go rename to protocol/tuic/inbound.go index b067c43cd9..33de10d5cb 100644 --- a/inbound/tuic.go +++ b/protocol/tuic/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package tuic import ( "context" @@ -8,6 +6,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" @@ -22,16 +22,21 @@ import ( "github.com/gofrs/uuid/v5" ) -var _ adapter.Inbound = (*TUIC)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, NewInbound) +} -type TUIC struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig server *tuic.Service[int] userNameList []string } -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (*TUIC, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -40,16 +45,15 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - inbound := &TUIC{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTUIC, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeTUIC, tag), + router: uot.NewRouter(router, logger), + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var udpTimeout time.Duration @@ -95,9 +99,12 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge return inbound, nil } -func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -109,9 +116,13 @@ func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapte return h.router.RouteConnection(ctx, conn, metadata) } -func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -123,23 +134,23 @@ func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metad return h.router.RoutePacketConnection(ctx, conn, metadata) } -func (h *TUIC) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.server.Start(packetConn) } -func (h *TUIC) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, common.PtrOrNil(h.server), ) diff --git a/outbound/tuic.go b/protocol/tuic/outbound.go similarity index 76% rename from outbound/tuic.go rename to protocol/tuic/outbound.go index aaf998b16e..691d1658de 100644 --- a/outbound/tuic.go +++ b/protocol/tuic/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package tuic import ( "context" @@ -9,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -18,6 +17,7 @@ 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" "github.com/sagernet/sing/common/uot" @@ -25,18 +25,20 @@ import ( "github.com/gofrs/uuid/v5" ) -var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, NewOutbound) +} + +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) -type TUIC struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *tuic.Client udpStream bool } -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (*TUIC, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -77,21 +79,15 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - return &TUIC{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTUIC, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTUIC, options.Network.Build(), tag, options.DialerOptions), + logger: logger, client: client, udpStream: options.UDPOverStream, }, nil } -func (h *TUIC) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +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) @@ -119,7 +115,7 @@ func (h *TUIC) DialContext(ctx context.Context, network string, destination M.So } } -func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.udpStream { h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination) streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version)) @@ -136,10 +132,10 @@ func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.P } } -func (h *TUIC) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { _ = h.client.CloseWithError(E.New("network changed")) } -func (h *TUIC) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/inbound/tun.go b/protocol/tun/inbound.go similarity index 92% rename from inbound/tun.go rename to protocol/tun/inbound.go index 11b16428b9..ff679c8ec8 100644 --- a/inbound/tun.go +++ b/protocol/tun/inbound.go @@ -1,4 +1,4 @@ -package inbound +package tun import ( "context" @@ -11,6 +11,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "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" @@ -24,13 +25,16 @@ import ( N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ranges" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" "go4.org/netipx" ) -var _ adapter.Inbound = (*TUN)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TunInboundOptions](registry, C.TypeTun, NewInbound) +} -type TUN struct { +type Inbound struct { tag string ctx context.Context router adapter.Router @@ -55,7 +59,7 @@ type TUN struct { routeExcludeAddressSet []*netipx.IPSet } -func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*TUN, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) { address := options.Address var deprecatedAddressUsed bool //nolint:staticcheck @@ -164,7 +168,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger outputMark = tun.DefaultAutoRedirectOutputMark } - inbound := &TUN{ + inbound := &Inbound{ tag: tag, ctx: ctx, router: router, @@ -198,7 +202,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger endpointIndependentNat: options.EndpointIndependentNat, udpTimeout: udpTimeout, stack: options.Stack, - platformInterface: platformInterface, + platformInterface: service.FromContext[platform.Interface](ctx), platformOptions: common.PtrValueOrDefault(options.Platform), } if options.AutoRedirect { @@ -285,15 +289,15 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges. return uidRanges, nil } -func (t *TUN) Type() string { +func (t *Inbound) Type() string { return C.TypeTun } -func (t *TUN) Tag() string { +func (t *Inbound) Tag() string { return t.tag } -func (t *TUN) Start() error { +func (t *Inbound) Start() error { if C.IsAndroid && t.platformInterface == nil { t.tunOptions.BuildAndroidRules(t.router.PackageManager()) } @@ -350,7 +354,7 @@ func (t *TUN) Start() error { return nil } -func (t *TUN) PostStart() error { +func (t *Inbound) PostStart() error { monitor := taskmonitor.New(t.logger, C.StartTimeout) if t.autoRedirect != nil { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) @@ -389,7 +393,7 @@ func (t *TUN) PostStart() error { return nil } -func (t *TUN) updateRouteAddressSet(it adapter.RuleSet) { +func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) t.autoRedirect.UpdateRouteAddressSet() @@ -397,7 +401,7 @@ func (t *TUN) updateRouteAddressSet(it adapter.RuleSet) { t.routeExcludeAddressSet = nil } -func (t *TUN) Close() error { +func (t *Inbound) Close() error { return common.Close( t.tunStack, t.tunIf, @@ -405,7 +409,7 @@ func (t *TUN) Close() error { ) } -func (t *TUN) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { +func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { return t.router.PreMatch(adapter.InboundContext{ Inbound: t.tag, InboundType: C.TypeTun, @@ -416,7 +420,7 @@ func (t *TUN) PrepareConnection(network string, source M.Socksaddr, destination }) } -func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { +func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag @@ -429,7 +433,7 @@ func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socks t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (t *TUN) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { +func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag @@ -442,7 +446,7 @@ func (t *TUN) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, sour t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -type autoRedirectHandler TUN +type autoRedirectHandler Inbound func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) diff --git a/inbound/vless.go b/protocol/vless/inbound.go similarity index 60% rename from inbound/vless.go rename to protocol/vless/inbound.go index ec26bd8859..0641549b0a 100644 --- a/inbound/vless.go +++ b/protocol/vless/inbound.go @@ -1,4 +1,4 @@ -package inbound +package vless import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" @@ -20,37 +22,36 @@ import ( "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*VLESS)(nil) - _ adapter.TCPInjectableInbound = (*VLESS)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.VLESSInboundOptions](registry, C.TypeVLESS, NewInbound) +} -type VLESS struct { - myInboundAdapter +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener users []option.VLESSUser service *vless.Service[int] tlsConfig tls.ServerConfig transport adapter.V2RayServerTransport } -func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (*VLESS, error) { - inbound := &VLESS{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeVLESS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - ctx: ctx, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeVLESS, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + users: options.Users, } var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) @@ -73,16 +74,22 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } } - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *VLESS) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { @@ -90,10 +97,10 @@ func (h *VLESS) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -105,7 +112,7 @@ func (h *VLESS) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -119,16 +126,16 @@ func (h *VLESS) Start() error { return nil } -func (h *VLESS) Close() error { +func (h *Inbound) Close() error { return common.Close( h.service, - &h.myInboundAdapter, + &h.listener, h.tlsConfig, h.transport, ) } -func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -139,7 +146,7 @@ func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VLESS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -147,7 +154,7 @@ func (h *VLESS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata ada } } -func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -162,7 +169,7 @@ func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapt return h.router.RouteConnection(ctx, conn, metadata) } -func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -183,10 +190,32 @@ func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, meta return h.router.RoutePacketConnection(ctx, conn, metadata) } -var _ adapter.V2RayServerTransportHandler = (*vlessTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) + +type inboundTransportHandler Inbound -type vlessTransportHandler VLESS +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} -func (t *vlessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - t.routeTCP(ctx, conn, source, destination, onClose) +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) } diff --git a/outbound/vless.go b/protocol/vless/outbound.go similarity index 84% rename from outbound/vless.go rename to protocol/vless/outbound.go index 536a1e8fba..1074549e8d 100644 --- a/outbound/vless.go +++ b/protocol/vless/outbound.go @@ -1,10 +1,11 @@ -package outbound +package vless import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -17,14 +18,18 @@ 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" ) -var _ adapter.Outbound = (*VLESS)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound) +} -type VLESS struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer client *vless.Client serverAddr M.Socksaddr @@ -35,20 +40,14 @@ type VLESS struct { xudp bool } -func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &VLESS{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVLESS, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVLESS, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } @@ -88,7 +87,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg return outbound, nil } -func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -108,7 +107,7 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S } } -func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*vlessDialer)(h).ListenPacket(ctx, destination) @@ -118,7 +117,7 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -func (h *VLESS) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } @@ -128,15 +127,15 @@ func (h *VLESS) InterfaceUpdated() { return } -func (h *VLESS) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -type vlessDialer VLESS +type vlessDialer Outbound func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error @@ -179,7 +178,7 @@ func (h *vlessDialer) DialContext(ctx context.Context, network string, destinati func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error diff --git a/inbound/vmess.go b/protocol/vmess/inbound.go similarity index 62% rename from inbound/vmess.go rename to protocol/vmess/inbound.go index 9099bd6293..1c80f376e1 100644 --- a/inbound/vmess.go +++ b/protocol/vmess/inbound.go @@ -1,4 +1,4 @@ -package inbound +package vmess import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" @@ -19,38 +21,37 @@ import ( "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "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/ntp" ) -var ( - _ adapter.Inbound = (*VMess)(nil) - _ adapter.TCPInjectableInbound = (*VMess)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.VMessInboundOptions](registry, C.TypeVMess, NewInbound) +} -type VMess struct { - myInboundAdapter +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener service *vmess.Service[int] users []option.VMessUser tlsConfig tls.ServerConfig transport adapter.V2RayServerTransport } -func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (*VMess, error) { - inbound := &VMess{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeVMess, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - ctx: ctx, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeVMess, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + users: options.Users, } var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) @@ -83,16 +84,22 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vmessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } } - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *VMess) Start() error { +func (h *Inbound) Start() error { err := h.service.Start() if err != nil { return err @@ -104,10 +111,10 @@ func (h *VMess) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -119,7 +126,7 @@ func (h *VMess) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -133,16 +140,16 @@ func (h *VMess) Start() error { return nil } -func (h *VMess) Close() error { +func (h *Inbound) Close() error { return common.Close( h.service, - &h.myInboundAdapter, + &h.listener, h.tlsConfig, h.transport, ) } -func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -153,7 +160,7 @@ func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VMess) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -161,7 +168,7 @@ func (h *VMess) NewConnectionEx(ctx context.Context, conn net.Conn, metadata ada } } -func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -176,7 +183,7 @@ func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapt return h.router.RouteConnection(ctx, conn, metadata) } -func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -197,10 +204,32 @@ func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, meta return h.router.RoutePacketConnection(ctx, conn, metadata) } -var _ adapter.V2RayServerTransportHandler = (*vmessTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) + +type inboundTransportHandler Inbound -type vmessTransportHandler VMess +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} -func (t *vmessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - (*VMess)(t).routeTCP(ctx, conn, source, destination, onClose) +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) } diff --git a/outbound/vmess.go b/protocol/vmess/outbound.go similarity index 84% rename from outbound/vmess.go rename to protocol/vmess/outbound.go index 126d2fd05f..759ea8baa4 100644 --- a/outbound/vmess.go +++ b/protocol/vmess/outbound.go @@ -1,10 +1,11 @@ -package outbound +package vmess import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -16,15 +17,19 @@ import ( "github.com/sagernet/sing-vmess/packetaddr" "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/ntp" ) -var _ adapter.Outbound = (*VMess)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.VMessOutboundOptions](registry, C.TypeVMess, NewOutbound) +} -type VMess struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer client *vmess.Client serverAddr M.Socksaddr @@ -35,20 +40,14 @@ type VMess struct { xudp bool } -func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &VMess{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVMess, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVMess, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } @@ -102,7 +101,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg return outbound, nil } -func (h *VMess) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } @@ -112,11 +111,11 @@ func (h *VMess) InterfaceUpdated() { return } -func (h *VMess) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -136,7 +135,7 @@ func (h *VMess) DialContext(ctx context.Context, network string, destination M.S } } -func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*vmessDialer)(h).ListenPacket(ctx, destination) @@ -146,11 +145,11 @@ func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -type vmessDialer VMess +type vmessDialer Outbound func (h *vmessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error @@ -178,7 +177,7 @@ func (h *vmessDialer) DialContext(ctx context.Context, network string, destinati func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error diff --git a/protocol/wireguard/init.go b/protocol/wireguard/init.go new file mode 100644 index 0000000000..848c113be8 --- /dev/null +++ b/protocol/wireguard/init.go @@ -0,0 +1,10 @@ +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/outbound/wireguard.go b/protocol/wireguard/outbound.go similarity index 75% rename from outbound/wireguard.go rename to protocol/wireguard/outbound.go index e6e4b8f8e4..901f8782d6 100644 --- a/outbound/wireguard.go +++ b/protocol/wireguard/outbound.go @@ -1,6 +1,4 @@ -//go:build with_wireguard - -package outbound +package wireguard import ( "context" @@ -12,6 +10,7 @@ import ( "strings" "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" @@ -21,6 +20,7 @@ import ( "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" @@ -30,14 +30,17 @@ import ( "github.com/sagernet/wireguard-go/device" ) -var ( - _ adapter.Outbound = (*WireGuard)(nil) - _ adapter.InterfaceUpdateListener = (*WireGuard)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.WireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) +} -type WireGuard struct { - myOutboundAdapter +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter ctx context.Context + router adapter.Router + logger logger.ContextLogger workers int peers []wireguard.PeerConfig useStdNetBind bool @@ -51,17 +54,12 @@ type WireGuard struct { tunDevice wireguard.Device } -func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) { - outbound := &WireGuard{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeWireGuard, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, options.Network.Build(), tag, options.DialerOptions), ctx: ctx, + router: router, + logger: logger, workers: options.Workers, pauseManager: service.FromContext[pause.Manager](ctx), } @@ -111,7 +109,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context return outbound, nil } -func (w *WireGuard) Start() error { +func (w *Outbound) Start() error { if common.Any(w.peers, func(peer wireguard.PeerConfig) bool { return !peer.Endpoint.IsValid() }) { @@ -121,7 +119,7 @@ func (w *WireGuard) Start() error { return w.start() } -func (w *WireGuard) PostStart() error { +func (w *Outbound) PostStart() error { if common.All(w.peers, func(peer wireguard.PeerConfig) bool { return peer.Endpoint.IsValid() }) { @@ -130,7 +128,7 @@ func (w *WireGuard) PostStart() error { return w.start() } -func (w *WireGuard) start() error { +func (w *Outbound) start() error { err := wireguard.ResolvePeers(w.ctx, w.router, w.peers) if err != nil { return err @@ -150,7 +148,7 @@ func (w *WireGuard) start() error { connectAddr = w.peers[0].Endpoint reserved = w.peers[0].Reserved } - bind = wireguard.NewClientBind(w.ctx, w, w.listener, isConnect, connectAddr, reserved) + bind = wireguard.NewClientBind(w.ctx, w.logger, w.listener, isConnect, connectAddr, reserved) } if w.useStdNetBind || len(w.peers) > 1 { for _, peer := range w.peers { @@ -184,7 +182,7 @@ func (w *WireGuard) start() error { return nil } -func (w *WireGuard) Close() error { +func (w *Outbound) Close() error { if w.device != nil { w.device.Close() } @@ -194,12 +192,12 @@ func (w *WireGuard) Close() error { return nil } -func (w *WireGuard) InterfaceUpdated() { +func (w *Outbound) InterfaceUpdated() { w.device.BindUpdate() return } -func (w *WireGuard) onPauseUpdated(event int) { +func (w *Outbound) onPauseUpdated(event int) { switch event { case pause.EventDevicePaused: w.device.Down() @@ -208,7 +206,7 @@ func (w *WireGuard) onPauseUpdated(event int) { } } -func (w *WireGuard) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (w *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch network { case N.NetworkTCP: w.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -225,7 +223,7 @@ func (w *WireGuard) DialContext(ctx context.Context, network string, destination return w.tunDevice.DialContext(ctx, network, destination) } -func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (w *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { w.logger.InfoContext(ctx, "outbound packet connection to ", destination) if destination.IsFqdn() { destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) @@ -243,12 +241,12 @@ func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) ( // TODO // Deprecated -func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) +func (w *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return outbound.NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } // TODO // Deprecated -func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) +func (w *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return outbound.NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } diff --git a/route/dns.go b/route/dns.go index cc0760b851..98ea8d6b34 100644 --- a/route/dns.go +++ b/route/dns.go @@ -8,7 +8,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/outbound" + dnsOutbound "github.com/sagernet/sing-box/protocol/dns" "github.com/sagernet/sing-dns" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/buf" @@ -24,7 +24,7 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad metadata.Destination = M.Socksaddr{} for { conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) - err := outbound.HandleStreamDNSRequest(ctx, r, conn, metadata) + err := dnsOutbound.HandleStreamDNSRequest(ctx, r, conn, metadata) if err != nil { return err } @@ -48,7 +48,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB }) return } - err := outbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) + err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) if err != nil && !E.IsClosedOrCanceled(err) { r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) } diff --git a/route/route.go b/route/route.go index ebffddddbf..6c68cf79cb 100644 --- a/route/route.go +++ b/route/route.go @@ -12,12 +12,12 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "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" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" "github.com/sagernet/sing-mux" diff --git a/route/router.go b/route/router.go index 05349485d2..b6cc8051ba 100644 --- a/route/router.go +++ b/route/router.go @@ -99,7 +99,6 @@ func NewRouter( dnsOptions option.DNSOptions, ntpOptions option.NTPOptions, inbounds []option.Inbound, - platformInterface platform.Interface, ) (*Router, error) { router := &Router{ ctx: ctx, @@ -122,10 +121,13 @@ func NewRouter( defaultInterface: options.DefaultInterface, defaultMark: options.DefaultMark, pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: platformInterface, + platformInterface: service.FromContext[platform.Interface](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), needPackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { - return len(inbound.TunOptions.IncludePackage) > 0 || len(inbound.TunOptions.ExcludePackage) > 0 + if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { + return true + } + return false }), } router.dnsClient = dns.NewClient(dns.ClientOptions{ @@ -324,9 +326,15 @@ func NewRouter( router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range) } - usePlatformDefaultInterfaceMonitor := platformInterface != nil && platformInterface.UsePlatformDefaultInterfaceMonitor() + usePlatformDefaultInterfaceMonitor := router.platformInterface != nil && router.platformInterface.UsePlatformDefaultInterfaceMonitor() needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { - return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || inbound.TunOptions.AutoRoute + if httpMixedOptions, isHTTPMixed := inbound.Options.(*option.HTTPMixedInboundOptions); isHTTPMixed && httpMixedOptions.SetSystemProxy { + return true + } + if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { + return true + } + return false }) if !usePlatformDefaultInterfaceMonitor { @@ -339,7 +347,7 @@ func NewRouter( interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ InterfaceFinder: router.interfaceFinder, OverrideAndroidVPN: options.OverrideAndroidVPN, - UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(), + UnderNetworkExtension: router.platformInterface != nil && router.platformInterface.UnderNetworkExtension(), }) if err != nil { return nil, E.New("auto_detect_interface unsupported on current platform") @@ -348,7 +356,7 @@ func NewRouter( router.interfaceMonitor = interfaceMonitor } } else { - interfaceMonitor := platformInterface.CreateDefaultInterfaceMonitor(router.logger) + interfaceMonitor := router.platformInterface.CreateDefaultInterfaceMonitor(router.logger) interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) router.interfaceMonitor = interfaceMonitor } diff --git a/test/brutal_test.go b/test/brutal_test.go index 18aae2e201..ce1d2c2a8b 100644 --- a/test/brutal_test.go +++ b/test/brutal_test.go @@ -15,7 +15,7 @@ func TestBrutalShadowsocks(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -46,7 +46,7 @@ func TestBrutalShadowsocks(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -100,7 +100,7 @@ func TestBrutalTrojan(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -138,7 +138,7 @@ func TestBrutalTrojan(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -197,7 +197,7 @@ func TestBrutalTrojan(t *testing.T) { func TestBrutalVMess(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -227,7 +227,7 @@ func TestBrutalVMess(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -279,7 +279,7 @@ func TestBrutalVMess(t *testing.T) { func TestBrutalVLESS(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -326,7 +326,7 @@ func TestBrutalVLESS(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/direct_test.go b/test/direct_test.go index 1dbf1de1b6..c4fd8c5ea3 100644 --- a/test/direct_test.go +++ b/test/direct_test.go @@ -11,7 +11,7 @@ import ( // Since this is a feature one-off added by outsiders, I won't address these anymore. func _TestProxyProtocol(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -33,7 +33,7 @@ func _TestProxyProtocol(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index 1ca2121dae..c82b0d2974 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -6,7 +6,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" "github.com/gofrs/uuid/v5" ) @@ -14,7 +14,7 @@ import ( func TestTUICDomainUDP(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -49,7 +49,7 @@ func TestTUICDomainUDP(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/ech_test.go b/test/ech_test.go index 90eae1f48c..eeac1acb72 100644 --- a/test/ech_test.go +++ b/test/ech_test.go @@ -16,7 +16,7 @@ func TestECH(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -55,7 +55,7 @@ func TestECH(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -109,7 +109,7 @@ func TestECHQUIC(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -145,7 +145,7 @@ func TestECHQUIC(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -199,7 +199,7 @@ func TestECHHysteria2(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -235,7 +235,7 @@ func TestECHHysteria2(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/http_test.go b/test/http_test.go index 88385c2757..7e72400534 100644 --- a/test/http_test.go +++ b/test/http_test.go @@ -10,7 +10,7 @@ import ( func TestHTTPSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -31,7 +31,7 @@ func TestHTTPSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/hysteria2_test.go b/test/hysteria2_test.go index 9ca2f5d38d..665da552c3 100644 --- a/test/hysteria2_test.go +++ b/test/hysteria2_test.go @@ -28,7 +28,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { } } startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -63,7 +63,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -115,7 +115,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { func TestHysteria2Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeHysteria2, Hysteria2Options: option.Hysteria2InboundOptions{ @@ -167,7 +167,7 @@ func TestHysteria2Outbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -178,7 +178,7 @@ func TestHysteria2Outbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeHysteria2, Hysteria2Options: option.Hysteria2OutboundOptions{ diff --git a/test/hysteria_test.go b/test/hysteria_test.go index bde1b9fa73..dce00390a4 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -11,7 +11,7 @@ import ( func TestHysteriaSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -46,7 +46,7 @@ func TestHysteriaSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -98,7 +98,7 @@ func TestHysteriaSelf(t *testing.T) { func TestHysteriaInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeHysteria, HysteriaOptions: option.HysteriaInboundOptions{ @@ -149,7 +149,7 @@ func TestHysteriaOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -160,7 +160,7 @@ func TestHysteriaOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeHysteria, HysteriaOptions: option.HysteriaOutboundOptions{ diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go index 9505c217ff..c26c81a799 100644 --- a/test/inbound_detour_test.go +++ b/test/inbound_detour_test.go @@ -13,7 +13,7 @@ func TestChainedInbound(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -49,7 +49,7 @@ func TestChainedInbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/mux_cool_test.go b/test/mux_cool_test.go index 81130fad9a..e72f244fc5 100644 --- a/test/mux_cool_test.go +++ b/test/mux_cool_test.go @@ -37,7 +37,7 @@ func TestMuxCoolServer(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -81,7 +81,7 @@ func TestMuxCoolClient(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -92,7 +92,7 @@ func TestMuxCoolClient(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, VMessOptions: option.VMessOutboundOptions{ @@ -112,7 +112,7 @@ func TestMuxCoolClient(t *testing.T) { func TestMuxCoolSelf(t *testing.T) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -139,7 +139,7 @@ func TestMuxCoolSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/mux_test.go b/test/mux_test.go index 8d75518554..335def2e93 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -55,7 +55,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -81,7 +81,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -125,7 +125,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -154,7 +154,7 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/naive_test.go b/test/naive_test.go index 1a1547da17..fe3e7dce93 100644 --- a/test/naive_test.go +++ b/test/naive_test.go @@ -13,7 +13,7 @@ import ( func TestNaiveInboundWithNginx(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ @@ -59,7 +59,7 @@ func TestNaiveInboundWithNginx(t *testing.T) { func TestNaiveInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ @@ -103,7 +103,7 @@ func TestNaiveInbound(t *testing.T) { func TestNaiveHTTP3Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ diff --git a/test/shadowsocks_legacy_test.go b/test/shadowsocks_legacy_test.go index 8075a7df23..ae6f38e400 100644 --- a/test/shadowsocks_legacy_test.go +++ b/test/shadowsocks_legacy_test.go @@ -24,7 +24,7 @@ func testShadowsocksLegacy(t *testing.T, method string) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -35,7 +35,7 @@ func testShadowsocksLegacy(t *testing.T, method string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index 4ef1ee9d93..0f7af7650d 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -99,7 +99,7 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass Cmd: []string{"-s", F.ToString("127.0.0.1:", serverPort), "-b", F.ToString("0.0.0.0:", clientPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ @@ -124,7 +124,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas Cmd: []string{"-s", F.ToString("0.0.0.0:", serverPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -135,7 +135,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ @@ -154,7 +154,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas func testShadowsocksSelf(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -177,7 +177,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -221,7 +221,7 @@ func TestShadowsocksUoT(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -244,7 +244,7 @@ func TestShadowsocksUoT(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -289,7 +289,7 @@ func TestShadowsocksUoT(t *testing.T) { func testShadowsocks2022EIH(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -317,7 +317,7 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index 6f9ee1e566..71e8d9fa2a 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -37,7 +37,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) method := shadowaead_2022.List[0] ssPassword := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -80,7 +80,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ @@ -142,7 +142,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) func TestShadowTLSFallback(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeShadowTLS, ShadowTLSOptions: option.ShadowTLSInboundOptions{ @@ -189,7 +189,7 @@ func TestShadowTLSInbound(t *testing.T) { Cmd: []string{"--v3", "--threads", "1", "client", "--listen", "0.0.0.0:" + F.ToString(otherPort), "--server", "127.0.0.1:" + F.ToString(serverPort), "--sni", "google.com", "--password", password}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "in", @@ -232,7 +232,7 @@ func TestShadowTLSInbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -283,7 +283,7 @@ func TestShadowTLSOutbound(t *testing.T) { Env: []string{"RUST_LOG=trace"}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -306,7 +306,7 @@ func TestShadowTLSOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ diff --git a/test/ss_plugin_test.go b/test/ss_plugin_test.go index 94606b70fd..3f837b4e92 100644 --- a/test/ss_plugin_test.go +++ b/test/ss_plugin_test.go @@ -33,7 +33,7 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -44,7 +44,7 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ diff --git a/test/tfo_test.go b/test/tfo_test.go index 7bd34e2db7..458a936d6b 100644 --- a/test/tfo_test.go +++ b/test/tfo_test.go @@ -13,7 +13,7 @@ func TestTCPSlowOpen(t *testing.T) { method := shadowaead.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -37,7 +37,7 @@ func TestTCPSlowOpen(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/tls_test.go b/test/tls_test.go index cfc6c1a50b..b42d924f42 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -11,7 +11,7 @@ import ( func TestUTLS(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -46,7 +46,7 @@ func TestUTLS(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/trojan_test.go b/test/trojan_test.go index f88ec88502..1a206c66a4 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -20,7 +20,7 @@ func TestTrojanOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -31,7 +31,7 @@ func TestTrojanOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeTrojan, TrojanOptions: option.TrojanOutboundOptions{ @@ -57,7 +57,7 @@ func TestTrojanOutbound(t *testing.T) { func TestTrojanSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -92,7 +92,7 @@ func TestTrojanSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -140,7 +140,7 @@ func TestTrojanSelf(t *testing.T) { func TestTrojanPlainSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -167,7 +167,7 @@ func TestTrojanPlainSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/tuic_test.go b/test/tuic_test.go index 5b838f22f7..41fb759997 100644 --- a/test/tuic_test.go +++ b/test/tuic_test.go @@ -29,7 +29,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { udpRelayMode = "quic" } startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -62,7 +62,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -113,7 +113,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { func TestTUICInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeTUIC, TUICOptions: option.TUICInboundOptions{ @@ -160,7 +160,7 @@ func TestTUICOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -171,7 +171,7 @@ func TestTUICOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeTUIC, TUICOptions: option.TUICOutboundOptions{ diff --git a/test/v2ray_api_test.go b/test/v2ray_api_test.go index 1bea41a678..cd7ae2c445 100644 --- a/test/v2ray_api_test.go +++ b/test/v2ray_api_test.go @@ -14,7 +14,7 @@ import ( func TestV2RayAPI(t *testing.T) { i := startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "in", @@ -26,7 +26,7 @@ func TestV2RayAPI(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, Tag: "out", diff --git a/test/v2ray_grpc_test.go b/test/v2ray_grpc_test.go index fa43f753a1..5cf875435d 100644 --- a/test/v2ray_grpc_test.go +++ b/test/v2ray_grpc_test.go @@ -27,7 +27,7 @@ func testV2RayGRPCInbound(t *testing.T, forceLite bool) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -126,7 +126,7 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -138,7 +138,7 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, Tag: "vmess-out", diff --git a/test/v2ray_transport_test.go b/test/v2ray_transport_test.go index c7362f345f..27074e78e1 100644 --- a/test/v2ray_transport_test.go +++ b/test/v2ray_transport_test.go @@ -44,7 +44,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -80,7 +80,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -133,7 +133,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -169,7 +169,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -224,7 +224,7 @@ func TestVMessQUICSelf(t *testing.T) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -260,7 +260,7 @@ func TestVMessQUICSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -312,7 +312,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO user, err := uuid.DefaultGenerator.NewV4() require.NoError(t, err) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -340,7 +340,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/v2ray_ws_test.go b/test/v2ray_ws_test.go index 0e238c28b6..de8d4bdce6 100644 --- a/test/v2ray_ws_test.go +++ b/test/v2ray_ws_test.go @@ -61,7 +61,7 @@ func testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeade require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -158,7 +158,7 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -170,7 +170,7 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, Tag: "vmess-out", diff --git a/test/vmess_test.go b/test/vmess_test.go index fcf7bf8f47..9f81d9a015 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -181,7 +181,7 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authe }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -229,7 +229,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -240,7 +240,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, VMessOptions: option.VMessOutboundOptions{ @@ -263,7 +263,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo func testVMessSelf(t *testing.T, security string, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -291,7 +291,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/wireguard_test.go b/test/wireguard_test.go index 50e87ee04d..70c0e5a558 100644 --- a/test/wireguard_test.go +++ b/test/wireguard_test.go @@ -21,7 +21,7 @@ func _TestWireGuard(t *testing.T) { }) time.Sleep(5 * time.Second) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -32,7 +32,7 @@ func _TestWireGuard(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeWireGuard, WireGuardOptions: option.WireGuardOutboundOptions{ diff --git a/test/wrapper_test.go b/test/wrapper_test.go index d2b6b9ffc1..a7c23f335e 100644 --- a/test/wrapper_test.go +++ b/test/wrapper_test.go @@ -10,7 +10,7 @@ import ( ) func TestOptionsWrapper(t *testing.T) { - inbound := option.Inbound{ + inbound := option.LegacyInbound{ Type: C.TypeHTTP, HTTPOptions: option.HTTPMixedInboundOptions{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ diff --git a/transport/trojan/mux.go b/transport/trojan/mux.go index b1cc9985c9..0329bd40fe 100644 --- a/transport/trojan/mux.go +++ b/transport/trojan/mux.go @@ -8,12 +8,13 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/task" "github.com/sagernet/smux" ) -func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) error { +func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) error { session, err := smux.Server(conn, smuxConfig()) if err != nil { return err @@ -26,7 +27,7 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata if err != nil { return err } - go newMuxConnection(ctx, stream, metadata, handler) + go newMuxConnection(ctx, stream, metadata, handler, logger) } }) group.Cleanup(func() { @@ -35,10 +36,10 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata return group.Run(ctx) } -func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) { +func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) { err := newMuxConnection0(ctx, conn, metadata, handler) if err != nil { - handler.NewError(ctx, E.Cause(err, "process trojan-go multiplex connection")) + logger.ErrorContext(ctx, E.Cause(err, "process trojan-go multiplex connection")) } } diff --git a/transport/trojan/service.go b/transport/trojan/service.go index 97f674ab93..978d737fe5 100644 --- a/transport/trojan/service.go +++ b/transport/trojan/service.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" @@ -17,7 +18,6 @@ import ( type Handler interface { N.TCPConnectionHandler N.UDPConnectionHandler - E.Handler } type Service[K comparable] struct { @@ -25,14 +25,16 @@ type Service[K comparable] struct { keys map[[56]byte]K handler Handler fallbackHandler N.TCPConnectionHandler + logger logger.ContextLogger } -func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandler) *Service[K] { +func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandler, logger logger.ContextLogger) *Service[K] { return &Service[K]{ users: make(map[K][56]byte), keys: make(map[[56]byte]K), handler: handler, fallbackHandler: fallbackHandler, + logger: logger, } } @@ -110,7 +112,7 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata return s.handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata) // case CommandMux: default: - return HandleMuxConnection(ctx, conn, metadata, s.handler) + return HandleMuxConnection(ctx, conn, metadata, s.handler, s.logger) } } diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go index 6c534532a1..20e7c0790c 100644 --- a/transport/wireguard/client_bind.go +++ b/transport/wireguard/client_bind.go @@ -10,6 +10,7 @@ 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" "github.com/sagernet/sing/service" @@ -21,10 +22,10 @@ var _ conn.Bind = (*ClientBind)(nil) type ClientBind struct { ctx context.Context + logger logger.Logger pauseManager pause.Manager bindCtx context.Context bindDone context.CancelFunc - errorHandler E.Handler dialer N.Dialer reservedForEndpoint map[netip.AddrPort][3]uint8 connAccess sync.Mutex @@ -35,11 +36,11 @@ type ClientBind struct { reserved [3]uint8 } -func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind { +func NewClientBind(ctx context.Context, logger logger.Logger, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind { return &ClientBind{ ctx: ctx, + logger: logger, pauseManager: service.FromContext[pause.Manager](ctx), - errorHandler: errorHandler, dialer: dialer, reservedForEndpoint: make(map[netip.AddrPort][3]uint8), done: make(chan struct{}), @@ -115,7 +116,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) return default: } - c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server")) + c.logger.Error(E.Cause(err, "connect to server")) err = nil c.pauseManager.WaitActive() time.Sleep(time.Second) @@ -127,7 +128,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) select { case <-c.done: default: - c.errorHandler.NewError(context.Background(), E.Cause(err, "read packet")) + c.logger.Error(context.Background(), E.Cause(err, "read packet")) err = nil } return diff --git a/transport/wireguard/resolve.go b/transport/wireguard/resolve.go index 5b4124d201..d7a1d19c03 100644 --- a/transport/wireguard/resolve.go +++ b/transport/wireguard/resolve.go @@ -8,7 +8,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" ) From 4fe40fcee02d90ca0e94e6ab845d10aa4353caf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 17:23:00 +0800 Subject: [PATCH 0154/1272] Remove unused reject methods --- cmd/sing-box/cmd_format.go | 3 +-- constant/rule.go | 8 ++----- option/rule_action.go | 18 +++++++------- protocol/tun/inbound.go | 18 +++++++++----- route/route.go | 7 +++--- route/rule/rule_action.go | 49 +++++++++++++++++++++++++++----------- route/rule/rule_default.go | 4 ++-- route/rule/rule_dns.go | 4 ++-- 8 files changed, 65 insertions(+), 46 deletions(-) diff --git a/cmd/sing-box/cmd_format.go b/cmd/sing-box/cmd_format.go index 9856c76336..ab59c9ae5d 100644 --- a/cmd/sing-box/cmd_format.go +++ b/cmd/sing-box/cmd_format.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "context" "os" "path/filepath" @@ -39,7 +38,7 @@ func format() error { return err } for _, optionsEntry := range optionsList { - optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options) + optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options) if err != nil { return err } diff --git a/constant/rule.go b/constant/rule.go index c7717376c1..73227175d8 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -34,10 +34,6 @@ const ( ) const ( - RuleActionRejectMethodDefault = "default" - RuleActionRejectMethodReset = "reset" - RuleActionRejectMethodNetworkUnreachable = "network-unreachable" - RuleActionRejectMethodHostUnreachable = "host-unreachable" - RuleActionRejectMethodPortUnreachable = "port-unreachable" - RuleActionRejectMethodDrop = "drop" + RuleActionRejectMethodDefault = "default" + RuleActionRejectMethodDrop = "drop" ) diff --git a/option/rule_action.go b/option/rule_action.go index e752a2beaf..3a40e1c069 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -73,11 +73,9 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { } type _DNSRuleAction struct { - Action string `json:"action,omitempty"` - RouteOptions DNSRouteActionOptions `json:"-"` - RejectOptions RejectActionOptions `json:"-"` - SniffOptions RouteActionSniff `json:"-"` - ResolveOptions RouteActionResolve `json:"-"` + Action string `json:"action,omitempty"` + RouteOptions DNSRouteActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` } type DNSRuleAction _DNSRuleAction @@ -139,6 +137,7 @@ type DNSRouteActionOptions struct { type _RejectActionOptions struct { Method string `json:"method,omitempty"` + NoDrop bool `json:"no_drop,omitempty"` } type RejectActionOptions _RejectActionOptions @@ -151,14 +150,13 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { switch r.Method { case "", C.RuleActionRejectMethodDefault: r.Method = C.RuleActionRejectMethodDefault - case C.RuleActionRejectMethodReset, - C.RuleActionRejectMethodNetworkUnreachable, - C.RuleActionRejectMethodHostUnreachable, - C.RuleActionRejectMethodPortUnreachable, - C.RuleActionRejectMethodDrop: + case C.RuleActionRejectMethodDrop: default: return E.New("unknown reject method: " + r.Method) } + if r.Method == C.RuleActionRejectMethodDrop && r.NoDrop { + return E.New("no_drop is not allowed when method is drop") + } return nil } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index ff679c8ec8..4be30d61b7 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -343,19 +343,25 @@ func (t *Inbound) Start() error { if err != nil { return err } - monitor.Start("initiating tun stack") - err = tunStack.Start() - monitor.Finish() t.tunStack = tunStack - if err != nil { - return err - } t.logger.Info("started at ", t.tunOptions.Name) return nil } func (t *Inbound) PostStart() error { monitor := taskmonitor.New(t.logger, C.StartTimeout) + monitor.Start("starting tun stack") + err := t.tunStack.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "starting tun stack") + } + monitor.Start("starting tun interface") + err = t.tunIf.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "starting TUN interface") + } if t.autoRedirect != nil { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) for _, routeRuleSet := range t.routeRuleSet { diff --git a/route/route.go b/route/route.go index 6c68cf79cb..2c199a2d25 100644 --- a/route/route.go +++ b/route/route.go @@ -8,7 +8,6 @@ import ( "os" "os/user" "strings" - "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -107,7 +106,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad selectReturn = true case *rule.RuleActionReject: buf.ReleaseMulti(buffers) - N.CloseOnHandshakeFailure(conn, onClose, action.Error()) + N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) return nil case *rule.RuleActionHijackDNS: for _, buffer := range buffers { @@ -252,7 +251,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m selectReturn = true case *rule.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) - N.CloseOnHandshakeFailure(conn, onClose, syscall.ECONNREFUSED) + N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) return nil case *rule.RuleActionHijackDNS: r.hijackDNSPacket(ctx, conn, packetBuffers, metadata) @@ -317,7 +316,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error { if !isReject { return nil } - return rejectAction.Error() + return rejectAction.Error(nil) } func (r *Router) matchRule( diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 57b7364794..031f181c7c 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -1,10 +1,10 @@ package rule import ( + "context" "net/netip" - "os" "strings" - "syscall" + "sync" "time" "github.com/sagernet/sing-box/adapter" @@ -13,11 +13,15 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" "github.com/sagernet/sing-tun" + "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" + + "golang.org/x/sys/unix" ) -func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { +func NewRuleAction(logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { switch action.Action { case C.RuleActionTypeRoute: return &RuleActionRoute{ @@ -29,6 +33,8 @@ func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { case C.RuleActionTypeReject: return &RuleActionReject{ Method: action.RejectOptions.Method, + NoDrop: action.RejectOptions.NoDrop, + logger: logger, }, nil case C.RuleActionTypeHijackDNS: return &RuleActionHijackDNS{}, nil @@ -48,7 +54,7 @@ func NewRuleAction(action option.RuleAction) (adapter.RuleAction, error) { } } -func NewDNSRuleAction(action option.DNSRuleAction) adapter.RuleAction { +func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) adapter.RuleAction { switch action.Action { case C.RuleActionTypeRoute: return &RuleActionDNSRoute{ @@ -62,6 +68,8 @@ func NewDNSRuleAction(action option.DNSRuleAction) adapter.RuleAction { case C.RuleActionTypeReject: return &RuleActionReject{ Method: action.RejectOptions.Method, + NoDrop: action.RejectOptions.NoDrop, + logger: logger, } default: panic(F.ToString("unknown rule action: ", action.Action)) @@ -107,7 +115,11 @@ func (r *RuleActionReturn) String() string { } type RuleActionReject struct { - Method string + Method string + NoDrop bool + logger logger.ContextLogger + dropAccess sync.Mutex + dropCounter []time.Time } func (r *RuleActionReject) Type() string { @@ -121,21 +133,30 @@ func (r *RuleActionReject) String() string { return F.ToString("reject(", r.Method, ")") } -func (r *RuleActionReject) Error() error { +func (r *RuleActionReject) Error(ctx context.Context) error { + var returnErr error switch r.Method { - case C.RuleActionRejectMethodReset: - return os.ErrClosed - case C.RuleActionRejectMethodNetworkUnreachable: - return syscall.ENETUNREACH - case C.RuleActionRejectMethodHostUnreachable: - return syscall.EHOSTUNREACH - case C.RuleActionRejectMethodDefault, C.RuleActionRejectMethodPortUnreachable: - return syscall.ECONNREFUSED + case C.RuleActionRejectMethodDefault: + returnErr = unix.ECONNREFUSED case C.RuleActionRejectMethodDrop: return tun.ErrDrop default: panic(F.ToString("unknown reject method: ", r.Method)) } + r.dropAccess.Lock() + defer r.dropAccess.Unlock() + timeNow := time.Now() + r.dropCounter = common.Filter(r.dropCounter, func(t time.Time) bool { + return timeNow.Sub(t) <= 30*time.Second + }) + r.dropCounter = append(r.dropCounter, timeNow) + if len(r.dropCounter) > 50 { + if ctx != nil { + r.logger.DebugContext(ctx, "dropped due to flooding") + } + return tun.ErrDrop + } + return returnErr } type RuleActionHijackDNS struct{} diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 4f5d1e8a3c..a337c19f22 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -52,7 +52,7 @@ type RuleItem interface { } func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { - action, err := NewRuleAction(options.RuleAction) + action, err := NewRuleAction(logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } @@ -254,7 +254,7 @@ type LogicalRule struct { } func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { - action, err := NewRuleAction(options.RuleAction) + action, err := NewRuleAction(logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 6e57633d10..2218f6a30c 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -51,7 +51,7 @@ func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.Co rule := &DefaultDNSRule{ abstractDefaultRule: abstractDefaultRule{ invert: options.Invert, - action: NewDNSRuleAction(options.DNSRuleAction), + action: NewDNSRuleAction(logger, options.DNSRuleAction), }, } if len(options.Inbound) > 0 { @@ -287,7 +287,7 @@ func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.Co abstractLogicalRule: abstractLogicalRule{ rules: make([]adapter.HeadlessRule, len(options.Rules)), invert: options.Invert, - action: NewDNSRuleAction(options.DNSRuleAction), + action: NewDNSRuleAction(logger, options.DNSRuleAction), }, } switch options.Mode { From 313be3d7a4ae8ed1ea07e651f235785662ff440f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 17:30:40 +0800 Subject: [PATCH 0155/1272] Improve rule actions --- adapter/inbound.go | 4 +- constant/rule.go | 13 +-- option/rule.go | 14 +-- option/rule_action.go | 182 ++++++++++++++++++++++++++++++++----- option/rule_dns.go | 16 ++-- protocol/dns/handle.go | 1 + route/route.go | 130 ++++++++++++++------------ route/route_dns.go | 107 +++++++++++++++++----- route/rule/rule_action.go | 111 ++++++++++++++++++---- route/rule/rule_default.go | 4 +- 10 files changed, 433 insertions(+), 149 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index ca3e9e5900..f9ed17085a 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -57,7 +57,9 @@ type InboundContext struct { // Deprecated InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool - DNSServer string + UDPConnect bool + + DNSServer string DestinationAddresses []netip.Addr SourceGeoIPCode string diff --git a/constant/rule.go b/constant/rule.go index 73227175d8..b1f91c608c 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -25,12 +25,13 @@ const ( ) const ( - RuleActionTypeRoute = "route" - RuleActionTypeReturn = "return" - RuleActionTypeReject = "reject" - RuleActionTypeHijackDNS = "hijack-dns" - RuleActionTypeSniff = "sniff" - RuleActionTypeResolve = "resolve" + RuleActionTypeRoute = "route" + RuleActionTypeRouteOptions = "route-options" + RuleActionTypeDirect = "direct" + RuleActionTypeReject = "reject" + RuleActionTypeHijackDNS = "hijack-dns" + RuleActionTypeSniff = "sniff" + RuleActionTypeResolve = "resolve" ) const ( diff --git a/option/rule.go b/option/rule.go index 07e6ddbec4..952afa6146 100644 --- a/option/rule.go +++ b/option/rule.go @@ -109,7 +109,7 @@ type DefaultRule struct { RuleAction } -func (r *DefaultRule) MarshalJSON() ([]byte, error) { +func (r DefaultRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects(r.RawDefaultRule, r.RuleAction) } @@ -128,27 +128,27 @@ func (r *DefaultRule) IsValid() bool { return !reflect.DeepEqual(r, defaultValue) } -type _LogicalRule struct { +type RawLogicalRule struct { Mode string `json:"mode"` Rules []Rule `json:"rules,omitempty"` Invert bool `json:"invert,omitempty"` } type LogicalRule struct { - _LogicalRule + RawLogicalRule RuleAction } -func (r *LogicalRule) MarshalJSON() ([]byte, error) { - return badjson.MarshallObjects(r._LogicalRule, r.RuleAction) +func (r LogicalRule) MarshalJSON() ([]byte, error) { + return badjson.MarshallObjects(r.RawLogicalRule, r.RuleAction) } func (r *LogicalRule) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &r._LogicalRule) + err := json.Unmarshal(data, &r.RawLogicalRule) if err != nil { return err } - return badjson.UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) + return badjson.UnmarshallExcluded(data, &r.RawLogicalRule, &r.RuleAction) } func (r *LogicalRule) IsValid() bool { diff --git a/option/rule_action.go b/option/rule_action.go index 3a40e1c069..edc197de0c 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -1,30 +1,41 @@ package option import ( + "fmt" + "time" + C "github.com/sagernet/sing-box/constant" + dns "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" ) type _RuleAction struct { - Action string `json:"action,omitempty"` - RouteOptions RouteActionOptions `json:"-"` - RejectOptions RejectActionOptions `json:"-"` - SniffOptions RouteActionSniff `json:"-"` - ResolveOptions RouteActionResolve `json:"-"` + Action string `json:"action,omitempty"` + RouteOptions RouteActionOptions `json:"-"` + RouteOptionsOptions RouteOptionsActionOptions `json:"-"` + DirectOptions DirectActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` + SniffOptions RouteActionSniff `json:"-"` + ResolveOptions RouteActionResolve `json:"-"` } type RuleAction _RuleAction func (r RuleAction) MarshalJSON() ([]byte, error) { + if r.Action == "" { + return json.Marshal(struct{}{}) + } var v any switch r.Action { case C.RuleActionTypeRoute: r.Action = "" v = r.RouteOptions - case C.RuleActionTypeReturn: - v = nil + case C.RuleActionTypeRouteOptions: + v = r.RouteOptionsOptions + case C.RuleActionTypeDirect: + v = r.DirectOptions case C.RuleActionTypeReject: v = r.RejectOptions case C.RuleActionTypeHijackDNS: @@ -52,8 +63,10 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { case "", C.RuleActionTypeRoute: r.Action = C.RuleActionTypeRoute v = &r.RouteOptions - case C.RuleActionTypeReturn: - v = nil + case C.RuleActionTypeRouteOptions: + v = &r.RouteOptionsOptions + case C.RuleActionTypeDirect: + v = &r.DirectOptions case C.RuleActionTypeReject: v = &r.RejectOptions case C.RuleActionTypeHijackDNS: @@ -73,29 +86,30 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { } type _DNSRuleAction struct { - Action string `json:"action,omitempty"` - RouteOptions DNSRouteActionOptions `json:"-"` - RejectOptions RejectActionOptions `json:"-"` + Action string `json:"action,omitempty"` + RouteOptions DNSRouteActionOptions `json:"-"` + RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` } type DNSRuleAction _DNSRuleAction func (r DNSRuleAction) MarshalJSON() ([]byte, error) { + if r.Action == "" { + return json.Marshal(struct{}{}) + } var v any switch r.Action { case C.RuleActionTypeRoute: r.Action = "" v = r.RouteOptions - case C.RuleActionTypeReturn: - v = nil + case C.RuleActionTypeRouteOptions: + v = r.RouteOptionsOptions case C.RuleActionTypeReject: v = r.RejectOptions default: return nil, E.New("unknown DNS rule action: " + r.Action) } - if v == nil { - return badjson.MarshallObjects((_DNSRuleAction)(r)) - } return badjson.MarshallObjects((_DNSRuleAction)(r), v) } @@ -109,8 +123,8 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { case "", C.RuleActionTypeRoute: r.Action = C.RuleActionTypeRoute v = &r.RouteOptions - case C.RuleActionTypeReturn: - v = nil + case C.RuleActionTypeRouteOptions: + v = &r.RouteOptionsOptions case C.RuleActionTypeReject: v = &r.RejectOptions default: @@ -123,18 +137,136 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { return badjson.UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) } -type RouteActionOptions struct { - Outbound string `json:"outbound"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` +type _RouteActionOptions struct { + Outbound string `json:"outbound,omitempty"` +} + +type RouteActionOptions _RouteActionOptions + +func (r *RouteActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_RouteActionOptions)(r)) + if err != nil { + return err + } + if r.Outbound == "" { + return E.New("missing outbound") + } + return nil +} + +type _RouteOptionsActionOptions struct { + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` +} + +type RouteOptionsActionOptions _RouteOptionsActionOptions + +func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_RouteOptionsActionOptions)(r)) + if err != nil { + return err + } + if *r == (RouteOptionsActionOptions{}) { + return E.New("empty route option action") + } + return nil +} + +type _DNSRouteActionOptions struct { + Server string `json:"server,omitempty"` + // Deprecated: Use DNSRouteOptionsActionOptions instead. + DisableCache bool `json:"disable_cache,omitempty"` + // Deprecated: Use DNSRouteOptionsActionOptions instead. + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + // Deprecated: Use DNSRouteOptionsActionOptions instead. + ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` +} + +type DNSRouteActionOptions _DNSRouteActionOptions + +func (r *DNSRouteActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DNSRouteActionOptions)(r)) + if err != nil { + return err + } + if r.Server == "" { + return E.New("missing server") + } + return nil } -type DNSRouteActionOptions struct { - Server string `json:"server"` +type _DNSRouteOptionsActionOptions struct { DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` } +type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions + +func (r *DNSRouteOptionsActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DNSRouteOptionsActionOptions)(r)) + if err != nil { + return err + } + if *r == (DNSRouteOptionsActionOptions{}) { + return E.New("empty DNS route option action") + } + return nil +} + +type _DirectActionOptions DialerOptions + +type DirectActionOptions _DirectActionOptions + +func (d DirectActionOptions) Descriptions() []string { + var descriptions []string + if d.BindInterface != "" { + descriptions = append(descriptions, "bind_interface="+d.BindInterface) + } + if d.Inet4BindAddress != nil { + descriptions = append(descriptions, "inet4_bind_address="+d.Inet4BindAddress.Build().String()) + } + if d.Inet6BindAddress != nil { + descriptions = append(descriptions, "inet6_bind_address="+d.Inet6BindAddress.Build().String()) + } + if d.RoutingMark != 0 { + descriptions = append(descriptions, "routing_mark="+fmt.Sprintf("0x%x", d.RoutingMark)) + } + if d.ReuseAddr { + descriptions = append(descriptions, "reuse_addr") + } + if d.ConnectTimeout != 0 { + descriptions = append(descriptions, "connect_timeout="+time.Duration(d.ConnectTimeout).String()) + } + if d.TCPFastOpen { + descriptions = append(descriptions, "tcp_fast_open") + } + if d.TCPMultiPath { + descriptions = append(descriptions, "tcp_multi_path") + } + if d.UDPFragment != nil { + descriptions = append(descriptions, "udp_fragment="+fmt.Sprint(*d.UDPFragment)) + } + if d.DomainStrategy != DomainStrategy(dns.DomainStrategyAsIS) { + descriptions = append(descriptions, "domain_strategy="+d.DomainStrategy.String()) + } + if d.FallbackDelay != 0 { + descriptions = append(descriptions, "fallback_delay="+time.Duration(d.FallbackDelay).String()) + } + return descriptions +} + +func (d *DirectActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DirectActionOptions)(d)) + if err != nil { + return err + } + if d.Detour != "" { + return E.New("detour is not available in the current context") + } + return nil +} + type _RejectActionOptions struct { Method string `json:"method,omitempty"` NoDrop bool `json:"no_drop,omitempty"` @@ -155,7 +287,7 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { return E.New("unknown reject method: " + r.Method) } if r.Method == C.RuleActionRejectMethodDrop && r.NoDrop { - return E.New("no_drop is not allowed when method is drop") + return E.New("no_drop is not available in current context") } return nil } diff --git a/option/rule_dns.go b/option/rule_dns.go index 8c4b6ab83b..0683e16a47 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -111,7 +111,7 @@ type DefaultDNSRule struct { DNSRuleAction } -func (r *DefaultDNSRule) MarshalJSON() ([]byte, error) { +func (r DefaultDNSRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) } @@ -123,34 +123,34 @@ func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { return badjson.UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) } -func (r *DefaultDNSRule) IsValid() bool { +func (r DefaultDNSRule) IsValid() bool { var defaultValue DefaultDNSRule defaultValue.Invert = r.Invert defaultValue.DNSRuleAction = r.DNSRuleAction return !reflect.DeepEqual(r, defaultValue) } -type _LogicalDNSRule struct { +type RawLogicalDNSRule struct { Mode string `json:"mode"` Rules []DNSRule `json:"rules,omitempty"` Invert bool `json:"invert,omitempty"` } type LogicalDNSRule struct { - _LogicalDNSRule + RawLogicalDNSRule DNSRuleAction } -func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) { - return badjson.MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) +func (r LogicalDNSRule) MarshalJSON() ([]byte, error) { + return badjson.MarshallObjects(r.RawLogicalDNSRule, r.DNSRuleAction) } func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &r._LogicalDNSRule) + err := json.Unmarshal(data, &r.RawLogicalDNSRule) if err != nil { return err } - return badjson.UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcluded(data, &r.RawLogicalDNSRule, &r.DNSRuleAction) } func (r *LogicalDNSRule) IsValid() bool { diff --git a/protocol/dns/handle.go b/protocol/dns/handle.go index 23ed1c0c79..bc58d9e208 100644 --- a/protocol/dns/handle.go +++ b/protocol/dns/handle.go @@ -43,6 +43,7 @@ func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net go func() error { response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) if err != nil { + conn.Close() return err } responseBuffer := buf.NewPacket() diff --git a/route/route.go b/route/route.go index 2c199a2d25..854fa4f19f 100644 --- a/route/route.go +++ b/route/route.go @@ -87,23 +87,34 @@ 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, -1) + selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, conn, nil) if err != nil { return err } - var selectedOutbound adapter.Outbound - var selectReturn bool + var ( + // selectedOutbound adapter.Outbound + selectedDialer N.Dialer + selectedTag string + selectedDescription string + ) if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - var loaded bool - selectedOutbound, loaded = r.Outbound(action.Outbound) + selectedOutbound, loaded := r.Outbound(action.Outbound) if !loaded { buf.ReleaseMulti(buffers) return E.New("outbound not found: ", action.Outbound) } - case *rule.RuleActionReturn: - selectReturn = true + if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) { + buf.ReleaseMulti(buffers) + return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) + } + selectedDialer = selectedOutbound + selectedTag = selectedOutbound.Tag() + selectedDescription = F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + case *rule.RuleActionDirect: + selectedDialer = action.Dialer + selectedDescription = action.String() case *rule.RuleActionReject: buf.ReleaseMulti(buffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) @@ -116,17 +127,16 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad return nil } } - if selectedRule == nil || selectReturn { + if selectedRule == nil { if r.defaultOutboundForConnection == nil { buf.ReleaseMulti(buffers) return E.New("missing default outbound with TCP support") } - selectedOutbound = r.defaultOutboundForConnection - } - if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) { - buf.ReleaseMulti(buffers) - return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) + selectedDialer = r.defaultOutboundForConnection + selectedTag = r.defaultOutboundForConnection.Tag() + selectedDescription = F.ToString("outbound/", r.defaultOutboundForConnection.Type(), "[", r.defaultOutboundForConnection.Tag(), "]") } + for _, buffer := range buffers { conn = bufio.NewCachedConn(conn, buffer) } @@ -137,10 +147,10 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad } if r.v2rayServer != nil { if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedConnection(metadata.Inbound, selectedOutbound.Tag(), metadata.User, conn) + conn = statsService.RoutedConnection(metadata.Inbound, selectedTag, metadata.User, conn) } } - legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler) + legacyOutbound, isLegacy := selectedDialer.(adapter.ConnectionHandler) if isLegacy { err = legacyOutbound.NewConnection(ctx, conn, metadata) if err != nil { @@ -148,7 +158,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if onClose != nil { onClose(err) } - return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + return E.Cause(err, selectedDescription) } else { if onClose != nil { onClose(nil) @@ -157,13 +167,13 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad return nil } // TODO - err = outbound.NewConnection(ctx, selectedOutbound, conn, metadata) + err = outbound.NewConnection(ctx, selectedDialer, conn, metadata) if err != nil { conn.Close() if onClose != nil { onClose(err) } - return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + return E.Cause(err, selectedDescription) } else { if onClose != nil { onClose(nil) @@ -231,24 +241,34 @@ 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, -1) + selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, nil, conn) if err != nil { return err } - var selectedOutbound adapter.Outbound + var ( + selectedDialer N.Dialer + selectedTag string + selectedDescription string + ) var selectReturn bool if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - var loaded bool - selectedOutbound, loaded = r.Outbound(action.Outbound) + selectedOutbound, loaded := r.Outbound(action.Outbound) if !loaded { N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("outbound not found: ", action.Outbound) } - metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping - case *rule.RuleActionReturn: - selectReturn = true + if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { + N.ReleaseMultiPacketBuffer(packetBuffers) + return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) + } + selectedDialer = selectedOutbound + selectedTag = selectedOutbound.Tag() + selectedDescription = F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + case *rule.RuleActionDirect: + selectedDialer = action.Dialer + selectedDescription = action.String() case *rule.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) @@ -263,11 +283,9 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("missing default outbound with UDP support") } - selectedOutbound = r.defaultOutboundForPacketConnection - } - if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { - N.ReleaseMultiPacketBuffer(packetBuffers) - return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) + selectedDialer = r.defaultOutboundForPacketConnection + selectedTag = r.defaultOutboundForPacketConnection.Tag() + selectedDescription = F.ToString("outbound/", r.defaultOutboundForPacketConnection.Type(), "[", r.defaultOutboundForPacketConnection.Tag(), "]") } for _, buffer := range packetBuffers { conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) @@ -280,32 +298,32 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } if r.v2rayServer != nil { if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedPacketConnection(metadata.Inbound, selectedOutbound.Tag(), metadata.User, conn) + conn = statsService.RoutedPacketConnection(metadata.Inbound, selectedTag, metadata.User, conn) } } if metadata.FakeIP { conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) } - legacyOutbound, isLegacy := selectedOutbound.(adapter.PacketConnectionHandler) + legacyOutbound, isLegacy := selectedDialer.(adapter.PacketConnectionHandler) if isLegacy { err = legacyOutbound.NewPacketConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + return E.Cause(err, selectedDescription) } return nil } // TODO - err = outbound.NewPacketConnection(ctx, selectedOutbound, conn, metadata) + err = outbound.NewPacketConnection(ctx, selectedDialer, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - return E.Cause(err, "outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") + return E.Cause(err, selectedDescription) } return nil } func (r *Router) PreMatch(metadata adapter.InboundContext) error { - selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil, -1) + selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil) if err != nil { return err } @@ -321,7 +339,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error { func (r *Router) matchRule( ctx context.Context, metadata *adapter.InboundContext, preMatch bool, - inputConn net.Conn, inputPacketConn N.PacketConn, ruleIndex int, + inputConn net.Conn, inputPacketConn N.PacketConn, ) ( selectedRule adapter.Rule, selectedRuleIndex int, buffers []*buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error, @@ -416,24 +434,10 @@ func (r *Router) matchRule( } match: - for ruleIndex < len(r.rules) { - rules := r.rules - if ruleIndex != -1 { - rules = rules[ruleIndex+1:] - } - var ( - currentRule adapter.Rule - currentRuleIndex int - matched bool - ) - for currentRuleIndex, currentRule = range rules { - if currentRule.Match(metadata) { - matched = true - break - } - } - if !matched { - break + for currentRuleIndex, currentRule := range r.rules { + metadata.ResetRuleCache() + if !currentRule.Match(metadata) { + continue } if !preMatch { ruleDescription := currentRule.String() @@ -444,7 +448,7 @@ match: } } else { switch currentRule.Action().Type() { - case C.RuleActionTypeReject, C.RuleActionTypeResolve: + case C.RuleActionTypeReject: ruleDescription := currentRule.String() if ruleDescription != "" { r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) @@ -454,6 +458,12 @@ match: } } switch action := currentRule.Action().(type) { + case *rule.RuleActionRoute: + metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping + metadata.UDPConnect = action.UDPConnect + case *rule.RuleActionRouteOptions: + metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping + metadata.UDPConnect = action.UDPConnect case *rule.RuleActionSniff: if !preMatch { newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) @@ -476,12 +486,16 @@ match: if fatalErr != nil { return } - default: + } + actionType := currentRule.Action().Type() + if actionType == C.RuleActionTypeRoute || + actionType == C.RuleActionTypeReject || + actionType == C.RuleActionTypeHijackDNS || + (actionType == C.RuleActionTypeSniff && preMatch) { selectedRule = currentRule selectedRuleIndex = currentRuleIndex break match } - ruleIndex = currentRuleIndex } if !preMatch && metadata.Destination.Addr.IsUnspecified() { newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) diff --git a/route/route_dns.go b/route/route_dns.go index 60aff6a9ff..c11c07fe1b 100644 --- a/route/route_dns.go +++ b/route/route_dns.go @@ -8,8 +8,10 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/cache" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -48,38 +50,63 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, if ruleIndex != -1 { dnsRules = dnsRules[ruleIndex+1:] } - for currentRuleIndex, rule := range dnsRules { - if rule.WithAddressLimit() && !isAddressQuery { + for currentRuleIndex, currentRule := range dnsRules { + if currentRule.WithAddressLimit() && !isAddressQuery { continue } metadata.ResetRuleCache() - if rule.Match(metadata) { + if currentRule.Match(metadata) { displayRuleIndex := currentRuleIndex if displayRuleIndex != -1 { displayRuleIndex += displayRuleIndex + 1 } - if routeAction, isRoute := rule.Action().(*R.RuleActionDNSRoute); isRoute { - transport, loaded := r.transportMap[routeAction.Server] + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + } + switch action := currentRule.Action().(type) { + case *R.RuleActionDNSRoute: + transport, loaded := r.transportMap[action.Server] if !loaded { - r.dnsLogger.ErrorContext(ctx, "transport not found: ", routeAction.Server) + r.dnsLogger.ErrorContext(ctx, "transport not found: ", action.Server) continue } _, isFakeIP := transport.(adapter.FakeIPTransport) if isFakeIP && !allowFakeIP { continue } - options.DisableCache = isFakeIP || routeAction.DisableCache - options.RewriteTTL = routeAction.RewriteTTL - options.ClientSubnet = routeAction.ClientSubnet + if isFakeIP || action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL + } + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet + } if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { options.Strategy = domainStrategy } else { options.Strategy = r.defaultDomainStrategy } - r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", rule.Action()) - return transport, options, rule, currentRuleIndex - } else { - return nil, options, rule, currentRuleIndex + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + return transport, options, currentRule, currentRuleIndex + case *R.RuleActionDNSRouteOptions: + if action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL + } + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet + } + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + case *R.RuleActionReject: + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + return nil, options, currentRule, currentRuleIndex } } } @@ -93,9 +120,19 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, } func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - if len(message.Question) > 0 { - r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String())) + if len(message.Question) != 1 { + r.dnsLogger.WarnContext(ctx, "bad question size: ", len(message.Question)) + responseMessage := mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Response: true, + Rcode: mDNS.RcodeFormatError, + }, + Question: message.Question, + } + return &responseMessage, nil } + r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String())) var ( response *mDNS.Msg cached bool @@ -107,16 +144,14 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er var metadata *adapter.InboundContext ctx, metadata = adapter.ExtendContext(ctx) metadata.Destination = M.Socksaddr{} - if len(message.Question) > 0 { - metadata.QueryType = message.Question[0].Qtype - switch metadata.QueryType { - case mDNS.TypeA: - metadata.IPVersion = 4 - case mDNS.TypeAAAA: - metadata.IPVersion = 6 - } - metadata.Domain = fqdnToDomain(message.Question[0].Name) + metadata.QueryType = message.Question[0].Qtype + switch metadata.QueryType { + case mDNS.TypeA: + metadata.IPVersion = 4 + case mDNS.TypeAAAA: + metadata.IPVersion = 6 } + metadata.Domain = fqdnToDomain(message.Question[0].Name) var ( options dns.QueryOptions rule adapter.DNSRule @@ -127,6 +162,17 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er dnsCtx := adapter.OverrideContext(ctx) var addressLimit bool transport, options, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) + if rule != nil { + switch action := rule.Action().(type) { + case *R.RuleActionReject: + switch action.Method { + case C.RuleActionRejectMethodDefault: + return dns.FixedResponse(message.Id, message.Question[0], nil, 0), nil + case C.RuleActionRejectMethodDrop: + return nil, tun.ErrDrop + } + } + } if rule != nil && rule.WithAddressLimit() { addressLimit = true response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(response *mDNS.Msg) bool { @@ -164,7 +210,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er if err != nil { return nil, err } - if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { + if r.dnsReverseMapping != nil && response != nil && len(response.Answer) > 0 { if _, isFakeIP := transport.(adapter.FakeIPTransport); !isFakeIP { for _, answer := range response.Answer { switch record := answer.(type) { @@ -238,6 +284,17 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS if strategy != dns.DomainStrategyAsIS { options.Strategy = strategy } + if rule != nil { + switch action := rule.Action().(type) { + case *R.RuleActionReject: + switch action.Method { + case C.RuleActionRejectMethodDefault: + return nil, nil + case C.RuleActionRejectMethodDrop: + return nil, tun.ErrDrop + } + } + } if rule != nil && rule.WithAddressLimit() { addressLimit = true responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, options, func(responseAddrs []netip.Addr) bool { diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 031f181c7c..620260d078 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -5,9 +5,11 @@ import ( "net/netip" "strings" "sync" + "syscall" "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" @@ -17,19 +19,42 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" - - "golang.org/x/sys/unix" + N "github.com/sagernet/sing/common/network" ) -func NewRuleAction(logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { +func NewRuleAction(router adapter.Router, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { switch action.Action { + case "": + return nil, nil case C.RuleActionTypeRoute: return &RuleActionRoute{ - Outbound: action.RouteOptions.Outbound, - UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, + Outbound: action.RouteOptions.Outbound, + }, nil + case C.RuleActionTypeRouteOptions: + return &RuleActionRouteOptions{ + UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, + UDPConnect: action.RouteOptionsOptions.UDPConnect, + }, nil + case C.RuleActionTypeDirect: + directDialer, err := dialer.New(router, option.DialerOptions(action.DirectOptions)) + if err != nil { + return nil, err + } + var description string + descriptions := action.DirectOptions.Descriptions() + switch len(descriptions) { + case 0: + case 1: + description = F.ToString("(", descriptions[0], ")") + case 2: + description = F.ToString("(", descriptions[0], ",", descriptions[1], ")") + default: + description = F.ToString("(", descriptions[0], ",", descriptions[1], ",...)") + } + return &RuleActionDirect{ + Dialer: directDialer, + description: description, }, nil - case C.RuleActionTypeReturn: - return &RuleActionReturn{}, nil case C.RuleActionTypeReject: return &RuleActionReject{ Method: action.RejectOptions.Method, @@ -56,6 +81,8 @@ func NewRuleAction(logger logger.ContextLogger, action option.RuleAction) (adapt func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) adapter.RuleAction { switch action.Action { + case "": + return nil case C.RuleActionTypeRoute: return &RuleActionDNSRoute{ Server: action.RouteOptions.Server, @@ -63,8 +90,12 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) RewriteTTL: action.RouteOptions.RewriteTTL, ClientSubnet: action.RouteOptions.ClientSubnet.Build(), } - case C.RuleActionTypeReturn: - return &RuleActionReturn{} + case C.RuleActionTypeRouteOptions: + return &RuleActionDNSRouteOptions{ + DisableCache: action.RouteOptionsOptions.DisableCache, + RewriteTTL: action.RouteOptionsOptions.RewriteTTL, + ClientSubnet: action.RouteOptionsOptions.ClientSubnet.Build(), + } case C.RuleActionTypeReject: return &RuleActionReject{ Method: action.RejectOptions.Method, @@ -77,8 +108,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) } type RuleActionRoute struct { - Outbound string - UDPDisableDomainUnmapping bool + Outbound string } func (r *RuleActionRoute) Type() string { @@ -89,6 +119,26 @@ func (r *RuleActionRoute) String() string { return F.ToString("route(", r.Outbound, ")") } +type RuleActionRouteOptions struct { + UDPDisableDomainUnmapping bool + UDPConnect bool +} + +func (r *RuleActionRouteOptions) Type() string { + return C.RuleActionTypeRouteOptions +} + +func (r *RuleActionRouteOptions) String() string { + var descriptions []string + if r.UDPDisableDomainUnmapping { + descriptions = append(descriptions, "udp-disable-domain-unmapping") + } + if r.UDPConnect { + descriptions = append(descriptions, "udp-connect") + } + return F.ToString("route-options(", strings.Join(descriptions, ","), ")") +} + type RuleActionDNSRoute struct { Server string DisableCache bool @@ -104,14 +154,41 @@ func (r *RuleActionDNSRoute) String() string { return F.ToString("route(", r.Server, ")") } -type RuleActionReturn struct{} +type RuleActionDNSRouteOptions struct { + DisableCache bool + RewriteTTL *uint32 + ClientSubnet netip.Prefix +} + +func (r *RuleActionDNSRouteOptions) Type() string { + return C.RuleActionTypeRouteOptions +} + +func (r *RuleActionDNSRouteOptions) String() string { + var descriptions []string + if r.DisableCache { + descriptions = append(descriptions, "disable-cache") + } + if r.RewriteTTL != nil { + descriptions = append(descriptions, F.ToString("rewrite-ttl(", *r.RewriteTTL, ")")) + } + if r.ClientSubnet.IsValid() { + descriptions = append(descriptions, F.ToString("client-subnet(", r.ClientSubnet, ")")) + } + return F.ToString("route-options(", strings.Join(descriptions, ","), ")") +} + +type RuleActionDirect struct { + Dialer N.Dialer + description string +} -func (r *RuleActionReturn) Type() string { - return C.RuleActionTypeReturn +func (r *RuleActionDirect) Type() string { + return C.RuleActionTypeDirect } -func (r *RuleActionReturn) String() string { - return "return" +func (r *RuleActionDirect) String() string { + return "direct" + r.description } type RuleActionReject struct { @@ -137,7 +214,7 @@ func (r *RuleActionReject) Error(ctx context.Context) error { var returnErr error switch r.Method { case C.RuleActionRejectMethodDefault: - returnErr = unix.ECONNREFUSED + returnErr = syscall.ECONNREFUSED case C.RuleActionRejectMethodDrop: return tun.ErrDrop default: diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index a337c19f22..566c816e11 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -52,7 +52,7 @@ type RuleItem interface { } func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { - action, err := NewRuleAction(logger, options.RuleAction) + action, err := NewRuleAction(router, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } @@ -254,7 +254,7 @@ type LogicalRule struct { } func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { - action, err := NewRuleAction(logger, options.RuleAction) + action, err := NewRuleAction(router, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } From 59a607e303760f64f671fab3a8625a4958ea473c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 7 Nov 2024 12:02:36 +0800 Subject: [PATCH 0156/1272] Implement new deprecated warnings --- experimental/deprecated/constants.go | 30 ++++++++++++++++++++++++++++ experimental/deprecated/manager.go | 1 - experimental/libbox/service.go | 4 ++-- option/outbound.go | 11 ++++++++++ option/rule_action.go | 15 +++++++------- option/rule_dns.go | 15 +++++++------- 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index f5b67119f2..8f7eee11ff 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -86,9 +86,39 @@ var OptionTUNAddressX = Note{ 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 OptionLegacyDNSRouteOptions = Note{ + Name: "legacy-dns-route-options", + Description: "legacy dns route options", + DeprecatedVersion: "1.11.0", + ScheduledVersion: "1.12.0", + EnvName: "LEGACY_DNS_ROUTE_OPTIONS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-dns-route-options-to-rule-actions", +} + var Options = []Note{ OptionBadMatchSource, OptionGEOIP, OptionGEOSITE, OptionTUNAddressX, + OptionSpecialOutbounds, + OptionInboundOptions, + OptionLegacyDNSRouteOptions, } diff --git a/experimental/deprecated/manager.go b/experimental/deprecated/manager.go index d12acf48fa..d7cae1797a 100644 --- a/experimental/deprecated/manager.go +++ b/experimental/deprecated/manager.go @@ -2,7 +2,6 @@ package deprecated import ( "context" - "github.com/sagernet/sing/service" ) diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index f6f56b6f6f..24fbdb4c94 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -43,16 +43,16 @@ type BoxService struct { func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) + ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) options, err := parseConfig(ctx, configContent) if err != nil { return nil, err } runtimeDebug.FreeOSMemory() ctx, cancel := context.WithCancel(ctx) - ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) - ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} ctx = service.ContextWith[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ diff --git a/option/outbound.go b/option/outbound.go index 00a20aa577..1dddb35487 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -3,6 +3,8 @@ package option 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" @@ -35,6 +37,10 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err if registry == nil { return E.New("missing outbound options registry in context") } + switch h.Type { + case C.TypeBlock, C.TypeDNS: + deprecated.Report(ctx, deprecated.OptionSpecialOutbounds) + } options, loaded := registry.CreateOptions(h.Type) if !loaded { return E.New("unknown outbound type: ", h.Type) @@ -43,6 +49,11 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err if err != nil { return err } + if listenWrapper, isListen := options.(ListenOptionsWrapper); isListen { + if listenWrapper.TakeListenOptions().InboundOptions != (InboundOptions{}) { + deprecated.Report(ctx, deprecated.OptionInboundOptions) + } + } h.Options = options return nil } diff --git a/option/rule_action.go b/option/rule_action.go index edc197de0c..a7244e179b 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -1,10 +1,12 @@ package option import ( + "context" "fmt" "time" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" dns "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -113,7 +115,7 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects((_DNSRuleAction)(r), v) } -func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { +func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) error { err := json.Unmarshal(data, (*_DNSRuleAction)(r)) if err != nil { return err @@ -130,11 +132,7 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { default: return E.New("unknown DNS rule action: " + r.Action) } - if v == nil { - // check unknown fields - return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{}) - } - return badjson.UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) + return badjson.UnmarshallExcludedContext(ctx, data, (*_DNSRuleAction)(r), v) } type _RouteActionOptions struct { @@ -184,7 +182,7 @@ type _DNSRouteActionOptions struct { type DNSRouteActionOptions _DNSRouteActionOptions -func (r *DNSRouteActionOptions) UnmarshalJSON(data []byte) error { +func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data []byte) error { err := json.Unmarshal(data, (*_DNSRouteActionOptions)(r)) if err != nil { return err @@ -192,6 +190,9 @@ func (r *DNSRouteActionOptions) UnmarshalJSON(data []byte) error { if r.Server == "" { return E.New("missing server") } + if r.DisableCache || r.RewriteTTL != nil || r.ClientSubnet != nil { + deprecated.Report(ctx, deprecated.OptionLegacyDNSRouteOptions) + } return nil } diff --git a/option/rule_dns.go b/option/rule_dns.go index 0683e16a47..e7e454bb0e 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -1,6 +1,7 @@ package option import ( + "context" "reflect" C "github.com/sagernet/sing-box/constant" @@ -32,7 +33,7 @@ func (r DNSRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects((_DNSRule)(r), v) } -func (r *DNSRule) UnmarshalJSON(bytes []byte) error { +func (r *DNSRule) UnmarshalJSONContext(ctx context.Context, bytes []byte) error { err := json.Unmarshal(bytes, (*_DNSRule)(r)) if err != nil { return err @@ -47,7 +48,7 @@ func (r *DNSRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = badjson.UnmarshallExcluded(bytes, (*_DNSRule)(r), v) + err = badjson.UnmarshallExcludedContext(ctx, bytes, (*_DNSRule)(r), v) if err != nil { return err } @@ -115,12 +116,12 @@ func (r DefaultDNSRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) } -func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &r.RawDefaultDNSRule) +func (r *DefaultDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error { + err := json.UnmarshalContext(ctx, data, &r.RawDefaultDNSRule) if err != nil { return err } - return badjson.UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcludedContext(ctx, data, &r.RawDefaultDNSRule, &r.DNSRuleAction) } func (r DefaultDNSRule) IsValid() bool { @@ -145,12 +146,12 @@ func (r LogicalDNSRule) MarshalJSON() ([]byte, error) { return badjson.MarshallObjects(r.RawLogicalDNSRule, r.DNSRuleAction) } -func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { +func (r *LogicalDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error { err := json.Unmarshal(data, &r.RawLogicalDNSRule) if err != nil { return err } - return badjson.UnmarshallExcluded(data, &r.RawLogicalDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcludedContext(ctx, data, &r.RawLogicalDNSRule, &r.DNSRuleAction) } func (r *LogicalDNSRule) IsValid() bool { From 1133cf3ef5e8834f2efaf94c091e6ad93b125570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 7 Nov 2024 13:44:00 +0800 Subject: [PATCH 0157/1272] Implement udp connect --- adapter/outbound/default.go | 110 ++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 31 deletions(-) diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go index 78b9bfd8d6..bb58ff54d9 100644 --- a/adapter/outbound/default.go +++ b/adapter/outbound/default.go @@ -20,6 +20,7 @@ import ( ) func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error { + defer conn.Close() ctx = adapter.WithContext(ctx, &metadata) var outConn net.Conn var err error @@ -40,6 +41,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a } func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { + defer conn.Close() ctx = adapter.WithContext(ctx, &metadata) var outConn net.Conn var err error @@ -67,29 +69,49 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial } func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error { + defer conn.Close() ctx = adapter.WithContext(ctx, &metadata) - var outConn net.PacketConn - var destinationAddress netip.Addr - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + var ( + outPacketConn net.PacketConn + outConn net.Conn + destinationAddress netip.Addr + err error + ) + if metadata.UDPConnect { + if len(metadata.DestinationAddresses) > 0 { + outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + } else { + outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) + } + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } + outPacketConn = bufio.NewUnbindPacketConn(outConn) + connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr()) + if connRemoteAddr != metadata.Destination.Addr { + destinationAddress = connRemoteAddr + } } else { - outConn, err = this.ListenPacket(ctx, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) + if len(metadata.DestinationAddresses) > 0 { + outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + } else { + outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) + } + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } } - err = N.ReportPacketConnHandshakeSuccess(conn, outConn) + err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn) if err != nil { - outConn.Close() + outPacketConn.Close() return err } if destinationAddress.IsValid() { if metadata.Destination.IsFqdn() { if metadata.UDPDisableDomainUnmapping { - outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) } else { - outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) } } if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { @@ -104,37 +126,63 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, case C.ProtocolDNS: ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) } - return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn)) + return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn)) } func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { + defer conn.Close() ctx = adapter.WithContext(ctx, &metadata) - var outConn net.PacketConn - var destinationAddress netip.Addr - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) + var ( + outPacketConn net.PacketConn + outConn net.Conn + destinationAddress netip.Addr + err error + ) + if metadata.UDPConnect { + if len(metadata.DestinationAddresses) > 0 { + outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + } else if metadata.Destination.IsFqdn() { + var destinationAddresses []netip.Addr + destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } + outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, destinationAddresses) + } else { + outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) + } if err != nil { return N.ReportHandshakeFailure(conn, err) } - outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses) + connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr()) + if connRemoteAddr != metadata.Destination.Addr { + destinationAddress = connRemoteAddr + } } else { - outConn, err = this.ListenPacket(ctx, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) + if len(metadata.DestinationAddresses) > 0 { + outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + } else if metadata.Destination.IsFqdn() { + var destinationAddresses []netip.Addr + destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } + outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses) + } else { + outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) + } + if err != nil { + return N.ReportHandshakeFailure(conn, err) + } } - err = N.ReportPacketConnHandshakeSuccess(conn, outConn) + err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn) if err != nil { - outConn.Close() + outPacketConn.Close() return err } if destinationAddress.IsValid() { if metadata.Destination.IsFqdn() { - outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) } if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { natConn.UpdateDestination(destinationAddress) @@ -148,7 +196,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this case C.ProtocolDNS: ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) } - return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn)) + return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn)) } func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error { From ce69e620e9405983396c497c382b8bfbc1e1e15c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Nov 2024 18:55:34 +0800 Subject: [PATCH 0158/1272] Migrate bad options to library --- common/dialer/default.go | 5 +- common/listener/listener.go | 3 +- common/listener/listener_tcp.go | 3 +- common/listener/listener_udp.go | 3 +- experimental/deprecated/manager.go | 1 + option/dns.go | 32 ++-- option/experimental.go | 32 ++-- option/group.go | 14 +- option/inbound.go | 35 ++--- option/ntp.go | 8 +- option/outbound.go | 31 ++-- option/platform.go | 7 +- option/rule.go | 67 ++++----- option/rule_action.go | 18 ++- option/rule_dns.go | 71 ++++----- option/rule_set.go | 49 ++++--- option/simple.go | 9 +- option/ssh.go | 18 ++- option/time_unit.go | 226 ----------------------------- option/tls.go | 80 +++++----- option/tls_acme.go | 3 +- option/tuic.go | 28 ++-- option/tun.go | 69 ++++----- option/tun_platform.go | 6 +- option/types.go | 132 ----------------- option/v2ray_transport.go | 37 ++--- option/wireguard.go | 26 ++-- protocol/tun/inbound.go | 3 +- route/router.go | 4 +- route/rule/rule_action.go | 4 +- transport/sip003/v2ray.go | 3 +- 31 files changed, 356 insertions(+), 671 deletions(-) delete mode 100644 option/time_unit.go diff --git a/common/dialer/default.go b/common/dialer/default.go index 02678961a7..0c8cee18fd 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -3,6 +3,7 @@ package dialer import ( "context" "net" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" @@ -102,7 +103,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi udpAddr4 string ) if options.Inet4BindAddress != nil { - bindAddr := options.Inet4BindAddress.Build() + bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified()) dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String() @@ -113,7 +114,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi udpAddr6 string ) if options.Inet6BindAddress != nil { - bindAddr := options.Inet6BindAddress.Build() + bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified()) dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String() diff --git a/common/listener/listener.go b/common/listener/listener.go index b42b0434b2..289f15c51b 100644 --- a/common/listener/listener.go +++ b/common/listener/listener.go @@ -3,6 +3,7 @@ package listener import ( "context" "net" + "net/netip" "sync/atomic" "github.com/sagernet/sing-box/adapter" @@ -92,7 +93,7 @@ func (l *Listener) Start() error { if l.setSystemProxy { listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port var listenAddrString string - listenAddr := l.listenOptions.Listen.Build() + listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified()) if listenAddr.IsUnspecified() { listenAddrString = "127.0.0.1" } else { diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index 02cef3f04d..646d40179a 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -2,6 +2,7 @@ package listener import ( "net" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" @@ -16,7 +17,7 @@ import ( func (l *Listener) ListenTCP() (net.Listener, error) { var err error - bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) var tcpListener net.Listener var listenConfig net.ListenConfig if l.listenOptions.TCPKeepAlive >= 0 { diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go index 0c3220a7f1..10d6dc3868 100644 --- a/common/listener/listener_udp.go +++ b/common/listener/listener_udp.go @@ -2,6 +2,7 @@ package listener import ( "net" + "net/netip" "os" "github.com/sagernet/sing/common/buf" @@ -12,7 +13,7 @@ import ( ) func (l *Listener) ListenUDP() (net.PacketConn, error) { - bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) var lc net.ListenConfig var udpFragment bool if l.listenOptions.UDPFragment != nil { diff --git a/experimental/deprecated/manager.go b/experimental/deprecated/manager.go index d7cae1797a..d12acf48fa 100644 --- a/experimental/deprecated/manager.go +++ b/experimental/deprecated/manager.go @@ -2,6 +2,7 @@ package deprecated import ( "context" + "github.com/sagernet/sing/service" ) diff --git a/option/dns.go b/option/dns.go index be947583a0..32c1ac2e1b 100644 --- a/option/dns.go +++ b/option/dns.go @@ -1,6 +1,10 @@ package option -import "net/netip" +import ( + "net/netip" + + "github.com/sagernet/sing/common/json/badoption" +) type DNSOptions struct { Servers []DNSServerOptions `json:"servers,omitempty"` @@ -12,22 +16,22 @@ type DNSOptions struct { } type DNSServerOptions struct { - Tag string `json:"tag,omitempty"` - Address string `json:"address"` - AddressResolver string `json:"address_resolver,omitempty"` - AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` - AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"` - Strategy DomainStrategy `json:"strategy,omitempty"` - Detour string `json:"detour,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + Tag string `json:"tag,omitempty"` + Address string `json:"address"` + AddressResolver string `json:"address_resolver,omitempty"` + AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` + AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + Detour string `json:"detour,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type DNSClientOptions struct { - Strategy DomainStrategy `json:"strategy,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - DisableExpire bool `json:"disable_expire,omitempty"` - IndependentCache bool `json:"independent_cache,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + DisableExpire bool `json:"disable_expire,omitempty"` + IndependentCache bool `json:"independent_cache,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type DNSFakeIPOptions struct { diff --git a/option/experimental.go b/option/experimental.go index 6ab6638550..bf0df9e78c 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -1,5 +1,7 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type ExperimentalOptions struct { CacheFile *CacheFileOptions `json:"cache_file,omitempty"` ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"` @@ -8,24 +10,24 @@ type ExperimentalOptions struct { } type CacheFileOptions struct { - Enabled bool `json:"enabled,omitempty"` - Path string `json:"path,omitempty"` - CacheID string `json:"cache_id,omitempty"` - StoreFakeIP bool `json:"store_fakeip,omitempty"` - StoreRDRC bool `json:"store_rdrc,omitempty"` - RDRCTimeout Duration `json:"rdrc_timeout,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Path string `json:"path,omitempty"` + CacheID string `json:"cache_id,omitempty"` + StoreFakeIP bool `json:"store_fakeip,omitempty"` + StoreRDRC bool `json:"store_rdrc,omitempty"` + RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"` } type ClashAPIOptions struct { - ExternalController string `json:"external_controller,omitempty"` - ExternalUI string `json:"external_ui,omitempty"` - ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` - ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` - Secret string `json:"secret,omitempty"` - DefaultMode string `json:"default_mode,omitempty"` - ModeList []string `json:"-"` - AccessControlAllowOrigin Listable[string] `json:"access_control_allow_origin,omitempty"` - AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` + ExternalController string `json:"external_controller,omitempty"` + ExternalUI string `json:"external_ui,omitempty"` + ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` + ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` + Secret string `json:"secret,omitempty"` + DefaultMode string `json:"default_mode,omitempty"` + ModeList []string `json:"-"` + AccessControlAllowOrigin badoption.Listable[string] `json:"access_control_allow_origin,omitempty"` + AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` // Deprecated: migrated to global cache file CacheFile string `json:"cache_file,omitempty"` diff --git a/option/group.go b/option/group.go index 72a0f63702..02b3a5ecb9 100644 --- a/option/group.go +++ b/option/group.go @@ -1,5 +1,7 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type SelectorOutboundOptions struct { Outbounds []string `json:"outbounds"` Default string `json:"default,omitempty"` @@ -7,10 +9,10 @@ type SelectorOutboundOptions struct { } type URLTestOutboundOptions struct { - Outbounds []string `json:"outbounds"` - URL string `json:"url,omitempty"` - Interval Duration `json:"interval,omitempty"` - Tolerance uint16 `json:"tolerance,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` + Outbounds []string `json:"outbounds"` + URL string `json:"url,omitempty"` + Interval badoption.Duration `json:"interval,omitempty"` + Tolerance uint16 `json:"tolerance,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` } diff --git a/option/inbound.go b/option/inbound.go index 651d02846c..a67719fa04 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -7,6 +7,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" "github.com/sagernet/sing/service" ) @@ -49,24 +50,24 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro // Deprecated: Use rule action instead type InboundOptions struct { - SniffEnabled bool `json:"sniff,omitempty"` - SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` - SniffTimeout Duration `json:"sniff_timeout,omitempty"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - Detour string `json:"detour,omitempty"` + SniffEnabled bool `json:"sniff,omitempty"` + SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` + 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 { - Listen *ListenAddress `json:"listen,omitempty"` - ListenPort uint16 `json:"listen_port,omitempty"` - TCPKeepAlive Duration `json:"tcp_keep_alive,omitempty"` - TCPKeepAliveInterval Duration `json:"tcp_keep_alive_interval,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Listen *badoption.Addr `json:"listen,omitempty"` + ListenPort uint16 `json:"listen_port,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"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` // Deprecated: removed ProxyProtocol bool `json:"proxy_protocol,omitempty"` @@ -75,7 +76,7 @@ type ListenOptions struct { InboundOptions } -type UDPTimeoutCompat Duration +type UDPTimeoutCompat badoption.Duration func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) { return json.Marshal((time.Duration)(c).String()) @@ -88,7 +89,7 @@ func (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error { *c = UDPTimeoutCompat(time.Second * time.Duration(valueNumber)) return nil } - return json.Unmarshal(data, (*Duration)(c)) + return json.Unmarshal(data, (*badoption.Duration)(c)) } type ListenOptionsWrapper interface { diff --git a/option/ntp.go b/option/ntp.go index 0bd2489ac2..d441d95e86 100644 --- a/option/ntp.go +++ b/option/ntp.go @@ -1,9 +1,11 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type NTPOptions struct { - Enabled bool `json:"enabled,omitempty"` - Interval Duration `json:"interval,omitempty"` - WriteToSystem bool `json:"write_to_system,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Interval badoption.Duration `json:"interval,omitempty"` + WriteToSystem bool `json:"write_to_system,omitempty"` ServerOptions DialerOptions } diff --git a/option/outbound.go b/option/outbound.go index 1dddb35487..0e2d5874da 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -8,6 +8,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/service" ) @@ -64,21 +65,21 @@ type DialerOptionsWrapper interface { } type DialerOptions struct { - Detour string `json:"detour,omitempty"` - BindInterface string `json:"bind_interface,omitempty"` - Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"` - Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"` - ProtectPath string `json:"protect_path,omitempty"` - RoutingMark uint32 `json:"routing_mark,omitempty"` - ReuseAddr bool `json:"reuse_addr,omitempty"` - ConnectTimeout Duration `json:"connect_timeout,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - FallbackDelay Duration `json:"fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` + 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 uint32 `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,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:"-"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` + IsWireGuardListener bool `json:"-"` } func (o *DialerOptions) TakeDialerOptions() DialerOptions { diff --git a/option/platform.go b/option/platform.go index a43cbf230f..e4ecd6fa1e 100644 --- a/option/platform.go +++ b/option/platform.go @@ -3,6 +3,7 @@ package option import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" ) type OnDemandOptions struct { @@ -12,10 +13,10 @@ type OnDemandOptions struct { type OnDemandRule struct { Action *OnDemandRuleAction `json:"action,omitempty"` - DNSSearchDomainMatch Listable[string] `json:"dns_search_domain_match,omitempty"` - DNSServerAddressMatch Listable[string] `json:"dns_server_address_match,omitempty"` + DNSSearchDomainMatch badoption.Listable[string] `json:"dns_search_domain_match,omitempty"` + DNSServerAddressMatch badoption.Listable[string] `json:"dns_server_address_match,omitempty"` InterfaceTypeMatch *OnDemandRuleInterfaceType `json:"interface_type_match,omitempty"` - SSIDMatch Listable[string] `json:"ssid_match,omitempty"` + SSIDMatch badoption.Listable[string] `json:"ssid_match,omitempty"` ProbeURL string `json:"probe_url,omitempty"` } diff --git a/option/rule.go b/option/rule.go index 952afa6146..d5ff9925a6 100644 --- a/option/rule.go +++ b/option/rule.go @@ -8,6 +8,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _Rule struct { @@ -66,39 +67,39 @@ func (r Rule) IsValid() bool { } type RawDefaultRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network Listable[string] `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Client Listable[string] `json:"client,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - GeoIP Listable[string] `json:"geoip,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet 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"` + 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"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/option/rule_action.go b/option/rule_action.go index a7244e179b..7a76391ca6 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -3,6 +3,7 @@ package option import ( "context" "fmt" + "net/netip" "time" C "github.com/sagernet/sing-box/constant" @@ -11,6 +12,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _RuleAction struct { @@ -177,7 +179,7 @@ type _DNSRouteActionOptions struct { // Deprecated: Use DNSRouteOptionsActionOptions instead. RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` // Deprecated: Use DNSRouteOptionsActionOptions instead. - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type DNSRouteActionOptions _DNSRouteActionOptions @@ -197,9 +199,9 @@ func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data [ } type _DNSRouteOptionsActionOptions struct { - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions @@ -225,10 +227,10 @@ func (d DirectActionOptions) Descriptions() []string { descriptions = append(descriptions, "bind_interface="+d.BindInterface) } if d.Inet4BindAddress != nil { - descriptions = append(descriptions, "inet4_bind_address="+d.Inet4BindAddress.Build().String()) + descriptions = append(descriptions, "inet4_bind_address="+d.Inet4BindAddress.Build(netip.IPv4Unspecified()).String()) } if d.Inet6BindAddress != nil { - descriptions = append(descriptions, "inet6_bind_address="+d.Inet6BindAddress.Build().String()) + descriptions = append(descriptions, "inet6_bind_address="+d.Inet6BindAddress.Build(netip.IPv6Unspecified()).String()) } if d.RoutingMark != 0 { descriptions = append(descriptions, "routing_mark="+fmt.Sprintf("0x%x", d.RoutingMark)) @@ -294,8 +296,8 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { } type RouteActionSniff struct { - Sniffer Listable[string] `json:"sniffer,omitempty"` - Timeout Duration `json:"timeout,omitempty"` + Sniffer badoption.Listable[string] `json:"sniffer,omitempty"` + Timeout badoption.Duration `json:"timeout,omitempty"` } type RouteActionResolve struct { diff --git a/option/rule_dns.go b/option/rule_dns.go index e7e454bb0e..6c9755fda8 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -9,6 +9,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _DNSRule struct { @@ -67,41 +68,41 @@ func (r DNSRule) IsValid() bool { } type RawDefaultDNSRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network Listable[string] `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - GeoIP Listable[string] `json:"geoip,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - Outbound Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet 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"` + 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"` + 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"` // 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 3bf9aa5c48..fbb527ca55 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -10,6 +10,7 @@ import ( F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" "go4.org/netipx" ) @@ -84,9 +85,9 @@ type LocalRuleSet struct { } type RemoteRuleSet struct { - URL string `json:"url"` - DownloadDetour string `json:"download_detour,omitempty"` - UpdateInterval Duration `json:"update_interval,omitempty"` + URL string `json:"url"` + DownloadDetour string `json:"download_detour,omitempty"` + UpdateInterval badoption.Duration `json:"update_interval,omitempty"` } type _HeadlessRule struct { @@ -145,32 +146,32 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network Listable[string] `json:"network,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID 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"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` IPSet *netipx.IPSet `json:"-"` - AdGuardDomain Listable[string] `json:"-"` - AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"` + AdGuardDomain badoption.Listable[string] `json:"-"` + AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"` } func (r DefaultHeadlessRule) IsValid() bool { diff --git a/option/simple.go b/option/simple.go index 78171ce4c2..5fda30efe4 100644 --- a/option/simple.go +++ b/option/simple.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 SocksInboundOptions struct { ListenOptions @@ -30,6 +33,6 @@ type HTTPOutboundOptions struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` OutboundTLSOptionsContainer - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` + Path string `json:"path,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` } diff --git a/option/ssh.go b/option/ssh.go index d0bfbf7425..1c6ca6bb96 100644 --- a/option/ssh.go +++ b/option/ssh.go @@ -1,14 +1,16 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type SSHOutboundOptions struct { DialerOptions ServerOptions - User string `json:"user,omitempty"` - Password string `json:"password,omitempty"` - PrivateKey Listable[string] `json:"private_key,omitempty"` - PrivateKeyPath string `json:"private_key_path,omitempty"` - PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"` - HostKey Listable[string] `json:"host_key,omitempty"` - HostKeyAlgorithms Listable[string] `json:"host_key_algorithms,omitempty"` - ClientVersion string `json:"client_version,omitempty"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + PrivateKey badoption.Listable[string] `json:"private_key,omitempty"` + PrivateKeyPath string `json:"private_key_path,omitempty"` + PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"` + HostKey badoption.Listable[string] `json:"host_key,omitempty"` + HostKeyAlgorithms badoption.Listable[string] `json:"host_key_algorithms,omitempty"` + ClientVersion string `json:"client_version,omitempty"` } diff --git a/option/time_unit.go b/option/time_unit.go deleted file mode 100644 index 5e531dadf1..0000000000 --- a/option/time_unit.go +++ /dev/null @@ -1,226 +0,0 @@ -package option - -import ( - "errors" - "time" -) - -// 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. - -const durationDay = 24 * time.Hour - -var unitMap = map[string]uint64{ - "ns": uint64(time.Nanosecond), - "us": uint64(time.Microsecond), - "µs": uint64(time.Microsecond), // U+00B5 = micro symbol - "μs": uint64(time.Microsecond), // U+03BC = Greek letter mu - "ms": uint64(time.Millisecond), - "s": uint64(time.Second), - "m": uint64(time.Minute), - "h": uint64(time.Hour), - "d": uint64(durationDay), -} - -// ParseDuration parses a duration string. -// A duration string is a possibly signed sequence of -// decimal numbers, each with optional fraction and a unit suffix, -// such as "300ms", "-1.5h" or "2h45m". -// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". -func ParseDuration(s string) (Duration, error) { - // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ - orig := s - var d uint64 - neg := false - - // Consume [-+]? - if s != "" { - c := s[0] - if c == '-' || c == '+' { - neg = c == '-' - s = s[1:] - } - } - // Special case: if all that is left is "0", this is zero. - if s == "0" { - return 0, nil - } - if s == "" { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - for s != "" { - var ( - v, f uint64 // integers before, after decimal point - scale float64 = 1 // value = v + f/scale - ) - - var err error - - // The next character must be [0-9.] - if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - // Consume [0-9]* - pl := len(s) - v, s, err = leadingInt(s) - if err != nil { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - pre := pl != len(s) // whether we consumed anything before a period - - // Consume (\.[0-9]*)? - post := false - if s != "" && s[0] == '.' { - s = s[1:] - pl := len(s) - f, scale, s = leadingFraction(s) - post = pl != len(s) - } - if !pre && !post { - // no digits (e.g. ".s" or "-.s") - return 0, errors.New("time: invalid duration " + quote(orig)) - } - - // Consume unit. - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c == '.' || '0' <= c && c <= '9' { - break - } - } - if i == 0 { - return 0, errors.New("time: missing unit in duration " + quote(orig)) - } - u := s[:i] - s = s[i:] - unit, ok := unitMap[u] - if !ok { - return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig)) - } - if v > 1<<63/unit { - // overflow - return 0, errors.New("time: invalid duration " + quote(orig)) - } - v *= unit - if f > 0 { - // float64 is needed to be nanosecond accurate for fractions of hours. - // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) - v += uint64(float64(f) * (float64(unit) / scale)) - if v > 1<<63 { - // overflow - return 0, errors.New("time: invalid duration " + quote(orig)) - } - } - d += v - if d > 1<<63 { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - } - if neg { - return -Duration(d), nil - } - if d > 1<<63-1 { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - return Duration(d), nil -} - -var errLeadingInt = errors.New("time: bad [0-9]*") // never printed - -// leadingInt consumes the leading [0-9]* from s. -func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, err error) { - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - if x > 1<<63/10 { - // overflow - return 0, rem, errLeadingInt - } - x = x*10 + uint64(c) - '0' - if x > 1<<63 { - // overflow - return 0, rem, errLeadingInt - } - } - return x, s[i:], nil -} - -// leadingFraction consumes the leading [0-9]* from s. -// It is used only for fractions, so does not return an error on overflow, -// it just stops accumulating precision. -func leadingFraction(s string) (x uint64, scale float64, rem string) { - i := 0 - scale = 1 - overflow := false - for ; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - if overflow { - continue - } - if x > (1<<63-1)/10 { - // It's possible for overflow to give a positive number, so take care. - overflow = true - continue - } - y := x*10 + uint64(c) - '0' - if y > 1<<63 { - overflow = true - continue - } - x = y - scale *= 10 - } - return x, scale, s[i:] -} - -// These are borrowed from unicode/utf8 and strconv and replicate behavior in -// that package, since we can't take a dependency on either. -const ( - lowerhex = "0123456789abcdef" - runeSelf = 0x80 - runeError = '\uFFFD' -) - -func quote(s string) string { - buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes - buf[0] = '"' - for i, c := range s { - if c >= runeSelf || c < ' ' { - // This means you are asking us to parse a time.Duration or - // time.Location with unprintable or non-ASCII characters in it. - // We don't expect to hit this case very often. We could try to - // reproduce strconv.Quote's behavior with full fidelity but - // given how rarely we expect to hit these edge cases, speed and - // conciseness are better. - var width int - if c == runeError { - width = 1 - if i+2 < len(s) && s[i:i+3] == string(runeError) { - width = 3 - } - } else { - width = len(string(c)) - } - for j := 0; j < width; j++ { - buf = append(buf, `\x`...) - buf = append(buf, lowerhex[s[i+j]>>4]) - buf = append(buf, lowerhex[s[i+j]&0xF]) - } - } else { - if c == '"' || c == '\\' { - buf = append(buf, '\\') - } - buf = append(buf, string(c)...) - } - } - buf = append(buf, '"') - return string(buf) -} diff --git a/option/tls.go b/option/tls.go index 3680afe052..72aaaef1ac 100644 --- a/option/tls.go +++ b/option/tls.go @@ -1,20 +1,22 @@ package option +import "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 Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - CipherSuites Listable[string] `json:"cipher_suites,omitempty"` - Certificate Listable[string] `json:"certificate,omitempty"` - CertificatePath string `json:"certificate_path,omitempty"` - Key Listable[string] `json:"key,omitempty"` - KeyPath string `json:"key_path,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"` + 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"` + ACME *InboundACMEOptions `json:"acme,omitempty"` + ECH *InboundECHOptions `json:"ech,omitempty"` + Reality *InboundRealityOptions `json:"reality,omitempty"` } type InboundTLSOptionsContainer struct { @@ -35,19 +37,19 @@ 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 Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - CipherSuites Listable[string] `json:"cipher_suites,omitempty"` - Certificate Listable[string] `json:"certificate,omitempty"` - CertificatePath string `json:"certificate_path,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"` + Certificate badoption.Listable[string] `json:"certificate,omitempty"` + CertificatePath string `json:"certificate_path,omitempty"` + ECH *OutboundECHOptions `json:"ech,omitempty"` + UTLS *OutboundUTLSOptions `json:"utls,omitempty"` + Reality *OutboundRealityOptions `json:"reality,omitempty"` } type OutboundTLSOptionsContainer struct { @@ -71,8 +73,8 @@ type InboundRealityOptions struct { Enabled bool `json:"enabled,omitempty"` Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"` PrivateKey string `json:"private_key,omitempty"` - ShortID Listable[string] `json:"short_id,omitempty"` - MaxTimeDifference Duration `json:"max_time_difference,omitempty"` + ShortID badoption.Listable[string] `json:"short_id,omitempty"` + MaxTimeDifference badoption.Duration `json:"max_time_difference,omitempty"` } type InboundRealityHandshakeOptions struct { @@ -81,19 +83,19 @@ type InboundRealityHandshakeOptions struct { } type InboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` - Key Listable[string] `json:"key,omitempty"` - KeyPath string `json:"key_path,omitempty"` + Enabled bool `json:"enabled,omitempty"` + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` + Key badoption.Listable[string] `json:"key,omitempty"` + KeyPath string `json:"key_path,omitempty"` } type OutboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` - Config Listable[string] `json:"config,omitempty"` - ConfigPath string `json:"config_path,omitempty"` + Enabled bool `json:"enabled,omitempty"` + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` + Config badoption.Listable[string] `json:"config,omitempty"` + ConfigPath string `json:"config_path,omitempty"` } type OutboundUTLSOptions struct { diff --git a/option/tls_acme.go b/option/tls_acme.go index 9c2e081fa8..502706073d 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -5,10 +5,11 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type InboundACMEOptions struct { - Domain Listable[string] `json:"domain,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` DataDirectory string `json:"data_directory,omitempty"` DefaultServerName string `json:"default_server_name,omitempty"` Email string `json:"email,omitempty"` diff --git a/option/tuic.go b/option/tuic.go index 736d5a6606..a9b739ec69 100644 --- a/option/tuic.go +++ b/option/tuic.go @@ -1,12 +1,14 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type TUICInboundOptions struct { ListenOptions - Users []TUICUser `json:"users,omitempty"` - CongestionControl string `json:"congestion_control,omitempty"` - AuthTimeout Duration `json:"auth_timeout,omitempty"` - ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` - Heartbeat Duration `json:"heartbeat,omitempty"` + Users []TUICUser `json:"users,omitempty"` + CongestionControl string `json:"congestion_control,omitempty"` + AuthTimeout badoption.Duration `json:"auth_timeout,omitempty"` + ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` + Heartbeat badoption.Duration `json:"heartbeat,omitempty"` InboundTLSOptionsContainer } @@ -19,13 +21,13 @@ type TUICUser struct { type TUICOutboundOptions struct { DialerOptions ServerOptions - UUID string `json:"uuid,omitempty"` - Password string `json:"password,omitempty"` - CongestionControl string `json:"congestion_control,omitempty"` - UDPRelayMode string `json:"udp_relay_mode,omitempty"` - UDPOverStream bool `json:"udp_over_stream,omitempty"` - ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` - Heartbeat Duration `json:"heartbeat,omitempty"` - Network NetworkList `json:"network,omitempty"` + UUID string `json:"uuid,omitempty"` + Password string `json:"password,omitempty"` + CongestionControl string `json:"congestion_control,omitempty"` + UDPRelayMode string `json:"udp_relay_mode,omitempty"` + UDPOverStream bool `json:"udp_over_stream,omitempty"` + ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` + Heartbeat badoption.Duration `json:"heartbeat,omitempty"` + Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer } diff --git a/option/tun.go b/option/tun.go index dbb1bfeae0..a7a0f6bc07 100644 --- a/option/tun.go +++ b/option/tun.go @@ -7,51 +7,52 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" ) type TunInboundOptions struct { - InterfaceName string `json:"interface_name,omitempty"` - MTU uint32 `json:"mtu,omitempty"` - GSO bool `json:"gso,omitempty"` - Address 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"` - StrictRoute bool `json:"strict_route,omitempty"` - RouteAddress Listable[netip.Prefix] `json:"route_address,omitempty"` - RouteAddressSet Listable[string] `json:"route_address_set,omitempty"` - RouteExcludeAddress Listable[netip.Prefix] `json:"route_exclude_address,omitempty"` - RouteExcludeAddressSet Listable[string] `json:"route_exclude_address_set,omitempty"` - IncludeInterface Listable[string] `json:"include_interface,omitempty"` - ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"` - IncludeUID Listable[uint32] `json:"include_uid,omitempty"` - IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"` - ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"` - ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"` - IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"` - IncludePackage Listable[string] `json:"include_package,omitempty"` - ExcludePackage Listable[string] `json:"exclude_package,omitempty"` - EndpointIndependentNat bool `json:"endpoint_independent_nat,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"` + GSO bool `json:"gso,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"` + 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"` + EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Stack string `json:"stack,omitempty"` + Platform *TunPlatformOptions `json:"platform,omitempty"` InboundOptions // Deprecated: merged to Address - Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"` + Inet4Address badoption.Listable[netip.Prefix] `json:"inet4_address,omitempty"` // Deprecated: merged to Address - Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"` + Inet6Address badoption.Listable[netip.Prefix] `json:"inet6_address,omitempty"` // Deprecated: merged to RouteAddress - Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` + Inet4RouteAddress badoption.Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` // Deprecated: merged to RouteAddress - Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` + Inet6RouteAddress badoption.Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` // Deprecated: merged to RouteExcludeAddress - Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` + Inet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` // Deprecated: merged to RouteExcludeAddress - Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` + Inet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` } type FwMark uint32 diff --git a/option/tun_platform.go b/option/tun_platform.go index a0a54eed05..b42f6894f4 100644 --- a/option/tun_platform.go +++ b/option/tun_platform.go @@ -1,5 +1,7 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type TunPlatformOptions struct { HTTPProxy *HTTPProxyOptions `json:"http_proxy,omitempty"` } @@ -7,6 +9,6 @@ type TunPlatformOptions struct { type HTTPProxyOptions struct { Enabled bool `json:"enabled,omitempty"` ServerOptions - BypassDomain Listable[string] `json:"bypass_domain,omitempty"` - MatchDomain Listable[string] `json:"match_domain,omitempty"` + BypassDomain badoption.Listable[string] `json:"bypass_domain,omitempty"` + MatchDomain badoption.Listable[string] `json:"match_domain,omitempty"` } diff --git a/option/types.go b/option/types.go index bb6485491e..04e3f10e23 100644 --- a/option/types.go +++ b/option/types.go @@ -1,10 +1,7 @@ package option import ( - "net/http" - "net/netip" "strings" - "time" "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" @@ -15,79 +12,6 @@ import ( mDNS "github.com/miekg/dns" ) -type ListenAddress netip.Addr - -func NewListenAddress(addr netip.Addr) *ListenAddress { - address := ListenAddress(addr) - return &address -} - -func (a ListenAddress) MarshalJSON() ([]byte, error) { - addr := netip.Addr(a) - if !addr.IsValid() { - return nil, nil - } - return json.Marshal(addr.String()) -} - -func (a *ListenAddress) UnmarshalJSON(content []byte) error { - var value string - err := json.Unmarshal(content, &value) - if err != nil { - return err - } - addr, err := netip.ParseAddr(value) - if err != nil { - return err - } - *a = ListenAddress(addr) - return nil -} - -func (a *ListenAddress) Build() netip.Addr { - if a == nil { - return netip.AddrFrom4([4]byte{127, 0, 0, 1}) - } - return (netip.Addr)(*a) -} - -type AddrPrefix netip.Prefix - -func (a AddrPrefix) MarshalJSON() ([]byte, error) { - prefix := netip.Prefix(a) - if prefix.Bits() == prefix.Addr().BitLen() { - return json.Marshal(prefix.Addr().String()) - } else { - return json.Marshal(prefix.String()) - } -} - -func (a *AddrPrefix) UnmarshalJSON(content []byte) error { - var value string - err := json.Unmarshal(content, &value) - if err != nil { - return err - } - prefix, prefixErr := netip.ParsePrefix(value) - if prefixErr == nil { - *a = AddrPrefix(prefix) - return nil - } - addr, addrErr := netip.ParseAddr(value) - if addrErr == nil { - *a = AddrPrefix(netip.PrefixFrom(addr, addr.BitLen())) - return nil - } - return prefixErr -} - -func (a *AddrPrefix) Build() netip.Prefix { - if a == nil { - return netip.Prefix{} - } - return netip.Prefix(*a) -} - type NetworkList string func (v *NetworkList) UnmarshalJSON(content []byte) error { @@ -120,30 +44,6 @@ func (v NetworkList) Build() []string { return strings.Split(string(v), "\n") } -type Listable[T any] []T - -func (l Listable[T]) MarshalJSON() ([]byte, error) { - arrayList := []T(l) - if len(arrayList) == 1 { - return json.Marshal(arrayList[0]) - } - return json.Marshal(arrayList) -} - -func (l *Listable[T]) UnmarshalJSON(content []byte) error { - err := json.UnmarshalDisallowUnknownFields(content, (*[]T)(l)) - if err == nil { - return nil - } - var singleItem T - newError := json.UnmarshalDisallowUnknownFields(content, &singleItem) - if newError != nil { - return E.Errors(err, newError) - } - *l = []T{singleItem} - return nil -} - type DomainStrategy dns.DomainStrategy func (s DomainStrategy) String() string { @@ -206,26 +106,6 @@ func (s *DomainStrategy) UnmarshalJSON(bytes []byte) error { return nil } -type Duration time.Duration - -func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal((time.Duration)(d).String()) -} - -func (d *Duration) UnmarshalJSON(bytes []byte) error { - var value string - err := json.Unmarshal(bytes, &value) - if err != nil { - return err - } - duration, err := ParseDuration(value) - if err != nil { - return err - } - *d = Duration(duration) - return nil -} - type DNSQueryType uint16 func (t DNSQueryType) String() string { @@ -270,15 +150,3 @@ func DNSQueryTypeToString(queryType uint16) string { } return F.ToString(queryType) } - -type HTTPHeader map[string]Listable[string] - -func (h HTTPHeader) Build() http.Header { - header := make(http.Header) - for name, values := range h { - for _, value := range values { - header.Add(name, value) - } - } - return header -} diff --git a/option/v2ray_transport.go b/option/v2ray_transport.go index f87b175d39..68c2385811 100644 --- a/option/v2ray_transport.go +++ b/option/v2ray_transport.go @@ -5,6 +5,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _V2RayTransportOptions struct { @@ -67,33 +68,33 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { } type V2RayHTTPOptions struct { - Host Listable[string] `json:"host,omitempty"` - Path string `json:"path,omitempty"` - Method string `json:"method,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - PingTimeout Duration `json:"ping_timeout,omitempty"` + Host badoption.Listable[string] `json:"host,omitempty"` + Path string `json:"path,omitempty"` + Method string `json:"method,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + PingTimeout badoption.Duration `json:"ping_timeout,omitempty"` } type V2RayWebsocketOptions struct { - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` - MaxEarlyData uint32 `json:"max_early_data,omitempty"` - EarlyDataHeaderName string `json:"early_data_header_name,omitempty"` + Path string `json:"path,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + MaxEarlyData uint32 `json:"max_early_data,omitempty"` + EarlyDataHeaderName string `json:"early_data_header_name,omitempty"` } type V2RayQUICOptions struct{} type V2RayGRPCOptions struct { - ServiceName string `json:"service_name,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - PingTimeout Duration `json:"ping_timeout,omitempty"` - PermitWithoutStream bool `json:"permit_without_stream,omitempty"` - ForceLite bool `json:"-"` // for test + ServiceName string `json:"service_name,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + PingTimeout badoption.Duration `json:"ping_timeout,omitempty"` + PermitWithoutStream bool `json:"permit_without_stream,omitempty"` + ForceLite bool `json:"-"` // for test } type V2RayHTTPUpgradeOptions struct { - Host string `json:"host,omitempty"` - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` + Host string `json:"host,omitempty"` + Path string `json:"path,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` } diff --git a/option/wireguard.go b/option/wireguard.go index 65bfad2061..ebdf159fde 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -1,15 +1,19 @@ package option -import "net/netip" +import ( + "net/netip" + + "github.com/sagernet/sing/common/json/badoption" +) type WireGuardOutboundOptions struct { DialerOptions - SystemInterface bool `json:"system_interface,omitempty"` - GSO bool `json:"gso,omitempty"` - InterfaceName string `json:"interface_name,omitempty"` - LocalAddress Listable[netip.Prefix] `json:"local_address"` - PrivateKey string `json:"private_key"` - Peers []WireGuardPeer `json:"peers,omitempty"` + 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 []WireGuardPeer `json:"peers,omitempty"` ServerOptions PeerPublicKey string `json:"peer_public_key"` PreSharedKey string `json:"pre_shared_key,omitempty"` @@ -21,8 +25,8 @@ type WireGuardOutboundOptions struct { type WireGuardPeer struct { ServerOptions - PublicKey string `json:"public_key,omitempty"` - PreSharedKey string `json:"pre_shared_key,omitempty"` - AllowedIPs Listable[string] `json:"allowed_ips,omitempty"` - Reserved []uint8 `json:"reserved,omitempty"` + PublicKey string `json:"public_key,omitempty"` + PreSharedKey string `json:"pre_shared_key,omitempty"` + AllowedIPs badoption.Listable[string] `json:"allowed_ips,omitempty"` + Reserved []uint8 `json:"reserved,omitempty"` } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 4be30d61b7..f2476223ce 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -21,6 +21,7 @@ import ( "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "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/common/ranges" @@ -257,7 +258,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] { +func uidToRange(uidList badoption.Listable[uint32]) []ranges.Range[uint32] { return common.Map(uidList, func(uid uint32) ranges.Range[uint32] { return ranges.NewSingle(uid) }) diff --git a/route/router.go b/route/router.go index b6cc8051ba..82b1f4b50d 100644 --- a/route/router.go +++ b/route/router.go @@ -239,9 +239,9 @@ func NewRouter( } var clientSubnet netip.Prefix if server.ClientSubnet != nil { - clientSubnet = server.ClientSubnet.Build() + clientSubnet = netip.Prefix(common.PtrValueOrDefault(server.ClientSubnet)) } else if dnsOptions.ClientSubnet != nil { - clientSubnet = dnsOptions.ClientSubnet.Build() + clientSubnet = netip.Prefix(common.PtrValueOrDefault(dnsOptions.ClientSubnet)) } if serverProtocol == "" { serverProtocol = "transport" diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 620260d078..0bf45ba25f 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -88,13 +88,13 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) Server: action.RouteOptions.Server, DisableCache: action.RouteOptions.DisableCache, RewriteTTL: action.RouteOptions.RewriteTTL, - ClientSubnet: action.RouteOptions.ClientSubnet.Build(), + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), } case C.RuleActionTypeRouteOptions: return &RuleActionDNSRouteOptions{ DisableCache: action.RouteOptionsOptions.DisableCache, RewriteTTL: action.RouteOptionsOptions.RewriteTTL, - ClientSubnet: action.RouteOptionsOptions.ClientSubnet.Build(), + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)), } case C.RuleActionTypeReject: return &RuleActionReject{ diff --git a/transport/sip003/v2ray.go b/transport/sip003/v2ray.go index c142180bbf..d7b752f6c3 100644 --- a/transport/sip003/v2ray.go +++ b/transport/sip003/v2ray.go @@ -12,6 +12,7 @@ import ( "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-vmess" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -67,7 +68,7 @@ func newV2RayPlugin(ctx context.Context, pluginOpts Args, router adapter.Router, transportOptions = option.V2RayTransportOptions{ Type: C.V2RayTransportTypeWebsocket, WebsocketOptions: option.V2RayWebsocketOptions{ - Headers: map[string]option.Listable[string]{ + Headers: map[string]badoption.Listable[string]{ "Host": []string{host}, }, Path: path, From 02ab8ce80667992b602fddf4355460ed4a0f5093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 19:05:34 +0800 Subject: [PATCH 0159/1272] documentation: Remove outdated icons --- docs/configuration/dns/index.md | 4 ---- docs/configuration/dns/index.zh.md | 4 ---- docs/configuration/dns/server.md | 4 ---- docs/configuration/dns/server.zh.md | 4 ---- docs/configuration/experimental/cache-file.md | 4 ---- docs/configuration/experimental/cache-file.zh.md | 4 ---- 6 files changed, 24 deletions(-) diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index c0eafccc3b..f37c93a4bb 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.9.0" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index ba390cef1c..c845ae0aa7 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.9.0 中的更改" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/dns/server.md b/docs/configuration/dns/server.md index 3c5245812d..5ec75faa51 100644 --- a/docs/configuration/dns/server.md +++ b/docs/configuration/dns/server.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.9.0" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/dns/server.zh.md b/docs/configuration/dns/server.zh.md index baa117510a..9f47054164 100644 --- a/docs/configuration/dns/server.zh.md +++ b/docs/configuration/dns/server.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.9.0 中的更改" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/experimental/cache-file.md b/docs/configuration/experimental/cache-file.md index b30538e591..18c430d973 100644 --- a/docs/configuration/experimental/cache-file.md +++ b/docs/configuration/experimental/cache-file.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.8.0" !!! quote "Changes in sing-box 1.9.0" diff --git a/docs/configuration/experimental/cache-file.zh.md b/docs/configuration/experimental/cache-file.zh.md index 6d86dc8423..656d53c4fe 100644 --- a/docs/configuration/experimental/cache-file.zh.md +++ b/docs/configuration/experimental/cache-file.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.8.0 起" !!! quote "sing-box 1.9.0 中的更改" From 467b1bbeeb2c6fe885490f6f9402616cb81e3953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 19:10:26 +0800 Subject: [PATCH 0160/1272] documentation: Update the scheduled removal time of deprecated features --- docs/configuration/dns/rule.md | 4 ++-- docs/configuration/route/geoip.md | 2 +- docs/configuration/route/geoip.zh.md | 2 +- docs/configuration/route/geosite.md | 2 +- docs/configuration/route/geosite.zh.md | 2 +- docs/configuration/route/rule.md | 6 +++--- docs/deprecated.md | 6 +++--- docs/deprecated.zh.md | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 03ca66bcc3..715c3b7fdb 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -218,7 +218,7 @@ Match domain using regular expression. !!! failure "Deprecated in sing-box 1.8.0" - Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). Match geosite. @@ -226,7 +226,7 @@ Match geosite. !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match source geoip. diff --git a/docs/configuration/route/geoip.md b/docs/configuration/route/geoip.md index 8a2ed1d4a1..a045574aa0 100644 --- a/docs/configuration/route/geoip.md +++ b/docs/configuration/route/geoip.md @@ -4,7 +4,7 @@ icon: material/delete-clock !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). ### Structure diff --git a/docs/configuration/route/geoip.zh.md b/docs/configuration/route/geoip.zh.md index fb2481e220..eb7bbe2d10 100644 --- a/docs/configuration/route/geoip.zh.md +++ b/docs/configuration/route/geoip.zh.md @@ -4,7 +4,7 @@ icon: material/delete-clock !!! failure "已在 sing-box 1.8.0 废弃" - GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。 + GeoIP 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。 ### 结构 diff --git a/docs/configuration/route/geosite.md b/docs/configuration/route/geosite.md index 0463057153..9a1b9dce03 100644 --- a/docs/configuration/route/geosite.md +++ b/docs/configuration/route/geosite.md @@ -4,7 +4,7 @@ icon: material/delete-clock !!! failure "Deprecated in sing-box 1.8.0" - Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). ### Structure diff --git a/docs/configuration/route/geosite.zh.md b/docs/configuration/route/geosite.zh.md index eeee38ff17..7cec5b2086 100644 --- a/docs/configuration/route/geosite.zh.md +++ b/docs/configuration/route/geosite.zh.md @@ -4,7 +4,7 @@ icon: material/delete-clock !!! failure "已在 sing-box 1.8.0 废弃" - Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。 + Geosite 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。 ### 结构 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index b5d17f215b..5e86560c6d 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -209,7 +209,7 @@ Match domain using regular expression. !!! failure "Deprecated in sing-box 1.8.0" - Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). Match geosite. @@ -217,7 +217,7 @@ Match geosite. !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match source geoip. @@ -225,7 +225,7 @@ Match source geoip. !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match geoip. diff --git a/docs/deprecated.md b/docs/deprecated.md index 9cb717cf28..604806f0f3 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -12,7 +12,7 @@ icon: material/delete-alert `inet4_route_address` and `inet6_route_address` are merged into `route_address`, `inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. -Old fields are deprecated and will be removed in sing-box 1.11.0. +Old fields are deprecated and will be removed in sing-box 1.12.0. #### Match source rule items are renamed @@ -32,7 +32,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o #### GeoIP -GeoIP is deprecated and may be removed in the future. +GeoIP is deprecated and may be removed in sing-box 1.12.0. The maxmind GeoIP National Database, as an IP classification database, is not entirely suitable for traffic bypassing, @@ -43,7 +43,7 @@ check [Migration](/migration/#migrate-geoip-to-rule-sets). #### Geosite -Geosite is deprecated and may be removed in the future. +Geosite is deprecated and will be removed in sing-box 1.12.0. Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution, suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management. diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 5bd2ea4585..64e155d19f 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -32,7 +32,7 @@ Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 ` #### GeoIP -GeoIP 已废弃且可能在不久的将来移除。 +GeoIP 已废弃且将在 sing-box 1.12.0 中被移除。 maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过, 且现有的实现均存在内存使用大与管理困难的问题。 @@ -42,7 +42,7 @@ sing-box 1.8.0 引入了[规则集](/configuration/rule-set/), #### Geosite -Geosite 已废弃且可能在不久的将来移除。 +Geosite 已废弃且将在 sing-box 1.12.0 中被移除。 Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案, 存在着包括缺少维护、规则不准确和管理困难内的大量问题。 From 28ec898a8c2c86870a22f5eb2db28b53c51c9dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 6 Nov 2024 19:02:55 +0800 Subject: [PATCH 0161/1272] documentation: Add rule action --- docs/configuration/dns/rule.md | 48 +++-- docs/configuration/dns/rule.zh.md | 2 +- docs/configuration/dns/rule_action.md | 85 +++++++++ docs/configuration/dns/rule_action.zh.md | 86 +++++++++ docs/configuration/outbound/block.md | 10 +- docs/configuration/outbound/block.zh.md | 10 +- docs/configuration/outbound/dns.md | 8 + docs/configuration/outbound/dns.zh.md | 8 + docs/configuration/route/rule.md | 19 +- docs/configuration/route/rule.zh.md | 19 +- docs/configuration/route/rule_action.md | 139 ++++++++++++++ docs/configuration/route/rule_action.zh.md | 136 ++++++++++++++ docs/configuration/shared/listen.md | 34 +++- docs/configuration/shared/listen.zh.md | 32 ++++ docs/deprecated.md | 30 ++- docs/deprecated.zh.md | 25 ++- docs/migration.md | 208 ++++++++++++++++++++- docs/migration.zh.md | 206 ++++++++++++++++++++ mkdocs.yml | 4 + option/inbound.go | 2 +- option/rule_action.go | 6 - route/route.go | 2 +- 22 files changed, 1076 insertions(+), 43 deletions(-) create mode 100644 docs/configuration/dns/rule_action.md create mode 100644 docs/configuration/dns/rule_action.zh.md create mode 100644 docs/configuration/route/rule_action.md create mode 100644 docs/configuration/route/rule_action.zh.md diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 715c3b7fdb..5cb24e8197 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -2,6 +2,14 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [action](#action) + :material-alert: [server](#server) + :material-alert: [disable_cache](#disable_cache) + :material-alert: [rewrite_ttl](#rewrite_ttl) + :material-alert: [client_subnet](#client_subnet) + !!! quote "Changes in sing-box 1.10.0" :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) @@ -14,7 +22,7 @@ icon: material/new-box :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) :material-plus: [ip_is_private](#ip_is_private) - :material-plus: [client_subnet](#client_subnet) + :material-plus: [client_subnet](#client_subnet) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) !!! quote "Changes in sing-box 1.8.0" @@ -135,19 +143,15 @@ icon: material/new-box "outbound": [ "direct" ], - "server": "local", - "disable_cache": false, - "rewrite_ttl": 100, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" }, { "type": "logical", "mode": "and", "rules": [], - "server": "local", - "disable_cache": false, - "rewrite_ttl": 100, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" } ] } @@ -354,29 +358,35 @@ Match outbound. `any` can be used as a value to match any outbound. -#### server +#### action ==Required== -Tag of the target dns server. +See [DNS Rule Actions](../rule_action/) for details. + +#### server + +!!! failure "Deprecated in sing-box 1.11.0" + + Moved to [DNS Rule Action](../rule_action#route). #### disable_cache -Disable cache and save cache in this query. +!!! failure "Deprecated in sing-box 1.11.0" -#### rewrite_ttl + Moved to [DNS Rule Action](../rule_action#route). -Rewrite TTL in DNS responses. +#### rewrite_ttl -#### client_subnet +!!! failure "Deprecated in sing-box 1.11.0" -!!! question "Since sing-box 1.9.0" + Moved to [DNS Rule Action](../rule_action#route). -Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. +#### client_subnet -If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. +!!! failure "Deprecated in sing-box 1.11.0" -Will overrides `dns.client_subnet` and `servers.[].client_subnet`. + Moved to [DNS Rule Action](../rule_action#route). ### Address Filter Fields diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index b484cbed6b..205b01ae00 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -14,7 +14,7 @@ icon: material/new-box :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) :material-plus: [ip_is_private](#ip_is_private) - :material-plus: [client_subnet](#client_subnet) + :material-plus: [client_subnet](#client_subnet) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) !!! quote "sing-box 1.8.0 中的更改" diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md new file mode 100644 index 0000000000..8943b65351 --- /dev/null +++ b/docs/configuration/dns/rule_action.md @@ -0,0 +1,85 @@ +--- +icon: material/new-box +--- + +# DNS Rule Action + +!!! question "Since sing-box 1.11.0" + +### route + +```json +{ + "action": "route", // default + "server": "", + + // for compatibility + "disable_cache": false, + "rewrite_ttl": 0, + "client_subnet": null +} +``` + +`route` inherits the classic rule behavior of routing DNS requests to the specified server. + +#### server + +==Required== + +Tag of target server. + +#### disable_cache/rewrite_ttl/client_subnet + +!!! failure "Deprecated in sing-box 1.11.0" + + Legacy route options is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-legacy-dns-route-options-to-rule-actions). + +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null +} +``` + +#### disable_cache + +Disable cache and save cache in this query. + +#### rewrite_ttl + +Rewrite TTL in DNS responses. + +#### client_subnet + +Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. + +If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. + +Will overrides `dns.client_subnet` and `servers.[].client_subnet`. + +### reject + +```json +{ + "action": "reject", + "method": "default", // default + "no_drop": false +} +``` + +`reject` reject DNS requests. + +#### method + +- `default`: Reply with NXDOMAIN. +- `drop`: Drop the request. + +#### no_drop + +If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. + +Not available when `method` is set to drop. diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md new file mode 100644 index 0000000000..8a9dc07eea --- /dev/null +++ b/docs/configuration/dns/rule_action.zh.md @@ -0,0 +1,86 @@ +--- +icon: material/new-box +--- + +# DNS 规则动作 + +!!! question "自 sing-box 1.11.0 起" + +### route + +```json +{ + "action": "route", // 默认 + "server": "", + + // 兼容性 + "disable_cache": false, + "rewrite_ttl": 0, + "client_subnet": null +} +``` + +`route` 继承了将 DNS 请求 路由到指定服务器的经典规则动作。 + +#### server + +==必填== + +目标 DNS 服务器的标签。 + +#### disable_cache/rewrite_ttl/client_subnet + +!!! failure "自 sing-box 1.11.0 起" + + 旧的路由选项已弃用,且将在 sing-box 1.12.0 中移除,参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions). + +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null +} +``` + + +#### disable_cache + +在此查询中禁用缓存。 + +#### rewrite_ttl + +重写 DNS 回应中的 TTL。 + +#### client_subnet + +默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 + +将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 + +### reject + +```json +{ + "action": "reject", + "method": "default", // default + "no_drop": false +} +``` + +`reject` 拒绝 DNS 请求。 + +#### method + +- `default`: 返回 NXDOMAIN。 +- `drop`: 丢弃请求。 + +#### no_drop + +如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 + +当 `method` 设为 `drop` 时不可用。 diff --git a/docs/configuration/outbound/block.md b/docs/configuration/outbound/block.md index e27a4b3ef3..f29120ccf2 100644 --- a/docs/configuration/outbound/block.md +++ b/docs/configuration/outbound/block.md @@ -1,8 +1,14 @@ -`block` outbound closes all incoming requests. +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.11.0" + + Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions). ### Structure -```json +```json F { "type": "block", "tag": "block" diff --git a/docs/configuration/outbound/block.zh.md b/docs/configuration/outbound/block.zh.md index bc0762e3ab..822478cea8 100644 --- a/docs/configuration/outbound/block.zh.md +++ b/docs/configuration/outbound/block.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "已在 sing-box 1.11.0 废弃" + + 旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions). + `block` 出站关闭所有传入请求。 ### 结构 @@ -11,4 +19,4 @@ ### 字段 -无字段。 \ No newline at end of file +无字段。 diff --git a/docs/configuration/outbound/dns.md b/docs/configuration/outbound/dns.md index 1f8c547701..d73360413e 100644 --- a/docs/configuration/outbound/dns.md +++ b/docs/configuration/outbound/dns.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.11.0" + + Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions). + `dns` outbound is a internal DNS server. ### Structure diff --git a/docs/configuration/outbound/dns.zh.md b/docs/configuration/outbound/dns.zh.md index 67538f6e02..3db2fefb0a 100644 --- a/docs/configuration/outbound/dns.zh.md +++ b/docs/configuration/outbound/dns.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "已在 sing-box 1.11.0 废弃" + + 旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除, 参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions). + `dns` 出站是一个内部 DNS 服务器。 ### 结构 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 5e86560c6d..fe40d56518 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -1,7 +1,12 @@ --- -icon: material/alert-decagram +icon: material/new-box --- +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [action](#action) + :material-alert: [outbound](#outbound) + !!! quote "Changes in sing-box 1.10.0" :material-plus: [client](#client) @@ -129,6 +134,7 @@ icon: material/alert-decagram "rule_set_ipcidr_match_source": false, "rule_set_ip_cidr_match_source": false, "invert": false, + "action": "route", "outbound": "direct" }, { @@ -136,6 +142,7 @@ icon: material/alert-decagram "mode": "and", "rules": [], "invert": false, + "action": "route", "outbound": "direct" } ] @@ -357,11 +364,17 @@ Make `ip_cidr` in rule-sets match the source IP. Invert match result. -#### outbound +#### action ==Required== -Tag of the target outbound. +See [Rule Actions](../rule_action/) for details. + +#### outbound + +!!! failure "Deprecated in sing-box 1.11.0" + + Moved to [Rule Action](../rule_action#route). ### Logical Fields diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index a93ce5e53d..316339f677 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -1,7 +1,12 @@ --- -icon: material/alert-decagram +icon: material/new-box --- +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [action](#action) + :material-alert: [outbound](#outbound) + !!! quote "sing-box 1.10.0 中的更改" :material-plus: [client](#client) @@ -127,6 +132,7 @@ icon: material/alert-decagram "rule_set_ipcidr_match_source": false, "rule_set_ip_cidr_match_source": false, "invert": false, + "action": "route", "outbound": "direct" }, { @@ -134,6 +140,7 @@ icon: material/alert-decagram "mode": "and", "rules": [], "invert": false, + "action": "route", "outbound": "direct" } ] @@ -355,11 +362,17 @@ icon: material/alert-decagram 反选匹配结果。 -#### outbound +#### action ==必填== -目标出站的标签。 +参阅 [规则行动](../rule_action/)。 + +#### outbound + +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [规则行动](../rule_action#route). ### 逻辑字段 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md new file mode 100644 index 0000000000..843d756366 --- /dev/null +++ b/docs/configuration/route/rule_action.md @@ -0,0 +1,139 @@ +--- +icon: material/new-box +--- + +# Rule Action + +!!! question "Since sing-box 1.11.0" + +## Final actions + +### route + +```json +{ + "action": "route", // default + "outbound": "" +} +``` + +`route` inherits the classic rule behavior of routing connection to the specified outbound. + +#### outbound + +==Required== + +Tag of target outbound. + +### route-options + +```json +{ + "action": "route-options", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +`route-options` set options for routing. + +#### udp_disable_domain_unmapping + +If enabled, for UDP proxy requests addressed to a domain, +the original packet address will be sent in the response instead of the mapped domain. + +This option is used for compatibility with clients that +do not support receiving UDP packets with domain addresses, such as Surge. + +#### udp_connect + +If enabled, attempts to connect UDP connection to the destination instead of listen. + +### reject + +```json +{ + "action": "reject", + "method": "default", // default + "no_drop": false +} +``` + +`reject` reject connections + +The specified method is used for reject tun connections if `sniff` action has not been performed yet. + +For non-tun connections and already established connections, will just be closed. + +#### method + +- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets. +- `drop`: Drop packets. + +#### no_drop + +If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. + +Not available when `method` is set to drop. + +### hijack-dns + +```json +{ + "action": "hijack-dns" +} +``` + +`hijack-dns` hijack DNS requests to the sing-box DNS module. + +## Non-final actions + +### sniff + +```json +{ + "action": "sniff", + "sniffer": [], + "timeout": "" +} +``` + +`sniff` performs protocol sniffing on connections. + +For deprecated `inbound.sniff` options, it is considered to `sniff()` performed before routing. + +#### sniffer + +Enabled sniffers. + +All sniffers enabled by default. + +Available protocol values an be found on in [Protocol Sniff](../sniff/) + +#### timeout + +Timeout for sniffing. + +`300ms` is used by default. + +### resolve + +```json +{ + "action": "resolve", + "strategy": "", + "server": "" +} +``` + +`resolve` resolve request destination from domain to IP addresses. + +#### strategy + +DNS resolution strategy, available values are: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. + +`dns.strategy` will be used by default. + +#### server + +Specifies DNS server tag to use instead of selecting through DNS routing. diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md new file mode 100644 index 0000000000..ae16d85fde --- /dev/null +++ b/docs/configuration/route/rule_action.zh.md @@ -0,0 +1,136 @@ +--- +icon: material/new-box +--- + +# 规则动作 + +!!! question "自 sing-box 1.11.0 起" + +## 最终动作 + +### route + +```json +{ + "action": "route", // 默认 + "outbound": "", + "udp_disable_domain_unmapping": false +} +``` + +`route` 继承了将连接路由到指定出站的经典规则动作。 + +#### outbound + +==必填== + +目标出站的标签。 + +### route-options + +```json +{ + "action": "route-options", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +#### udp_disable_domain_unmapping + +如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 + +此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。 + +#### udp_connect + +如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 + +### reject + +```json +{ + "action": "reject", + "method": "default", // 默认 + "no_drop": false +} +``` + +`reject` 拒绝连接。 + +如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。 + +对于非 tun 连接和已建立的连接,将直接关闭。 + +#### method + +- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。 +- `drop`: 丢弃数据包。 + +#### no_drop + +如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 + +当 `method` 设为 `drop` 时不可用。 + +### hijack-dns + +```json +{ + "action": "hijack-dns" +} +``` + +`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。 + +## 非最终动作 + +### sniff + +```json +{ + "action": "sniff", + "sniffer": [], + "timeout": "" +} +``` + +`sniff` 对连接执行协议嗅探。 + +对于已弃用的 `inbound.sniff` 选项,被视为在路由之前执行的 `sniff`。 + +#### sniffer + +启用的探测器。 + +默认启用所有探测器。 + +可用的协议值可以在 [协议嗅探](../sniff/) 中找到。 + +#### timeout + +探测超时时间。 + +默认使用 300ms。 + +### resolve + +```json +{ + "action": "resolve", + "strategy": "", + "server": "" +} +``` + +`resolve` 将请求的目标从域名解析为 IP 地址。 + +#### strategy + +DNS 解析策略,可用值有:`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、`ipv6_only`。 + +默认使用 `dns.strategy`。 + +#### server + +指定要使用的 DNS 服务器的标签,而不是通过 DNS 路由进行选择。 diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index ae3ed6a403..fa6a05b97c 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -1,3 +1,15 @@ +--- +icon: material/delete-clock +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-delete-clock: [sniff](#sniff) + :material-delete-clock: [sniff_override_destination](#sniff_override_destination) + :material-delete-clock: [sniff_timeout](#sniff_timeout) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping) + ### Structure ```json @@ -68,24 +80,40 @@ Requires target inbound support, see [Injectable](/configuration/inbound/#fields #### sniff +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + Enable sniffing. See [Protocol Sniff](/configuration/route/sniff/) for details. #### sniff_override_destination +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0. + Override the connection destination address with the sniffed domain. If the domain name is invalid (like tor), this will not work. #### sniff_timeout +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + Timeout for sniffing. -300ms is used by default. +`300ms` is used by default. #### domain_strategy +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. If set, the requested domain name will be resolved to IP before routing. @@ -94,6 +122,10 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb #### udp_disable_domain_unmapping +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + If enabled, for UDP proxy requests addressed to a domain, the original packet address will be sent in the response instead of the mapped domain. diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index 398c98c53d..3b472c4dff 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -1,3 +1,15 @@ +--- +icon: material/delete-clock +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-delete-clock: [sniff](#sniff) + :material-delete-clock: [sniff_override_destination](#sniff_override_destination) + :material-delete-clock: [sniff_timeout](#sniff_timeout) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping) + ### 结构 ```json @@ -69,24 +81,40 @@ UDP NAT 过期时间,以秒为单位。 #### sniff +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + 启用协议探测。 参阅 [协议探测](/zh/configuration/route/sniff/) #### sniff_override_destination +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除。 + 用探测出的域名覆盖连接目标地址。 如果域名无效(如 Tor),将不生效。 #### sniff_timeout +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + 探测超时时间。 默认使用 300ms。 #### domain_strategy +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + 可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 如果设置,请求的域名将在路由之前解析为 IP。 @@ -95,6 +123,10 @@ UDP NAT 过期时间,以秒为单位。 #### udp_disable_domain_unmapping +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + 如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。 diff --git a/docs/deprecated.md b/docs/deprecated.md index 604806f0f3..f057319ade 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -4,6 +4,32 @@ icon: material/delete-alert # Deprecated Feature List +## 1.11.0 + +#### Legacy special outbounds + +Legacy special outbounds (`block` / `dns`) are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-legacy-special-outbounds-to-rule-actions). + +Old fields will be removed in sing-box 1.13.0. + +#### Legacy inbound fields + +Legacy inbound fields (`inbound.` are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions). + +Old fields will be removed in sing-box 1.13.0. + +#### Legacy DNS route options + +Legacy DNS route options (`disable_cache`, `rewrite_ttl`, `client_subnet`) are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-legacy-dns-route-options-to-rule-actions). + +Old fields will be removed in sing-box 1.12.0. + ## 1.10.0 #### TUN address fields are merged @@ -12,7 +38,7 @@ icon: material/delete-alert `inet4_route_address` and `inet6_route_address` are merged into `route_address`, `inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. -Old fields are deprecated and will be removed in sing-box 1.12.0. +Old fields will be removed in sing-box 1.12.0. #### Match source rule items are renamed @@ -32,7 +58,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o #### GeoIP -GeoIP is deprecated and may be removed in sing-box 1.12.0. +GeoIP is deprecated and will be removed in sing-box 1.12.0. The maxmind GeoIP National Database, as an IP classification database, is not entirely suitable for traffic bypassing, diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 64e155d19f..2f7b28f7bd 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -4,6 +4,29 @@ icon: material/delete-alert # 废弃功能列表 +## 1.11.0 + +#### 旧的特殊出站 + +旧的特殊出站(`block` / `dns`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions)。 + +旧字段将在 sing-box 1.13.0 中被移除。 + +#### 旧的入站字段 + +旧的入站字段(`inbound.`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions)。 + +旧字段将在 sing-box 1.13.0 中被移除。 + +#### 旧的 DNS 路由参数 + +旧的 DNS 路由参数(`disable_cache`、`rewrite_ttl`、`client_subnet`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions)。 + +旧字段将在 sing-box 1.12.0 中被移除。 + ## 1.10.0 #### Match source 规则项已重命名 @@ -17,7 +40,7 @@ icon: material/delete-alert `inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`, `inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。 -旧字段已废弃,且将在 sing-box 1.11.0 中被移除。 +旧字段将在 sing-box 1.11.0 中被移除。 #### 移除对 go1.18 和 go1.19 的支持 diff --git a/docs/migration.md b/docs/migration.md index 71b6169216..9207db5bc9 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -2,6 +2,212 @@ icon: material/arrange-bring-forward --- +## 1.11.0 + +### Migrate legacy special outbounds to rule actions + +Legacy special outbounds are deprecated and can be replaced by rule actions. + +!!! info "References" + + [Rule Action](/configuration/route/rule_action/) / + [Block](/configuration/outbound/block/) / + [DNS](/configuration/outbound/dns) + +=== "Block" + + === ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "block", + "tag": "block" + } + ], + "route": { + "rules": [ + { + ..., + + "outbound": "block" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "route": { + "rules": [ + { + ..., + + "action": "reject" + } + ] + } + } + ``` + +=== "DNS" + + === ":material-card-remove: Deprecated" + + ```json + { + "inbound": [ + { + ..., + + "sniff": true + } + ], + "outbounds": [ + { + "tag": "dns", + "type": "dns" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "route": { + "rules": [ + { + "action": "sniff" + }, + { + "protocol": "dns", + "action": "hijack-dns" + } + ] + } + } + ``` + +### Migrate legacy inbound fields to rule actions + +Inbound fields are deprecated and can be replaced by rule actions. + +!!! info "References" + + [Listen Fields](/configuration/inbound/listen/) / + [Rule](/configuration/route/rule/) / + [Rule Action](/configuration/route/rule_action/) / + [DNS Rule](/configuration/dns/rule/) / + [DNS Rule Action](/configuration/dns/rule_action/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "sniff": true, + "sniff_timeout": "1s", + "domain_strategy": "prefer_ipv4" + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "tag": "in" + } + ], + "route": { + "rules": [ + { + "inbound": "in", + "action": "resolve", + "strategy": "prefer_ipv4" + }, + { + "inbound": "in", + "action": "sniff", + "timeout": "1s" + } + ] + } + } + ``` + +### Migrate legacy DNS route options to rule actions + +Legacy DNS route options are deprecated and can be replaced by rule actions. + +!!! info "References" + + [DNS Rule](/configuration/dns/rule/) / + [DNS Rule Action](/configuration/dns/rule_action/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "rules": [ + { + ..., + + "server": "local", + "disable_cache": true, + "rewrite_ttl": 600, + "client_subnet": "1.1.1.1/24" + } + ] + } + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "dns": { + "rules": [ + { + ..., + + "action": "route-options", + "disable_cache": true, + "rewrite_ttl": 600, + "client_subnet": "1.1.1.1/24" + }, + { + ..., + + "server": "local" + } + ] + } + } + ``` + ## 1.10.0 ### TUN address fields are merged @@ -10,8 +216,6 @@ icon: material/arrange-bring-forward `inet4_route_address` and `inet6_route_address` are merged into `route_address`, `inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. -Old fields are deprecated and will be removed in sing-box 1.11.0. - !!! info "References" [TUN](/configuration/inbound/tun/) diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 62fbe9edeb..f51860f720 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -2,6 +2,212 @@ icon: material/arrange-bring-forward --- +## 1.11.0 + +### 迁移旧的特殊出站到规则动作 + +旧的特殊出站已被弃用,且可以被规则动作替代。 + +!!! info "参考" + + [规则动作](/zh/configuration/route/rule_action/) / + [Block](/zh/configuration/outbound/block/) / + [DNS](/zh/configuration/outbound/dns) + +=== "Block" + + === ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "block", + "tag": "block" + } + ], + "route": { + "rules": [ + { + ..., + + "outbound": "block" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "route": { + "rules": [ + { + ..., + + "action": "reject" + } + ] + } + } + ``` + +=== "DNS" + + === ":material-card-remove: 弃用的" + + ```json + { + "inbound": [ + { + ..., + + "sniff": true + } + ], + "outbounds": [ + { + "tag": "dns", + "type": "dns" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "route": { + "rules": [ + { + "action": "sniff" + }, + { + "protocol": "dns", + "action": "hijack-dns" + } + ] + } + } + ``` + +### 迁移旧的入站字段到规则动作 + +入站选项已被弃用,且可以被规则动作替代。 + +!!! info "参考" + + [监听字段](/zh/configuration/shared/listen/) / + [规则](/zh/configuration/route/rule/) / + [规则动作](/zh/configuration/route/rule_action/) / + [DNS 规则](/zh/configuration/dns/rule/) / + [DNS 规则动作](/zh/configuration/dns/rule_action/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "sniff": true, + "sniff_timeout": "1s", + "domain_strategy": "prefer_ipv4" + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "tag": "in" + } + ], + "route": { + "rules": [ + { + "inbound": "in", + "action": "resolve", + "strategy": "prefer_ipv4" + }, + { + "inbound": "in", + "action": "sniff", + "timeout": "1s" + } + ] + } + } + ``` + +### 迁移旧的 DNS 路由选项到规则动作 + +旧的 DNS 路由选项已被弃用,且可以被规则动作替代。 + +!!! info "参考" + + [DNS 规则](/zh/configuration/dns/rule/) / + [DNS 规则动作](/zh/configuration/dns/rule_action/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "rules": [ + { + ..., + + "server": "local", + "disable_cache": true, + "rewrite_ttl": 600, + "client_subnet": "1.1.1.1/24" + } + ] + } + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "dns": { + "rules": [ + { + ..., + + "action": "route-options", + "disable_cache": true, + "rewrite_ttl": 600, + "client_subnet": "1.1.1.1/24" + }, + { + ..., + + "server": "local" + } + ] + } + } + ``` + ## 1.10.0 ### TUN 地址字段已合并 diff --git a/mkdocs.yml b/mkdocs.yml index d5cdcee470..66e8a2e99b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,6 +82,7 @@ nav: - configuration/dns/index.md - DNS Server: configuration/dns/server.md - DNS Rule: configuration/dns/rule.md + - DNS Rule Action: configuration/dns/rule_action.md - FakeIP: configuration/dns/fakeip.md - NTP: - configuration/ntp/index.md @@ -90,6 +91,7 @@ nav: - GeoIP: configuration/route/geoip.md - Geosite: configuration/route/geosite.md - Route Rule: configuration/route/rule.md + - Rule Action: configuration/route/rule_action.md - Protocol Sniff: configuration/route/sniff.md - Rule Set: - configuration/rule-set/index.md @@ -218,9 +220,11 @@ plugins: Log: 日志 DNS Server: DNS 服务器 DNS Rule: DNS 规则 + DNS Rule Action: DNS 规则动作 Route: 路由 Route Rule: 路由规则 + Rule Action: 规则动作 Protocol Sniff: 协议探测 Rule Set: 规则集 diff --git a/option/inbound.go b/option/inbound.go index a67719fa04..2cc1598946 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -34,7 +34,7 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro } registry := service.FromContext[InboundOptionsRegistry](ctx) if registry == nil { - return E.New("missing inbound options registry in context") + return E.New("missing Inbound fields registry in context") } options, loaded := registry.CreateOptions(h.Type) if !loaded { diff --git a/option/rule_action.go b/option/rule_action.go index 7a76391ca6..3b4e8edb5f 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -148,9 +148,6 @@ func (r *RouteActionOptions) UnmarshalJSON(data []byte) error { if err != nil { return err } - if r.Outbound == "" { - return E.New("missing outbound") - } return nil } @@ -189,9 +186,6 @@ func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data [ if err != nil { return err } - if r.Server == "" { - return E.New("missing server") - } if r.DisableCache || r.RewriteTTL != nil || r.ClientSubnet != nil { deprecated.Report(ctx, deprecated.OptionLegacyDNSRouteOptions) } diff --git a/route/route.go b/route/route.go index 854fa4f19f..91d7f3c9bf 100644 --- a/route/route.go +++ b/route/route.go @@ -76,7 +76,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad metadata.Network = N.NetworkTCP switch metadata.Destination.Fqdn { case mux.Destination.Fqdn: - return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in inbound options instead.") + return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in Inbound fields instead.") case vmess.MuxDestination.Fqdn: return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.") case uot.MagicAddress: From 19fb21422651978246ae70c16ff87d9d241bbea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 9 Nov 2024 21:16:11 +0800 Subject: [PATCH 0162/1272] refactor: Modular inbound/outbound manager --- adapter/experimental.go | 4 +- adapter/inbound.go | 10 +- adapter/inbound/manager.go | 143 +++++ adapter/inbound/registry.go | 44 +- adapter/lifecycle.go | 41 ++ adapter/lifecycle_legacy.go | 33 ++ adapter/outbound.go | 9 + adapter/outbound/manager.go | 265 +++++++++ adapter/outbound/registry.go | 8 +- adapter/prestart.go | 8 - adapter/router.go | 18 +- box.go | 138 +++-- box_outbound.go | 85 --- cmd/sing-box/cmd_tools.go | 6 +- cmd/sing-box/cmd_tools_connect.go | 2 +- cmd/sing-box/cmd_tools_fetch.go | 2 +- cmd/sing-box/cmd_tools_fetch_http3.go | 2 +- cmd/sing-box/cmd_tools_synctime.go | 3 +- common/dialer/detour.go | 16 +- common/dialer/dialer.go | 18 +- common/dialer/router.go | 28 +- common/settings/proxy_darwin.go | 3 +- common/tls/ech_client.go | 3 +- common/tls/reality_server.go | 3 +- experimental/clashapi.go | 6 +- experimental/clashapi/api_meta_group.go | 8 +- experimental/clashapi/proxies.go | 19 +- experimental/clashapi/server.go | 64 +-- experimental/clashapi/server_resources.go | 8 +- .../clashapi/trafficontrol/tracker.go | 16 +- protocol/direct/inbound.go | 29 +- protocol/direct/outbound.go | 4 +- protocol/group/selector.go | 6 +- protocol/group/urltest.go | 10 +- protocol/http/inbound.go | 16 +- protocol/http/outbound.go | 2 +- protocol/hysteria/inbound.go | 20 +- protocol/hysteria/outbound.go | 2 +- protocol/hysteria2/inbound.go | 20 +- protocol/hysteria2/outbound.go | 2 +- protocol/mixed/inbound.go | 14 +- protocol/redirect/redirect.go | 2 + protocol/redirect/tproxy.go | 4 + protocol/shadowsocks/inbound.go | 8 +- protocol/shadowsocks/inbound_multi.go | 8 +- protocol/shadowsocks/inbound_relay.go | 8 +- protocol/shadowsocks/outbound.go | 2 +- protocol/shadowtls/inbound.go | 40 +- protocol/shadowtls/outbound.go | 2 +- protocol/socks/inbound.go | 14 +- protocol/socks/outbound.go | 2 +- protocol/ssh/outbound.go | 2 +- protocol/tor/outbound.go | 2 +- protocol/trojan/inbound.go | 24 +- protocol/trojan/outbound.go | 2 +- protocol/tuic/inbound.go | 20 +- protocol/tuic/outbound.go | 2 +- protocol/vless/inbound.go | 20 +- protocol/vless/outbound.go | 2 +- protocol/vmess/inbound.go | 20 +- protocol/vmess/outbound.go | 2 +- protocol/wireguard/outbound.go | 2 +- route/dns.go | 1 - route/router.go | 507 ++++++++---------- route/rule/rule_action.go | 4 +- route/rule/rule_default.go | 4 +- route/rule/rule_set_remote.go | 58 +- transport/dhcp/server.go | 3 +- transport/fakeip/server.go | 3 +- 69 files changed, 1169 insertions(+), 737 deletions(-) create mode 100644 adapter/inbound/manager.go create mode 100644 adapter/lifecycle.go create mode 100644 adapter/lifecycle_legacy.go create mode 100644 adapter/outbound/manager.go delete mode 100644 box_outbound.go diff --git a/adapter/experimental.go b/adapter/experimental.go index 0cab5ed5a8..bee24c4f8a 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -15,7 +15,7 @@ import ( type ClashServer interface { Service - PreStarter + LegacyPreStarter Mode() string ModeList() []string HistoryStorage() *urltest.HistoryStorage @@ -25,7 +25,7 @@ type ClashServer interface { type CacheFile interface { Service - PreStarter + LegacyPreStarter StoreFakeIP() bool FakeIPStorage diff --git a/adapter/inbound.go b/adapter/inbound.go index f9ed17085a..d80e59f7b9 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -28,7 +28,15 @@ type UDPInjectableInbound interface { type InboundRegistry interface { option.InboundOptionsRegistry - CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error) + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error) +} + +type InboundManager interface { + NewService + Inbounds() []Inbound + Get(tag string) (Inbound, bool) + Remove(tag string) error + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error } type InboundContext struct { diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go new file mode 100644 index 0000000000..d2be4b578c --- /dev/null +++ b/adapter/inbound/manager.go @@ -0,0 +1,143 @@ +package inbound + +import ( + "context" + "os" + "sync" + + "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/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +var _ adapter.InboundManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.InboundRegistry + access sync.Mutex + started bool + stage adapter.StartStage + inbounds []adapter.Inbound + inboundByTag map[string]adapter.Inbound +} + +func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry) *Manager { + return &Manager{ + logger: logger, + registry: registry, + inboundByTag: make(map[string]adapter.Inbound), + } +} + +func (m *Manager) Start(stage adapter.StartStage) error { + m.access.Lock() + defer m.access.Unlock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + for _, inbound := range m.inbounds { + err := adapter.LegacyStart(inbound, stage) + if err != nil { + return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + } + } + return nil +} + +func (m *Manager) Close() error { + m.access.Lock() + if !m.started { + panic("not started") + } + m.started = false + inbounds := m.inbounds + m.inbounds = nil + m.access.Unlock() + monitor := taskmonitor.New(m.logger, C.StopTimeout) + var err error + for _, inbound := range inbounds { + monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]") + err = E.Append(err, inbound.Close(), func(err error) error { + return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]") + }) + monitor.Finish() + } + return nil +} + +func (m *Manager) Inbounds() []adapter.Inbound { + m.access.Lock() + defer m.access.Unlock() + return m.inbounds +} + +func (m *Manager) Get(tag string) (adapter.Inbound, bool) { + m.access.Lock() + defer m.access.Unlock() + inbound, found := m.inboundByTag[tag] + return inbound, found +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + inbound, found := m.inboundByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.inboundByTag, tag) + index := common.Index(m.inbounds, func(it adapter.Inbound) bool { + return it == inbound + }) + if index == -1 { + panic("invalid inbound index") + } + m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...) + started := m.started + m.access.Unlock() + if started { + return inbound.Close() + } + return nil +} + +func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error { + inbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + for _, stage := range adapter.ListStartStages { + err = adapter.LegacyStart(inbound, stage) + if err != nil { + return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + } + } + } + if existsInbound, loaded := m.inboundByTag[tag]; loaded { + if m.started { + err = existsInbound.Close() + if err != nil { + return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]") + } + } + existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool { + return it == existsInbound + }) + if existsIndex == -1 { + panic("invalid inbound index") + } + m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...) + } + m.inbounds = append(m.inbounds, inbound) + m.inboundByTag[tag] = inbound + return nil +} diff --git a/adapter/inbound/registry.go b/adapter/inbound/registry.go index 9f678c90e7..01e367d8b0 100644 --- a/adapter/inbound/registry.go +++ b/adapter/inbound/registry.go @@ -15,8 +15,12 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { registry.register(outboundType, func() any { return new(Options) - }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) { - return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options))) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options)) }) } @@ -28,41 +32,41 @@ type ( ) type Registry struct { - access sync.Mutex - optionsType map[string]optionsConstructorFunc - constructors map[string]constructorFunc + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructor map[string]constructorFunc } func NewRegistry() *Registry { return &Registry{ - optionsType: make(map[string]optionsConstructorFunc), - constructors: make(map[string]constructorFunc), + optionsType: make(map[string]optionsConstructorFunc), + constructor: make(map[string]constructorFunc), } } -func (r *Registry) CreateOptions(outboundType string) (any, bool) { - r.access.Lock() - defer r.access.Unlock() - optionsConstructor, loaded := r.optionsType[outboundType] +func (m *Registry) CreateOptions(outboundType string) (any, bool) { + m.access.Lock() + defer m.access.Unlock() + optionsConstructor, loaded := m.optionsType[outboundType] if !loaded { return nil, false } return optionsConstructor(), true } -func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) { - r.access.Lock() - defer r.access.Unlock() - constructor, loaded := r.constructors[outboundType] +func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) { + m.access.Lock() + defer m.access.Unlock() + constructor, loaded := m.constructor[outboundType] if !loaded { return nil, E.New("outbound type not found: " + outboundType) } return constructor(ctx, router, logger, tag, options) } -func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { - r.access.Lock() - defer r.access.Unlock() - r.optionsType[outboundType] = optionsConstructor - r.constructors[outboundType] = constructor +func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + m.access.Lock() + defer m.access.Unlock() + m.optionsType[outboundType] = optionsConstructor + m.constructor[outboundType] = constructor } diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go new file mode 100644 index 0000000000..85de425d5a --- /dev/null +++ b/adapter/lifecycle.go @@ -0,0 +1,41 @@ +package adapter + +type StartStage uint8 + +const ( + StartStateInitialize StartStage = iota + StartStateStart + StartStatePostStart + StartStateStarted +) + +var ListStartStages = []StartStage{ + StartStateInitialize, + StartStateStart, + StartStatePostStart, + StartStateStarted, +} + +func (s StartStage) Action() string { + switch s { + case StartStateInitialize: + return "initialize" + case StartStateStart: + return "start" + case StartStatePostStart: + return "post-start" + case StartStateStarted: + return "start-after-started" + default: + panic("unknown stage") + } +} + +type NewService interface { + NewStarter + Close() error +} + +type NewStarter interface { + Start(stage StartStage) error +} diff --git a/adapter/lifecycle_legacy.go b/adapter/lifecycle_legacy.go new file mode 100644 index 0000000000..5968131bb4 --- /dev/null +++ b/adapter/lifecycle_legacy.go @@ -0,0 +1,33 @@ +package adapter + +type LegacyPreStarter interface { + PreStart() error +} + +type LegacyPostStarter interface { + PostStart() error +} + +func LegacyStart(starter any, stage StartStage) error { + switch stage { + case StartStateInitialize: + if preStarter, isPreStarter := starter.(interface { + PreStart() error + }); isPreStarter { + return preStarter.PreStart() + } + case StartStateStart: + if starter, isStarter := starter.(interface { + Start() error + }); isStarter { + return starter.Start() + } + case StartStatePostStart: + if postStarter, isPostStarter := starter.(interface { + PostStart() error + }); isPostStarter { + return postStarter.PostStart() + } + } + return nil +} diff --git a/adapter/outbound.go b/adapter/outbound.go index df11ed6132..b170398a96 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -22,3 +22,12 @@ type OutboundRegistry interface { option.OutboundOptionsRegistry CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) } + +type OutboundManager interface { + NewService + Outbounds() []Outbound + Outbound(tag string) (Outbound, bool) + Default() Outbound + Remove(tag string) error + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error +} diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go new file mode 100644 index 0000000000..b3e1a170bf --- /dev/null +++ b/adapter/outbound/manager.go @@ -0,0 +1,265 @@ +package outbound + +import ( + "context" + "io" + "os" + "strings" + "sync" + + "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/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +var _ adapter.OutboundManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.OutboundRegistry + defaultTag string + access sync.Mutex + started bool + stage adapter.StartStage + outbounds []adapter.Outbound + outboundByTag map[string]adapter.Outbound + dependByTag map[string][]string + defaultOutbound adapter.Outbound + defaultOutboundFallback adapter.Outbound +} + +func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, defaultTag string) *Manager { + return &Manager{ + logger: logger, + registry: registry, + defaultTag: defaultTag, + outboundByTag: make(map[string]adapter.Outbound), + dependByTag: make(map[string][]string), + } +} + +func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) { + m.defaultOutboundFallback = defaultOutboundFallback +} + +func (m *Manager) Start(stage adapter.StartStage) error { + m.access.Lock() + defer m.access.Unlock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + if stage == adapter.StartStateStart { + m.startOutbounds() + } else { + for _, outbound := range m.outbounds { + err := adapter.LegacyStart(outbound, stage) + if err != nil { + return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + } + } + } + return nil +} + +func (m *Manager) startOutbounds() error { + monitor := taskmonitor.New(m.logger, C.StartTimeout) + started := make(map[string]bool) + for { + canContinue := false + startOne: + for _, outboundToStart := range m.outbounds { + outboundTag := outboundToStart.Tag() + if started[outboundTag] { + continue + } + dependencies := outboundToStart.Dependencies() + for _, dependency := range dependencies { + if !started[dependency] { + continue startOne + } + } + started[outboundTag] = true + canContinue = true + if starter, isStarter := outboundToStart.(interface { + Start() error + }); isStarter { + monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + err := starter.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + } + } + } + if len(started) == len(m.outbounds) { + break + } + if canContinue { + continue + } + currentOutbound := common.Find(m.outbounds, func(it adapter.Outbound) bool { + return !started[it.Tag()] + }) + var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error + lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error { + problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool { + return !started[it] + }) + if common.Contains(oTree, problemOutboundTag) { + return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) + } + problemOutbound := m.outboundByTag[problemOutboundTag] + if problemOutbound == nil { + return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]") + } + return lintOutbound(append(oTree, problemOutboundTag), problemOutbound) + } + return lintOutbound([]string{currentOutbound.Tag()}, currentOutbound) + } + return nil +} + +func (m *Manager) Close() error { + monitor := taskmonitor.New(m.logger, C.StopTimeout) + m.access.Lock() + if !m.started { + panic("not started") + } + m.started = false + outbounds := m.outbounds + m.outbounds = nil + m.access.Unlock() + var err error + for _, outbound := range outbounds { + if closer, isCloser := outbound.(io.Closer); isCloser { + monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]") + err = E.Append(err, closer.Close(), func(err error) error { + return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]") + }) + monitor.Finish() + } + } + return nil +} + +func (m *Manager) Outbounds() []adapter.Outbound { + m.access.Lock() + defer m.access.Unlock() + return m.outbounds +} + +func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) { + m.access.Lock() + defer m.access.Unlock() + outbound, found := m.outboundByTag[tag] + return outbound, found +} + +func (m *Manager) Default() adapter.Outbound { + m.access.Lock() + defer m.access.Unlock() + if m.defaultOutbound != nil { + return m.defaultOutbound + } else { + return m.defaultOutboundFallback + } +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + outbound, found := m.outboundByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.outboundByTag, tag) + index := common.Index(m.outbounds, func(it adapter.Outbound) bool { + return it == outbound + }) + if index == -1 { + panic("invalid inbound index") + } + m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...) + started := m.started + if m.defaultOutbound == outbound { + if len(m.outbounds) > 0 { + m.defaultOutbound = m.outbounds[0] + m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag()) + } else { + m.defaultOutbound = nil + } + } + dependBy := m.dependByTag[tag] + if len(dependBy) > 0 { + return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", ")) + } + dependencies := outbound.Dependencies() + for _, dependency := range dependencies { + if len(m.dependByTag[dependency]) == 1 { + delete(m.dependByTag, dependency) + } else { + m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool { + return it != tag + }) + } + } + m.access.Unlock() + if started { + return common.Close(outbound) + } + return nil +} + +func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error { + if tag == "" { + return os.ErrInvalid + } + outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + for _, stage := range adapter.ListStartStages { + err = adapter.LegacyStart(outbound, stage) + if err != nil { + return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + } + } + } + if existsOutbound, loaded := m.outboundByTag[tag]; loaded { + if m.started { + err = common.Close(existsOutbound) + if err != nil { + return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]") + } + } + existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool { + return it == existsOutbound + }) + if existsIndex == -1 { + panic("invalid inbound index") + } + m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...) + } + m.outbounds = append(m.outbounds, outbound) + m.outboundByTag[tag] = outbound + dependencies := outbound.Dependencies() + for _, dependency := range dependencies { + m.dependByTag[dependency] = append(m.dependByTag[dependency], tag) + } + if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) { + m.defaultOutbound = outbound + if m.started { + m.logger.Info("updated default outbound to ", outbound.Tag()) + } + } + return nil +} diff --git a/adapter/outbound/registry.go b/adapter/outbound/registry.go index f25631cf14..8743ba1030 100644 --- a/adapter/outbound/registry.go +++ b/adapter/outbound/registry.go @@ -15,8 +15,12 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { registry.register(outboundType, func() any { return new(Options) - }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) { - return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options))) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options)) }) } diff --git a/adapter/prestart.go b/adapter/prestart.go index 6a39aec3ff..b8e8da3083 100644 --- a/adapter/prestart.go +++ b/adapter/prestart.go @@ -1,9 +1 @@ package adapter - -type PreStarter interface { - PreStart() error -} - -type PostStarter interface { - PostStart() error -} diff --git a/adapter/router.go b/adapter/router.go index c9cd46e91a..b8ac51f573 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -15,21 +15,13 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" - "github.com/sagernet/sing/service" mdns "github.com/miekg/dns" "go4.org/netipx" ) type Router interface { - Service - PreStarter - PostStarter - Cleanup() error - - Outbounds() []Outbound - Outbound(tag string) (Outbound, bool) - DefaultOutbound(network string) (Outbound, error) + NewService FakeIPStore() FakeIPStore @@ -84,14 +76,6 @@ type ConnectionRouterEx interface { RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) } -func ContextWithRouter(ctx context.Context, router Router) context.Context { - return service.ContextWith(ctx, router) -} - -func RouterFromContext(ctx context.Context) Router { - return service.FromContext[Router](ctx) -} - type RuleSet interface { Name() string StartContext(ctx context.Context, startContext *HTTPStartContext) error diff --git a/box.go b/box.go index bc714e5a32..d44e8ed058 100644 --- a/box.go +++ b/box.go @@ -9,6 +9,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental" @@ -30,8 +32,8 @@ var _ adapter.Service = (*Box)(nil) type Box struct { createdAt time.Time router adapter.Router - inbounds []adapter.Inbound - outbounds []adapter.Outbound + inbound *inbound.Manager + outbound *outbound.Manager logFactory log.Factory logger log.ContextLogger preServices1 map[string]adapter.Service @@ -66,6 +68,7 @@ func New(options Options) (*Box, error) { if ctx == nil { ctx = context.Background() } + ctx = service.ContextWithDefaultRegistry(ctx) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) if inboundRegistry == nil { return nil, E.New("missing inbound registry in context") @@ -74,7 +77,6 @@ func New(options Options) (*Box, error) { if outboundRegistry == nil { return nil, E.New("missing outbound registry in context") } - ctx = service.ContextWithDefaultRegistry(ctx) ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) @@ -106,10 +108,15 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "create log factory") } + routeOptions := common.PtrValueOrDefault(options.Route) + inboundManager := inbound.NewManager(logFactory.NewLogger("inbound-manager"), inboundRegistry) + outboundManager := outbound.NewManager(logFactory.NewLogger("outbound-manager"), outboundRegistry, routeOptions.Final) + ctx = service.ContextWith[adapter.InboundManager](ctx, inboundManager) + ctx = service.ContextWith[adapter.OutboundManager](ctx, outboundManager) router, err := route.NewRouter( ctx, logFactory, - common.PtrValueOrDefault(options.Route), + routeOptions, common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP), options.Inbounds, @@ -118,15 +125,13 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "parse route options") } for i, inboundOptions := range options.Inbounds { - var currentInbound adapter.Inbound var tag string if inboundOptions.Tag != "" { tag = inboundOptions.Tag } else { tag = F.ToString(i) } - currentInbound, err = inboundRegistry.CreateInbound( - ctx, + err = inboundManager.Create(ctx, router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), tag, @@ -134,12 +139,10 @@ func New(options Options) (*Box, error) { inboundOptions.Options, ) if err != nil { - return nil, E.Cause(err, "parse inbound[", i, "]") + return nil, E.Cause(err, "initialize inbound[", i, "]") } - inbounds = append(inbounds, currentInbound) } for i, outboundOptions := range options.Outbounds { - var currentOutbound adapter.Outbound var tag string if outboundOptions.Tag != "" { tag = outboundOptions.Tag @@ -153,7 +156,7 @@ func New(options Options) (*Box, error) { Outbound: tag, }) } - currentOutbound, err = outboundRegistry.CreateOutbound( + err = outboundManager.Create( outboundCtx, router, logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), @@ -162,16 +165,18 @@ func New(options Options) (*Box, error) { outboundOptions.Options, ) if err != nil { - return nil, E.Cause(err, "parse outbound[", i, "]") + return nil, E.Cause(err, "initialize outbound[", i, "]") } - outbounds = append(outbounds, currentOutbound) } - err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { - defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{}) - common.Must(cErr) - outbounds = append(outbounds, defaultOutbound) - return defaultOutbound - }) + outboundManager.Initialize(common.Must1( + direct.NewOutbound( + ctx, + router, + logFactory.NewLogger("outbound/direct"), + "direct", + option.DirectOutboundOptions{}, + ), + )) if err != nil { return nil, err } @@ -195,7 +200,7 @@ func New(options Options) (*Box, error) { if needClashAPI { clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) - clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions) + clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions) if err != nil { return nil, E.Cause(err, "create clash api server") } @@ -212,8 +217,8 @@ func New(options Options) (*Box, error) { } return &Box{ router: router, - inbounds: inbounds, - outbounds: outbounds, + inbound: inboundManager, + outbound: outboundManager, createdAt: createdAt, logFactory: logFactory, logger: logFactory.Logger(), @@ -271,7 +276,7 @@ func (s *Box) preStart() error { return E.Cause(err, "start logger") } for serviceName, service := range s.preServices1 { - if preService, isPreService := service.(adapter.PreStarter); isPreService { + if preService, isPreService := service.(adapter.LegacyPreStarter); isPreService { monitor.Start("pre-start ", serviceName) err := preService.PreStart() monitor.Finish() @@ -281,7 +286,7 @@ func (s *Box) preStart() error { } } for serviceName, service := range s.preServices2 { - if preService, isPreService := service.(adapter.PreStarter); isPreService { + if preService, isPreService := service.(adapter.LegacyPreStarter); isPreService { monitor.Start("pre-start ", serviceName) err := preService.PreStart() monitor.Finish() @@ -290,15 +295,15 @@ func (s *Box) preStart() error { } } } - err = s.router.PreStart() + err = s.router.Start(adapter.StartStateInitialize) if err != nil { - return E.Cause(err, "pre-start router") + return E.Cause(err, "initialize router") } - err = s.startOutbounds() + err = s.outbound.Start(adapter.StartStateStart) if err != nil { return err } - return s.router.Start() + return s.router.Start(adapter.StartStateStart) } func (s *Box) start() error { @@ -318,52 +323,39 @@ func (s *Box) start() error { return E.Cause(err, "start ", serviceName) } } - for i, in := range s.inbounds { - var tag string - if in.Tag() == "" { - tag = F.ToString(i) - } else { - tag = in.Tag() - } - err = in.Start() - if err != nil { - return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") - } - } - err = s.postStart() + err = s.inbound.Start(adapter.StartStateStart) if err != nil { return err } - return s.router.Cleanup() -} - -func (s *Box) postStart() error { for serviceName, service := range s.postServices { err := service.Start() if err != nil { return E.Cause(err, "start ", serviceName) } } - // TODO: reorganize ALL start order - for _, out := range s.outbounds { - if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound { - err := lateOutbound.PostStart() - if err != nil { - return E.Cause(err, "post-start outbound/", out.Tag()) - } - } + err = s.outbound.Start(adapter.StartStatePostStart) + if err != nil { + return err } - err := s.router.PostStart() + err = s.router.Start(adapter.StartStatePostStart) if err != nil { return err } - for _, in := range s.inbounds { - if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound { - err = lateInbound.PostStart() - if err != nil { - return E.Cause(err, "post-start inbound/", in.Tag()) - } - } + err = s.inbound.Start(adapter.StartStatePostStart) + if err != nil { + return err + } + err = s.router.Start(adapter.StartStateStarted) + if err != nil { + return err + } + err = s.outbound.Start(adapter.StartStateStarted) + if err != nil { + return err + } + err = s.inbound.Start(adapter.StartStateStarted) + if err != nil { + return err } return nil } @@ -384,20 +376,8 @@ func (s *Box) Close() error { }) monitor.Finish() } - for i, in := range s.inbounds { - monitor.Start("close inbound/", in.Type(), "[", i, "]") - errors = E.Append(errors, in.Close(), func(err error) error { - return E.Cause(err, "close inbound/", in.Type(), "[", i, "]") - }) - monitor.Finish() - } - for i, out := range s.outbounds { - monitor.Start("close outbound/", out.Type(), "[", i, "]") - errors = E.Append(errors, common.Close(out), func(err error) error { - return E.Cause(err, "close outbound/", out.Type(), "[", i, "]") - }) - monitor.Finish() - } + errors = E.Errors(errors, s.inbound.Close()) + errors = E.Errors(errors, s.outbound.Close()) monitor.Start("close router") if err := common.Close(s.router); err != nil { errors = E.Append(errors, err, func(err error) error { @@ -427,6 +407,14 @@ func (s *Box) Close() error { return errors } +func (s *Box) Inbound() adapter.InboundManager { + return s.inbound +} + +func (s *Box) Outbound() adapter.OutboundManager { + return s.outbound +} + func (s *Box) Router() adapter.Router { return s.router } diff --git a/box_outbound.go b/box_outbound.go deleted file mode 100644 index f03f3b7d41..0000000000 --- a/box_outbound.go +++ /dev/null @@ -1,85 +0,0 @@ -package box - -import ( - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" -) - -func (s *Box) startOutbounds() error { - monitor := taskmonitor.New(s.logger, C.StartTimeout) - outboundTags := make(map[adapter.Outbound]string) - outbounds := make(map[string]adapter.Outbound) - for i, outboundToStart := range s.outbounds { - var outboundTag string - if outboundToStart.Tag() == "" { - outboundTag = F.ToString(i) - } else { - outboundTag = outboundToStart.Tag() - } - if _, exists := outbounds[outboundTag]; exists { - return E.New("outbound tag ", outboundTag, " duplicated") - } - outboundTags[outboundToStart] = outboundTag - outbounds[outboundTag] = outboundToStart - } - started := make(map[string]bool) - for { - canContinue := false - startOne: - for _, outboundToStart := range s.outbounds { - outboundTag := outboundTags[outboundToStart] - if started[outboundTag] { - continue - } - dependencies := outboundToStart.Dependencies() - for _, dependency := range dependencies { - if !started[dependency] { - continue startOne - } - } - started[outboundTag] = true - canContinue = true - if starter, isStarter := outboundToStart.(interface { - Start() error - }); isStarter { - monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") - err := starter.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") - } - } - } - if len(started) == len(s.outbounds) { - break - } - if canContinue { - continue - } - currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool { - return !started[outboundTags[it]] - }) - var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error - lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error { - problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool { - return !started[it] - }) - if common.Contains(oTree, problemOutboundTag) { - return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) - } - problemOutbound := outbounds[problemOutboundTag] - if problemOutbound == nil { - return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]") - } - return lintOutbound(append(oTree, problemOutboundTag), problemOutbound) - } - return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound) - } - return nil -} diff --git a/cmd/sing-box/cmd_tools.go b/cmd/sing-box/cmd_tools.go index f4c8736ec1..55e5b45873 100644 --- a/cmd/sing-box/cmd_tools.go +++ b/cmd/sing-box/cmd_tools.go @@ -41,11 +41,11 @@ func createPreStartedClient() (*box.Box, error) { return instance, nil } -func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) { +func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) { if outboundTag == "" { - return instance.Router().DefaultOutbound(N.NetworkName(network)) + return instance.Outbound().Default(), nil } else { - outbound, loaded := instance.Router().Outbound(outboundTag) + outbound, loaded := instance.Outbound().Outbound(outboundTag) if !loaded { return nil, E.New("outbound not found: ", outboundTag) } diff --git a/cmd/sing-box/cmd_tools_connect.go b/cmd/sing-box/cmd_tools_connect.go index 3ea04bcd40..d352d533e7 100644 --- a/cmd/sing-box/cmd_tools_connect.go +++ b/cmd/sing-box/cmd_tools_connect.go @@ -45,7 +45,7 @@ func connect(address string) error { return err } defer instance.Close() - dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return err } diff --git a/cmd/sing-box/cmd_tools_fetch.go b/cmd/sing-box/cmd_tools_fetch.go index 3f62424a49..5ee3b87510 100644 --- a/cmd/sing-box/cmd_tools_fetch.go +++ b/cmd/sing-box/cmd_tools_fetch.go @@ -48,7 +48,7 @@ func fetch(args []string) error { httpClient = &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - dialer, err := createDialer(instance, network, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return nil, err } diff --git a/cmd/sing-box/cmd_tools_fetch_http3.go b/cmd/sing-box/cmd_tools_fetch_http3.go index 9a7e440272..b7a31a726d 100644 --- a/cmd/sing-box/cmd_tools_fetch_http3.go +++ b/cmd/sing-box/cmd_tools_fetch_http3.go @@ -16,7 +16,7 @@ import ( ) func initializeHTTP3Client(instance *box.Box) error { - dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return err } diff --git a/cmd/sing-box/cmd_tools_synctime.go b/cmd/sing-box/cmd_tools_synctime.go index 20d73a6d67..38510eb8e1 100644 --- a/cmd/sing-box/cmd_tools_synctime.go +++ b/cmd/sing-box/cmd_tools_synctime.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-box/log" 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" "github.com/spf13/cobra" @@ -45,7 +44,7 @@ func syncTime() error { if err != nil { return err } - dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return err } diff --git a/common/dialer/detour.go b/common/dialer/detour.go index 81600913d4..c1d40faa1d 100644 --- a/common/dialer/detour.go +++ b/common/dialer/detour.go @@ -12,15 +12,15 @@ import ( ) type DetourDialer struct { - router adapter.Router - detour string - dialer N.Dialer - initOnce sync.Once - initErr error + outboundManager adapter.OutboundManager + detour string + dialer N.Dialer + initOnce sync.Once + initErr error } -func NewDetour(router adapter.Router, detour string) N.Dialer { - return &DetourDialer{router: router, detour: detour} +func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer { + return &DetourDialer{outboundManager: outboundManager, detour: detour} } func (d *DetourDialer) Start() error { @@ -31,7 +31,7 @@ func (d *DetourDialer) Start() error { func (d *DetourDialer) Dialer() (N.Dialer, error) { d.initOnce.Do(func() { var loaded bool - d.dialer, loaded = d.router.Outbound(d.detour) + d.dialer, loaded = d.outboundManager.Outbound(d.detour) if !loaded { d.initErr = E.New("outbound detour not found: ", d.detour) } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 56c5f2ad15..fe4c7c1204 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -1,21 +1,22 @@ package dialer import ( + "context" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" + E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) -func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) { +func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { + router := service.FromContext[adapter.Router](ctx) if options.IsWireGuardListener { return NewDefault(router, options) } - if router == nil { - return NewDefault(nil, options) - } var ( dialer N.Dialer err error @@ -26,7 +27,14 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) return nil, err } } else { - dialer = NewDetour(router, options.Detour) + outboundManager := service.FromContext[adapter.OutboundManager](ctx) + if outboundManager == nil { + return nil, E.New("missing outbound manager") + } + dialer = NewDetour(outboundManager, options.Detour) + } + if router == nil { + return NewDefault(router, options) } if options.Detour == "" { dialer = NewResolveDialer( diff --git a/common/dialer/router.go b/common/dialer/router.go index 2531607753..3edce65b09 100644 --- a/common/dialer/router.go +++ b/common/dialer/router.go @@ -9,30 +9,22 @@ import ( N "github.com/sagernet/sing/common/network" ) -type RouterDialer struct { - router adapter.Router +type DefaultOutboundDialer struct { + outboundManager adapter.OutboundManager } -func NewRouter(router adapter.Router) N.Dialer { - return &RouterDialer{router: router} +func NewDefaultOutbound(outboundManager adapter.OutboundManager) N.Dialer { + return &DefaultOutboundDialer{outboundManager: outboundManager} } -func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - dialer, err := d.router.DefaultOutbound(network) - if err != nil { - return nil, err - } - return dialer.DialContext(ctx, network, destination) +func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return d.outboundManager.Default().DialContext(ctx, network, destination) } -func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - dialer, err := d.router.DefaultOutbound(N.NetworkUDP) - if err != nil { - return nil, err - } - return dialer.ListenPacket(ctx, destination) +func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return d.outboundManager.Default().ListenPacket(ctx, destination) } -func (d *RouterDialer) Upstream() any { - return d.router +func (d *DefaultOutboundDialer) Upstream() any { + return d.outboundManager.Default() } diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index f03658a858..2d86fa2d9a 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -12,6 +12,7 @@ import ( M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/shell" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" ) type DarwinSystemProxy struct { @@ -24,7 +25,7 @@ type DarwinSystemProxy struct { } func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) { - interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor() + interfaceMonitor := service.FromContext[adapter.Router](ctx).InterfaceMonitor() if interfaceMonitor == nil { return nil, E.New("missing interface monitor") } diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go index 19022d410c..0ae3997aba 100644 --- a/common/tls/ech_client.go +++ b/common/tls/ech_client.go @@ -19,6 +19,7 @@ import ( "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" + "github.com/sagernet/sing/service" mDNS "github.com/miekg/dns" ) @@ -214,7 +215,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam }, }, } - response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message) + response, err := service.FromContext[adapter.Router](ctx).Exchange(ctx, message) if err != nil { return nil, err } diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index 995b454d86..cf429815ee 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -11,7 +11,6 @@ import ( "time" "github.com/sagernet/reality" - "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -102,7 +101,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb tlsConfig.ShortIds[shortID] = true } - handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions) if err != nil { return nil, err } diff --git a/experimental/clashapi.go b/experimental/clashapi.go index 872d9b9956..4ad07c8b88 100644 --- a/experimental/clashapi.go +++ b/experimental/clashapi.go @@ -12,7 +12,7 @@ import ( "github.com/sagernet/sing/common" ) -type ClashServerConstructor = func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) +type ClashServerConstructor = func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) var clashServerConstructor ClashServerConstructor @@ -20,11 +20,11 @@ func RegisterClashServerConstructor(constructor ClashServerConstructor) { clashServerConstructor = constructor } -func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewClashServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { if clashServerConstructor == nil { return nil, os.ErrInvalid } - return clashServerConstructor(ctx, router, logFactory, options) + return clashServerConstructor(ctx, logFactory, options) } func CalculateClashModeList(options option.Options) []string { diff --git a/experimental/clashapi/api_meta_group.go b/experimental/clashapi/api_meta_group.go index 531311f4fe..c5c07ba6a3 100644 --- a/experimental/clashapi/api_meta_group.go +++ b/experimental/clashapi/api_meta_group.go @@ -23,7 +23,7 @@ func groupRouter(server *Server) http.Handler { r := chi.NewRouter() r.Get("/", getGroups(server)) r.Route("/{name}", func(r chi.Router) { - r.Use(parseProxyName, findProxyByName(server.router)) + r.Use(parseProxyName, findProxyByName(server)) r.Get("/", getGroup(server)) r.Get("/delay", getGroupDelay(server)) }) @@ -32,7 +32,7 @@ func groupRouter(server *Server) http.Handler { func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - groups := common.Map(common.Filter(server.router.Outbounds(), func(it adapter.Outbound) bool { + groups := common.Map(common.Filter(server.outboundManager.Outbounds(), func(it adapter.Outbound) bool { _, isGroup := it.(adapter.OutboundGroup) return isGroup }), func(it adapter.Outbound) *badjson.JSONObject { @@ -86,7 +86,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) result, err = urlTestGroup.URLTest(ctx) } else { outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { - itOutbound, _ := server.router.Outbound(it) + itOutbound, _ := server.outboundManager.Outbound(it) return itOutbound })) b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10)) @@ -100,7 +100,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) continue } checked[realTag] = true - p, loaded := server.router.Outbound(realTag) + p, loaded := server.outboundManager.Outbound(realTag) if !loaded { continue } diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index 4a9564ee86..8d8ecb3845 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -23,10 +23,10 @@ import ( func proxyRouter(server *Server, router adapter.Router) http.Handler { r := chi.NewRouter() - r.Get("/", getProxies(server, router)) + r.Get("/", getProxies(server)) r.Route("/{name}", func(r chi.Router) { - r.Use(parseProxyName, findProxyByName(router)) + r.Use(parseProxyName, findProxyByName(server)) r.Get("/", getProxy(server)) r.Get("/delay", getProxyDelay(server)) r.Put("/", updateProxy) @@ -42,11 +42,11 @@ func parseProxyName(next http.Handler) http.Handler { }) } -func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler { +func findProxyByName(server *Server) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { name := r.Context().Value(CtxKeyProxyName).(string) - proxy, exist := router.Outbound(name) + proxy, exist := server.outboundManager.Outbound(name) if !exist { render.Status(r, http.StatusNotFound) render.JSON(w, r, ErrNotFound) @@ -83,10 +83,10 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject { return &info } -func getProxies(server *Server, router adapter.Router) func(w http.ResponseWriter, r *http.Request) { +func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var proxyMap badjson.JSONObject - outbounds := common.Filter(router.Outbounds(), func(detour adapter.Outbound) bool { + outbounds := common.Filter(server.outboundManager.Outbounds(), func(detour adapter.Outbound) bool { return detour.Tag() != "" }) @@ -100,12 +100,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite allProxies = append(allProxies, detour.Tag()) } - var defaultTag string - if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { - defaultTag = defaultOutbound.Tag() - } else { - defaultTag = allProxies[0] - } + defaultTag := server.outboundManager.Default().Tag() sort.SliceStable(allProxies, func(i, j int) bool { return allProxies[i] == defaultTag diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 462f76b57d..89f33de348 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -40,15 +40,16 @@ func init() { var _ adapter.ClashServer = (*Server)(nil) type Server struct { - ctx context.Context - router adapter.Router - logger log.Logger - httpServer *http.Server - trafficManager *trafficontrol.Manager - urlTestHistory *urltest.HistoryStorage - mode string - modeList []string - modeUpdateHook chan<- struct{} + ctx context.Context + router adapter.Router + outboundManager adapter.OutboundManager + logger log.Logger + httpServer *http.Server + trafficManager *trafficontrol.Manager + urlTestHistory *urltest.HistoryStorage + mode string + modeList []string + modeUpdateHook chan<- struct{} externalController bool externalUI string @@ -56,13 +57,14 @@ type Server struct { externalUIDownloadDetour string } -func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { trafficManager := trafficontrol.NewManager() chiRouter := chi.NewRouter() - server := &Server{ - ctx: ctx, - router: router, - logger: logFactory.NewLogger("clash-api"), + s := &Server{ + ctx: ctx, + router: service.FromContext[adapter.Router](ctx), + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + logger: logFactory.NewLogger("clash-api"), httpServer: &http.Server{ Addr: options.ExternalController, Handler: chiRouter, @@ -73,18 +75,18 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ externalUIDownloadURL: options.ExternalUIDownloadURL, externalUIDownloadDetour: options.ExternalUIDownloadDetour, } - server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx) - if server.urlTestHistory == nil { - server.urlTestHistory = urltest.NewHistoryStorage() + s.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx) + if s.urlTestHistory == nil { + s.urlTestHistory = urltest.NewHistoryStorage() } defaultMode := "Rule" if options.DefaultMode != "" { defaultMode = options.DefaultMode } - if !common.Contains(server.modeList, defaultMode) { - server.modeList = append([]string{defaultMode}, server.modeList...) + if !common.Contains(s.modeList, defaultMode) { + s.modeList = append([]string{defaultMode}, s.modeList...) } - server.mode = defaultMode + s.mode = defaultMode //goland:noinspection GoDeprecation //nolint:staticcheck if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" { @@ -108,27 +110,27 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ r.Get("/logs", getLogs(logFactory)) r.Get("/traffic", traffic(trafficManager)) r.Get("/version", version) - r.Mount("/configs", configRouter(server, logFactory)) - r.Mount("/proxies", proxyRouter(server, router)) - r.Mount("/rules", ruleRouter(router)) - r.Mount("/connections", connectionRouter(router, trafficManager)) + 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("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/script", scriptRouter()) r.Mount("/profile", profileRouter()) r.Mount("/cache", cacheRouter(ctx)) - r.Mount("/dns", dnsRouter(router)) + r.Mount("/dns", dnsRouter(s.router)) - server.setupMetaAPI(r) + s.setupMetaAPI(r) }) if options.ExternalUI != "" { - server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) + s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) chiRouter.Group(func(r chi.Router) { r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP) - r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(server.externalUI)))) + r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.externalUI)))) }) } - return server, nil + return s, nil } func (s *Server) PreStart() error { @@ -232,12 +234,12 @@ func (s *Server) TrafficManager() *trafficontrol.Manager { } func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) { - tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule) + tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule) return tracker, tracker } func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) { - tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.router, matchedRule) + tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule) return tracker, tracker } diff --git a/experimental/clashapi/server_resources.go b/experimental/clashapi/server_resources.go index a5c79e0cf2..e5b28e300c 100644 --- a/experimental/clashapi/server_resources.go +++ b/experimental/clashapi/server_resources.go @@ -15,7 +15,6 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service/filemanager" ) @@ -45,16 +44,13 @@ func (s *Server) downloadExternalUI() error { s.logger.Info("downloading external ui") var detour adapter.Outbound if s.externalUIDownloadDetour != "" { - outbound, loaded := s.router.Outbound(s.externalUIDownloadDetour) + outbound, loaded := s.outboundManager.Outbound(s.externalUIDownloadDetour) if !loaded { return E.New("detour outbound not found: ", s.externalUIDownloadDetour) } detour = outbound } else { - outbound, err := s.router.DefaultOutbound(N.NetworkTCP) - if err != nil { - return err - } + outbound := s.outboundManager.Default() detour = outbound } httpClient := &http.Client{ diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index 9c18abebf6..df5437fafa 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -124,7 +124,7 @@ func (tt *TCPConn) WriterReplaceable() bool { return true } -func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *TCPConn { +func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, rule adapter.Rule) *TCPConn { id, _ := uuid.NewV4() var ( chain []string @@ -138,11 +138,11 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont } if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { next = routeAction.Outbound - } else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { - next = defaultOutbound.Tag() + } else { + next = outboundManager.Default().Tag() } for { - detour, loaded := router.Outbound(next) + detour, loaded := outboundManager.Outbound(next) if !loaded { break } @@ -213,7 +213,7 @@ func (ut *UDPConn) WriterReplaceable() bool { return true } -func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *UDPConn { +func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, rule adapter.Rule) *UDPConn { id, _ := uuid.NewV4() var ( chain []string @@ -227,11 +227,11 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound } if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { next = routeAction.Outbound - } else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil { - next = defaultOutbound.Tag() + } else { + next = outboundManager.Default().Tag() } for { - detour, loaded := router.Outbound(next) + detour, loaded := outboundManager.Outbound(next) if !loaded { break } diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go index 568a72cbd0..8415e21fd8 100644 --- a/protocol/direct/inbound.go +++ b/protocol/direct/inbound.go @@ -76,21 +76,6 @@ func (i *Inbound) Close() error { return i.listener.Close() } -func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - switch i.overrideOption { - case 1: - metadata.Destination = i.overrideDestination - case 2: - destination := i.overrideDestination - destination.Port = metadata.Destination.Port - metadata.Destination = destination - case 3: - metadata.Destination.Port = i.overrideDestination.Port - } - i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return i.router.RouteConnection(ctx, conn, metadata) -} - func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { var destination M.Socksaddr switch i.overrideOption { @@ -107,9 +92,21 @@ func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { } func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + switch i.overrideOption { + case 1: + metadata.Destination = i.overrideDestination + case 2: + destination := i.overrideDestination + destination.Port = metadata.Destination.Port + metadata.Destination = destination + case 3: + metadata.Destination.Port = i.overrideDestination.Port + } i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) metadata.Inbound = i.Tag() metadata.InboundType = i.Type() + metadata.InboundDetour = i.listener.ListenOptions().Detour + metadata.InboundOptions = i.listener.ListenOptions().InboundOptions i.router.RouteConnectionEx(ctx, conn, metadata, onClose) } @@ -119,6 +116,8 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, var metadata adapter.InboundContext metadata.Inbound = i.Tag() metadata.InboundType = i.Type() + metadata.InboundDetour = i.listener.ListenOptions().Detour + metadata.InboundOptions = i.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination metadata.OriginDestination = i.listener.UDPAddr() diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 32c1ed8fda..27b334c9a4 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -12,7 +12,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-dns" + dns "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -39,7 +39,7 @@ type Outbound struct { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/group/selector.go b/protocol/group/selector.go index 8ade27a943..08db74c09e 100644 --- a/protocol/group/selector.go +++ b/protocol/group/selector.go @@ -26,7 +26,7 @@ var _ adapter.OutboundGroup = (*Selector)(nil) type Selector struct { outbound.Adapter ctx context.Context - router adapter.Router + outboundManager adapter.OutboundManager logger logger.ContextLogger tags []string defaultTag string @@ -40,7 +40,7 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL outbound := &Selector{ Adapter: outbound.NewAdapter(C.TypeSelector, nil, tag, options.Outbounds), ctx: ctx, - router: router, + outboundManager: service.FromContext[adapter.OutboundManager](ctx), logger: logger, tags: options.Outbounds, defaultTag: options.Default, @@ -63,7 +63,7 @@ func (s *Selector) Network() []string { func (s *Selector) Start() error { for i, tag := range s.tags { - detour, loaded := s.router.Outbound(tag) + detour, loaded := s.outboundManager.Outbound(tag) if !loaded { return E.New("outbound ", i, " not found: ", tag) } diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index ccdf809d9b..4d76a31c6a 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -36,6 +36,7 @@ type URLTest struct { outbound.Adapter ctx context.Context router adapter.Router + outboundManager adapter.OutboundManager logger log.ContextLogger tags []string link string @@ -51,6 +52,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo Adapter: outbound.NewAdapter(C.TypeURLTest, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.Outbounds), ctx: ctx, router: router, + outboundManager: service.FromContext[adapter.OutboundManager](ctx), logger: logger, tags: options.Outbounds, link: options.URL, @@ -68,7 +70,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo func (s *URLTest) Start() error { outbounds := make([]adapter.Outbound, 0, len(s.tags)) for i, tag := range s.tags { - detour, loaded := s.router.Outbound(tag) + detour, loaded := s.outboundManager.Outbound(tag) if !loaded { return E.New("outbound ", i, " not found: ", tag) } @@ -77,6 +79,7 @@ func (s *URLTest) Start() error { group, err := NewURLTestGroup( s.ctx, s.router, + s.outboundManager, s.logger, outbounds, s.link, @@ -190,6 +193,7 @@ func (s *URLTest) InterfaceUpdated() { type URLTestGroup struct { ctx context.Context router adapter.Router + outboundManager adapter.OutboundManager logger log.Logger outbounds []adapter.Outbound link string @@ -214,6 +218,7 @@ type URLTestGroup struct { func NewURLTestGroup( ctx context.Context, router adapter.Router, + outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, @@ -244,6 +249,7 @@ func NewURLTestGroup( return &URLTestGroup{ ctx: ctx, router: router, + outboundManager: outboundManager, logger: logger, outbounds: outbounds, link: link, @@ -385,7 +391,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint continue } checked[realTag] = true - p, loaded := g.router.Outbound(realTag) + p, loaded := g.outboundManager.Outbound(realTag) if !loaded { continue } diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go index 87ed9a1089..bb8fe8c7dd 100644 --- a/protocol/http/inbound.go +++ b/protocol/http/inbound.go @@ -73,7 +73,7 @@ func (h *Inbound) Start() error { func (h *Inbound) Close() error { return common.Close( - &h.listener, + h.listener, h.tlsConfig, ) } @@ -82,7 +82,11 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a err := h.newConnection(ctx, conn, metadata, onClose) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } } @@ -98,6 +102,10 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada } func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) @@ -110,6 +118,10 @@ func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata } func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) diff --git a/protocol/http/outbound.go b/protocol/http/outbound.go index 4c930591b1..81fd024669 100644 --- a/protocol/http/outbound.go +++ b/protocol/http/outbound.go @@ -30,7 +30,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/hysteria/inbound.go b/protocol/hysteria/inbound.go index f1f6da3ef6..00defa92ab 100644 --- a/protocol/hysteria/inbound.go +++ b/protocol/hysteria/inbound.go @@ -17,6 +17,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -85,7 +86,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo XPlusPassword: options.Obfs, TLSConfig: tlsConfig, UDPTimeout: udpTimeout, - Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil), + Handler: inbound, // Legacy options @@ -117,12 +118,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -131,16 +136,19 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada } else { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) } - return h.router.RouteConnection(ctx, conn, metadata) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -149,7 +157,7 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me } else { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) } - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } func (h *Inbound) Start() error { @@ -168,7 +176,7 @@ func (h *Inbound) Start() error { func (h *Inbound) Close() error { return common.Close( - &h.listener, + h.listener, h.tlsConfig, common.PtrOrNil(h.service), ) diff --git a/protocol/hysteria/outbound.go b/protocol/hysteria/outbound.go index 6df32eaf71..89e6cc1089 100644 --- a/protocol/hysteria/outbound.go +++ b/protocol/hysteria/outbound.go @@ -47,7 +47,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/hysteria2/inbound.go b/protocol/hysteria2/inbound.go index cbf811097f..03cd8d2d52 100644 --- a/protocol/hysteria2/inbound.go +++ b/protocol/hysteria2/inbound.go @@ -20,6 +20,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -108,7 +109,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo TLSConfig: tlsConfig, IgnoreClientBandwidth: options.IgnoreClientBandwidth, UDPTimeout: udpTimeout, - Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil), + Handler: inbound, MasqueradeHandler: masqueradeHandler, }) if err != nil { @@ -128,12 +129,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -142,16 +147,19 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada } else { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) } - return h.router.RouteConnection(ctx, conn, metadata) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -160,7 +168,7 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me } else { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) } - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } func (h *Inbound) Start() error { @@ -179,7 +187,7 @@ func (h *Inbound) Start() error { func (h *Inbound) Close() error { return common.Close( - &h.listener, + h.listener, h.tlsConfig, common.PtrOrNil(h.service), ) diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go index 5ebc6c91f3..4cabb4751c 100644 --- a/protocol/hysteria2/outbound.go +++ b/protocol/hysteria2/outbound.go @@ -59,7 +59,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, E.New("unknown obfs type: ", options.Obfs.Type) } } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go index e57b791fec..4f48144086 100644 --- a/protocol/mixed/inbound.go +++ b/protocol/mixed/inbound.go @@ -66,7 +66,11 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a err := h.newConnection(ctx, conn, metadata, onClose) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } } @@ -85,6 +89,10 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada } func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) @@ -97,6 +105,10 @@ func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata } func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) diff --git a/protocol/redirect/redirect.go b/protocol/redirect/redirect.go index 71e1fced1a..23bfad3eb4 100644 --- a/protocol/redirect/redirect.go +++ b/protocol/redirect/redirect.go @@ -59,6 +59,8 @@ func (h *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata } metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.Destination = M.SocksaddrFromNetIP(destination) h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) h.router.RouteConnectionEx(ctx, conn, metadata, onClose) diff --git a/protocol/redirect/tproxy.go b/protocol/redirect/tproxy.go index dee40ec5a8..825b9f0085 100644 --- a/protocol/redirect/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -90,6 +90,8 @@ func (t *TProxy) Close() error { } func (t *TProxy) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) t.router.RouteConnectionEx(ctx, conn, metadata, onClose) @@ -101,6 +103,8 @@ func (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, s var metadata adapter.InboundContext metadata.Inbound = t.Tag() metadata.InboundType = t.Type() + metadata.InboundDetour = t.listener.ListenOptions().Detour + metadata.InboundOptions = t.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination metadata.OriginDestination = t.listener.UDPAddr() diff --git a/protocol/shadowsocks/inbound.go b/protocol/shadowsocks/inbound.go index b23516d995..84ad43fcb0 100644 --- a/protocol/shadowsocks/inbound.go +++ b/protocol/shadowsocks/inbound.go @@ -105,7 +105,11 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } } @@ -120,6 +124,8 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RouteConnection(ctx, conn, metadata) } diff --git a/protocol/shadowsocks/inbound_multi.go b/protocol/shadowsocks/inbound_multi.go index 0e1efedfc2..a76075efe0 100644 --- a/protocol/shadowsocks/inbound_multi.go +++ b/protocol/shadowsocks/inbound_multi.go @@ -113,7 +113,11 @@ func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metad err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } } @@ -138,6 +142,8 @@ func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadat h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RouteConnection(ctx, conn, metadata) } diff --git a/protocol/shadowsocks/inbound_relay.go b/protocol/shadowsocks/inbound_relay.go index 5818ca29f1..f7ec2b7703 100644 --- a/protocol/shadowsocks/inbound_relay.go +++ b/protocol/shadowsocks/inbound_relay.go @@ -98,7 +98,11 @@ func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metad err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } } @@ -123,6 +127,8 @@ func (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadat h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RouteConnection(ctx, conn, metadata) } diff --git a/protocol/shadowsocks/outbound.go b/protocol/shadowsocks/outbound.go index 73b3838503..8771fa8e99 100644 --- a/protocol/shadowsocks/outbound.go +++ b/protocol/shadowsocks/outbound.go @@ -44,7 +44,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index 6887e838a5..1be422decc 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -16,6 +16,7 @@ import ( "github.com/sagernet/sing/common/auth" 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" ) @@ -46,7 +47,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if options.Version > 1 { handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) for serverName, serverOptions := range options.HandshakeForServerName { - handshakeDialer, err := dialer.New(router, serverOptions.DialerOptions) + handshakeDialer, err := dialer.New(ctx, serverOptions.DialerOptions) if err != nil { return nil, err } @@ -56,7 +57,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } } } - handshakeDialer, err := dialer.New(router, options.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions) if err != nil { return nil, err } @@ -72,7 +73,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo }, HandshakeForServerName: handshakeForServerName, StrictMode: options.StrictMode, - Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, nil), + Handler: (*inboundHandler)(inbound), Logger: logger, }) if err != nil { @@ -97,24 +98,33 @@ func (h *Inbound) Close() error { return h.listener.Close() } -func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, metadata.Source, metadata.Destination, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } + } } -func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +type inboundHandler Inbound + +func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination if userName, _ := auth.UserFromContext[string](ctx); userName != "" { metadata.User = userName h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) } else { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) } - return h.router.RouteConnection(ctx, conn, metadata) -} - -func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.NewConnection(ctx, conn, metadata) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } diff --git a/protocol/shadowtls/outbound.go b/protocol/shadowtls/outbound.go index 7d46a8f682..e979dba270 100644 --- a/protocol/shadowtls/outbound.go +++ b/protocol/shadowtls/outbound.go @@ -68,7 +68,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig) } } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go index 29649a88df..fddacb21fb 100644 --- a/protocol/socks/inbound.go +++ b/protocol/socks/inbound.go @@ -62,11 +62,19 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } } func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) @@ -79,6 +87,10 @@ func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata } func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) diff --git a/protocol/socks/outbound.go b/protocol/socks/outbound.go index 0194800a7c..dbb5ab6177 100644 --- a/protocol/socks/outbound.go +++ b/protocol/socks/outbound.go @@ -46,7 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/ssh/outbound.go b/protocol/ssh/outbound.go index 62a2a8d9b5..1dfc1f6d68 100644 --- a/protocol/ssh/outbound.go +++ b/protocol/ssh/outbound.go @@ -49,7 +49,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/tor/outbound.go b/protocol/tor/outbound.go index 89a295b822..3d2170115d 100644 --- a/protocol/tor/outbound.go +++ b/protocol/tor/outbound.go @@ -75,7 +75,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } startConf.TorrcFile = torrcFile } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/trojan/inbound.go b/protocol/trojan/inbound.go index 010ae8baf2..2dc1bb9421 100644 --- a/protocol/trojan/inbound.go +++ b/protocol/trojan/inbound.go @@ -149,7 +149,7 @@ func (h *Inbound) Start() error { func (h *Inbound) Close() error { return common.Close( - &h.listener, + h.listener, h.tlsConfig, h.transport, ) @@ -170,11 +170,19 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } } func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -207,12 +215,20 @@ func (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadat } fallbackAddr = h.fallbackAddr } + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "fallback connection to ", fallbackAddr) metadata.Destination = fallbackAddr return h.router.RouteConnection(ctx, conn, metadata) } func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -233,10 +249,6 @@ type inboundTransportHandler Inbound func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { var metadata adapter.InboundContext - metadata.Inbound = h.Tag() - metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index f64c48c3c5..68b0069043 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -38,7 +38,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/tuic/inbound.go b/protocol/tuic/inbound.go index 33de10d5cb..496079c194 100644 --- a/protocol/tuic/inbound.go +++ b/protocol/tuic/inbound.go @@ -17,6 +17,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/gofrs/uuid/v5" @@ -71,7 +72,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo ZeroRTTHandshake: options.ZeroRTTHandshake, Heartbeat: time.Duration(options.Heartbeat), UDPTimeout: udpTimeout, - Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil), + Handler: inbound, }) if err != nil { return nil, err @@ -99,12 +100,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -113,16 +118,19 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada } else { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) } - return h.router.RouteConnection(ctx, conn, metadata) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -131,7 +139,7 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me } else { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) } - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } func (h *Inbound) Start() error { @@ -150,7 +158,7 @@ func (h *Inbound) Start() error { func (h *Inbound) Close() error { return common.Close( - &h.listener, + h.listener, h.tlsConfig, common.PtrOrNil(h.server), ) diff --git a/protocol/tuic/outbound.go b/protocol/tuic/outbound.go index 691d1658de..177f21fc95 100644 --- a/protocol/tuic/outbound.go +++ b/protocol/tuic/outbound.go @@ -60,7 +60,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL case "quic": tuicUDPStream = true } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/vless/inbound.go b/protocol/vless/inbound.go index 0641549b0a..f5dfeabb6b 100644 --- a/protocol/vless/inbound.go +++ b/protocol/vless/inbound.go @@ -129,7 +129,7 @@ func (h *Inbound) Start() error { func (h *Inbound) Close() error { return common.Close( h.service, - &h.listener, + h.listener, h.tlsConfig, h.transport, ) @@ -150,11 +150,19 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } } func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -170,6 +178,10 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada } func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -196,10 +208,6 @@ type inboundTransportHandler Inbound func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { var metadata adapter.InboundContext - metadata.Inbound = h.Tag() - metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index 1074549e8d..de655230e0 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -41,7 +41,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/vmess/inbound.go b/protocol/vmess/inbound.go index 1c80f376e1..88ad222764 100644 --- a/protocol/vmess/inbound.go +++ b/protocol/vmess/inbound.go @@ -143,7 +143,7 @@ func (h *Inbound) Start() error { func (h *Inbound) Close() error { return common.Close( h.service, - &h.listener, + h.listener, h.tlsConfig, h.transport, ) @@ -164,11 +164,19 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } } } func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -184,6 +192,10 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada } func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -210,10 +222,6 @@ type inboundTransportHandler Inbound func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { var metadata adapter.InboundContext - metadata.Inbound = h.Tag() - metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index 759ea8baa4..1e84639f47 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -41,7 +41,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 901f8782d6..79e8873257 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -78,7 +78,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL options.IsWireGuardListener = true outbound.useStdNetBind = true } - listener, err := dialer.New(router, options.DialerOptions) + listener, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } diff --git a/route/dns.go b/route/dns.go index 98ea8d6b34..b69be5a279 100644 --- a/route/dns.go +++ b/route/dns.go @@ -77,7 +77,6 @@ func exchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, b return err } err = conn.WritePacket(responseBuffer, destination) - responseBuffer.Release() return err } diff --git a/route/router.go b/route/router.go index 82b1f4b50d..12255b4d7b 100644 --- a/route/router.go +++ b/route/router.go @@ -87,7 +87,7 @@ type Router struct { v2rayServer adapter.V2RayServer platformInterface platform.Interface needWIFIState bool - needPackageManager bool + enforcePackageManager bool wifiState adapter.WIFIState started bool } @@ -123,7 +123,7 @@ func NewRouter( pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[platform.Interface](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), - needPackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { + enforcePackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { return true } @@ -191,7 +191,8 @@ func NewRouter( transportTags[i] = tag transportTagMap[tag] = true } - ctx = adapter.ContextWithRouter(ctx, router) + ctx = service.ContextWith[adapter.Router](ctx, router) + outboundManager := service.FromContext[adapter.OutboundManager](ctx) for { lastLen := len(dummyTransportMap) for i, server := range dnsOptions.Servers { @@ -201,9 +202,9 @@ func NewRouter( } var detour N.Dialer if server.Detour == "" { - detour = dialer.NewRouter(router) + detour = dialer.NewDefaultOutbound(outboundManager) } else { - detour = dialer.NewDetour(router, server.Detour) + detour = dialer.NewDetour(outboundManager, server.Detour) } var serverProtocol string switch server.Address { @@ -327,7 +328,7 @@ func NewRouter( } usePlatformDefaultInterfaceMonitor := router.platformInterface != nil && router.platformInterface.UsePlatformDefaultInterfaceMonitor() - needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { + enforceInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { if httpMixedOptions, isHTTPMixed := inbound.Options.(*option.HTTPMixedInboundOptions); isHTTPMixed && httpMixedOptions.SetSystemProxy { return true } @@ -339,7 +340,7 @@ func NewRouter( if !usePlatformDefaultInterfaceMonitor { networkMonitor, err := tun.NewNetworkUpdateMonitor(router.logger) - if !((err != nil && !needInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { + if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { if err != nil { return nil, err } @@ -362,7 +363,7 @@ func NewRouter( } if ntpOptions.Enabled { - ntpDialer, err := dialer.New(router, ntpOptions.DialerOptions) + ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) if err != nil { return nil, E.Cause(err, "create NTP service") } @@ -380,73 +381,6 @@ func NewRouter( return router, nil } -func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outbound, defaultOutbound func() adapter.Outbound) error { - inboundByTag := make(map[string]adapter.Inbound) - for _, inbound := range inbounds { - inboundByTag[inbound.Tag()] = inbound - } - outboundByTag := make(map[string]adapter.Outbound) - for _, detour := range outbounds { - outboundByTag[detour.Tag()] = detour - } - var defaultOutboundForConnection adapter.Outbound - var defaultOutboundForPacketConnection adapter.Outbound - if r.defaultDetour != "" { - detour, loaded := outboundByTag[r.defaultDetour] - if !loaded { - return E.New("default detour not found: ", r.defaultDetour) - } - if common.Contains(detour.Network(), N.NetworkTCP) { - defaultOutboundForConnection = detour - } - if common.Contains(detour.Network(), N.NetworkUDP) { - defaultOutboundForPacketConnection = detour - } - } - if defaultOutboundForConnection == nil { - for _, detour := range outbounds { - if common.Contains(detour.Network(), N.NetworkTCP) { - defaultOutboundForConnection = detour - break - } - } - } - if defaultOutboundForPacketConnection == nil { - for _, detour := range outbounds { - if common.Contains(detour.Network(), N.NetworkUDP) { - defaultOutboundForPacketConnection = detour - break - } - } - } - if defaultOutboundForConnection == nil || defaultOutboundForPacketConnection == nil { - detour := defaultOutbound() - if defaultOutboundForConnection == nil { - defaultOutboundForConnection = detour - } - if defaultOutboundForPacketConnection == nil { - defaultOutboundForPacketConnection = detour - } - outbounds = append(outbounds, detour) - outboundByTag[detour.Tag()] = detour - } - r.inboundByTag = inboundByTag - r.outbounds = outbounds - r.defaultOutboundForConnection = defaultOutboundForConnection - r.defaultOutboundForPacketConnection = defaultOutboundForPacketConnection - r.outboundByTag = outboundByTag - for i, rule := range r.rules { - routeAction, isRoute := rule.Action().(*R.RuleActionRoute) - if !isRoute { - continue - } - if _, loaded := outboundByTag[routeAction.Outbound]; !loaded { - return E.New("outbound not found for rule[", i, "]: ", routeAction.Outbound) - } - } - return nil -} - func (r *Router) Outbounds() []adapter.Outbound { if !r.started { return nil @@ -454,140 +388,240 @@ func (r *Router) Outbounds() []adapter.Outbound { return r.outbounds } -func (r *Router) PreStart() error { +func (r *Router) Start(stage adapter.StartStage) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) - if r.interfaceMonitor != nil { - monitor.Start("initialize interface monitor") - err := r.interfaceMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } - if r.networkMonitor != nil { - monitor.Start("initialize network monitor") - err := r.networkMonitor.Start() - monitor.Finish() - if err != nil { - return err + switch stage { + case adapter.StartStateInitialize: + if r.interfaceMonitor != nil { + monitor.Start("initialize interface monitor") + err := r.interfaceMonitor.Start() + monitor.Finish() + if err != nil { + return err + } } - } - if r.fakeIPStore != nil { - monitor.Start("initialize fakeip store") - err := r.fakeIPStore.Start() - monitor.Finish() - if err != nil { - return err + if r.networkMonitor != nil { + monitor.Start("initialize network monitor") + err := r.networkMonitor.Start() + monitor.Finish() + if err != nil { + return err + } } - } - return nil -} - -func (r *Router) Start() error { - monitor := taskmonitor.New(r.logger, C.StartTimeout) - if r.needGeoIPDatabase { - monitor.Start("initialize geoip database") - err := r.prepareGeoIPDatabase() - monitor.Finish() - if err != nil { - return err + if r.fakeIPStore != nil { + monitor.Start("initialize fakeip store") + err := r.fakeIPStore.Start() + monitor.Finish() + if err != nil { + return err + } } - } - if r.needGeositeDatabase { - monitor.Start("initialize geosite database") - err := r.prepareGeositeDatabase() - monitor.Finish() - if err != nil { - return err + case adapter.StartStateStart: + if r.needGeoIPDatabase { + monitor.Start("initialize geoip database") + err := r.prepareGeoIPDatabase() + monitor.Finish() + if err != nil { + return err + } } - } - if r.needGeositeDatabase { - for _, rule := range r.rules { - err := rule.UpdateGeosite() + if r.needGeositeDatabase { + monitor.Start("initialize geosite database") + err := r.prepareGeositeDatabase() + monitor.Finish() if err != nil { - r.logger.Error("failed to initialize geosite: ", err) + return err } } - for _, rule := range r.dnsRules { - err := rule.UpdateGeosite() + if r.needGeositeDatabase { + for _, rule := range r.rules { + err := rule.UpdateGeosite() + if err != nil { + r.logger.Error("failed to initialize geosite: ", err) + } + } + for _, rule := range r.dnsRules { + err := rule.UpdateGeosite() + if err != nil { + r.logger.Error("failed to initialize geosite: ", err) + } + } + err := common.Close(r.geositeReader) if err != nil { - r.logger.Error("failed to initialize geosite: ", err) + return err } + r.geositeCache = nil + r.geositeReader = nil } - err := common.Close(r.geositeReader) - if err != nil { - return err + + if runtime.GOOS == "windows" { + powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) + if err == nil { + r.powerListener = powerListener + } else { + r.logger.Warn("initialize power listener: ", err) + } } - r.geositeCache = nil - r.geositeReader = nil - } - if runtime.GOOS == "windows" { - powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) - if err == nil { - r.powerListener = powerListener - } else { - r.logger.Warn("initialize power listener: ", err) + if r.powerListener != nil { + monitor.Start("start power listener") + err := r.powerListener.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start power listener") + } } - } - if r.powerListener != nil { - monitor.Start("start power listener") - err := r.powerListener.Start() + monitor.Start("initialize DNS client") + r.dnsClient.Start() monitor.Finish() - if err != nil { - return E.Cause(err, "start power listener") - } - } - monitor.Start("initialize DNS client") - r.dnsClient.Start() - monitor.Finish() + if C.IsAndroid && r.platformInterface == nil { + monitor.Start("initialize package manager") + packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ + Callback: r, + Logger: r.logger, + }) + monitor.Finish() + if err != nil { + return E.Cause(err, "create package manager") + } + if r.enforcePackageManager { + monitor.Start("start package manager") + err = packageManager.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start package manager") + } + } + r.packageManager = packageManager + } - if C.IsAndroid && r.platformInterface == nil { - monitor.Start("initialize package manager") - packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ - Callback: r, - Logger: r.logger, - }) - monitor.Finish() - if err != nil { - return E.Cause(err, "create package manager") + for i, rule := range r.dnsRules { + monitor.Start("initialize DNS rule[", i, "]") + err := rule.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "initialize DNS rule[", i, "]") + } } - if r.needPackageManager { - monitor.Start("start package manager") - err = packageManager.Start() + for i, transport := range r.transports { + monitor.Start("initialize DNS transport[", i, "]") + err := transport.Start() monitor.Finish() if err != nil { - return E.Cause(err, "start package manager") + return E.Cause(err, "initialize DNS server[", i, "]") } } - r.packageManager = packageManager - } - - for i, rule := range r.dnsRules { - monitor.Start("initialize DNS rule[", i, "]") - err := rule.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize DNS rule[", i, "]") + if r.timeService != nil { + monitor.Start("initialize time service") + err := r.timeService.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "initialize time service") + } } - } - for i, transport := range r.transports { - monitor.Start("initialize DNS transport[", i, "]") - err := transport.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize DNS server[", i, "]") + case adapter.StartStatePostStart: + var cacheContext *adapter.HTTPStartContext + if len(r.ruleSets) > 0 { + monitor.Start("initialize rule-set") + cacheContext = adapter.NewHTTPStartContext() + var ruleSetStartGroup task.Group + for i, ruleSet := range r.ruleSets { + ruleSetInPlace := ruleSet + ruleSetStartGroup.Append0(func(ctx context.Context) error { + err := ruleSetInPlace.StartContext(ctx, cacheContext) + if err != nil { + return E.Cause(err, "initialize rule-set[", i, "]") + } + return nil + }) + } + ruleSetStartGroup.Concurrency(5) + ruleSetStartGroup.FastFail() + err := ruleSetStartGroup.Run(r.ctx) + monitor.Finish() + if err != nil { + return err + } } - } - if r.timeService != nil { - monitor.Start("initialize time service") - err := r.timeService.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize time service") + if cacheContext != nil { + cacheContext.Close() + } + needFindProcess := r.needFindProcess + needWIFIState := r.needWIFIState + for _, ruleSet := range r.ruleSets { + metadata := ruleSet.Metadata() + if metadata.ContainsProcessRule { + needFindProcess = true + } + if metadata.ContainsWIFIRule { + needWIFIState = true + } + } + if C.IsAndroid && r.platformInterface == nil && !r.enforcePackageManager { + if needFindProcess { + monitor.Start("start package manager") + err := r.packageManager.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start package manager") + } + } else { + r.packageManager = nil + } } + if needFindProcess { + if r.platformInterface != nil { + r.processSearcher = r.platformInterface + } else { + monitor.Start("initialize process searcher") + searcher, err := process.NewSearcher(process.Config{ + Logger: r.logger, + PackageManager: r.packageManager, + }) + monitor.Finish() + if err != nil { + if err != os.ErrInvalid { + r.logger.Warn(E.Cause(err, "create process searcher")) + } + } else { + r.processSearcher = searcher + } + } + } + if needWIFIState && r.platformInterface != nil { + monitor.Start("initialize WIFI state") + r.needWIFIState = true + r.interfaceMonitor.RegisterCallback(func(_ int) { + r.updateWIFIState() + }) + r.updateWIFIState() + monitor.Finish() + } + for i, rule := range r.rules { + monitor.Start("initialize rule[", i, "]") + err := rule.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "initialize rule[", i, "]") + } + } + for _, ruleSet := range r.ruleSets { + monitor.Start("post start rule_set[", ruleSet.Name(), "]") + err := ruleSet.PostStart() + monitor.Finish() + if err != nil { + return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]") + } + } + r.started = true + return nil + case adapter.StartStateStarted: + for _, ruleSet := range r.ruleSetMap { + ruleSet.Cleanup() + } + runtime.GC() } return nil } @@ -668,113 +702,6 @@ func (r *Router) Close() error { return err } -func (r *Router) PostStart() error { - monitor := taskmonitor.New(r.logger, C.StopTimeout) - var cacheContext *adapter.HTTPStartContext - if len(r.ruleSets) > 0 { - monitor.Start("initialize rule-set") - cacheContext = adapter.NewHTTPStartContext() - var ruleSetStartGroup task.Group - for i, ruleSet := range r.ruleSets { - ruleSetInPlace := ruleSet - ruleSetStartGroup.Append0(func(ctx context.Context) error { - err := ruleSetInPlace.StartContext(ctx, cacheContext) - if err != nil { - return E.Cause(err, "initialize rule-set[", i, "]") - } - return nil - }) - } - ruleSetStartGroup.Concurrency(5) - ruleSetStartGroup.FastFail() - err := ruleSetStartGroup.Run(r.ctx) - monitor.Finish() - if err != nil { - return err - } - } - if cacheContext != nil { - cacheContext.Close() - } - needFindProcess := r.needFindProcess - needWIFIState := r.needWIFIState - for _, ruleSet := range r.ruleSets { - metadata := ruleSet.Metadata() - if metadata.ContainsProcessRule { - needFindProcess = true - } - if metadata.ContainsWIFIRule { - needWIFIState = true - } - } - if C.IsAndroid && r.platformInterface == nil && !r.needPackageManager { - if needFindProcess { - monitor.Start("start package manager") - err := r.packageManager.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start package manager") - } - } else { - r.packageManager = nil - } - } - if needFindProcess { - if r.platformInterface != nil { - r.processSearcher = r.platformInterface - } else { - monitor.Start("initialize process searcher") - searcher, err := process.NewSearcher(process.Config{ - Logger: r.logger, - PackageManager: r.packageManager, - }) - monitor.Finish() - if err != nil { - if err != os.ErrInvalid { - r.logger.Warn(E.Cause(err, "create process searcher")) - } - } else { - r.processSearcher = searcher - } - } - } - if needWIFIState && r.platformInterface != nil { - monitor.Start("initialize WIFI state") - r.needWIFIState = true - r.interfaceMonitor.RegisterCallback(func(_ int) { - r.updateWIFIState() - }) - r.updateWIFIState() - monitor.Finish() - } - for i, rule := range r.rules { - monitor.Start("initialize rule[", i, "]") - err := rule.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize rule[", i, "]") - } - } - for _, ruleSet := range r.ruleSets { - monitor.Start("post start rule_set[", ruleSet.Name(), "]") - err := ruleSet.PostStart() - monitor.Finish() - if err != nil { - return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]") - } - } - r.started = true - return nil -} - -func (r *Router) Cleanup() error { - for _, ruleSet := range r.ruleSetMap { - ruleSet.Cleanup() - } - runtime.GC() - return nil -} - func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { outbound, loaded := r.outboundByTag[tag] return outbound, loaded diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 0bf45ba25f..00579c18d5 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -22,7 +22,7 @@ import ( N "github.com/sagernet/sing/common/network" ) -func NewRuleAction(router adapter.Router, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { +func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { switch action.Action { case "": return nil, nil @@ -36,7 +36,7 @@ func NewRuleAction(router adapter.Router, logger logger.ContextLogger, action op UDPConnect: action.RouteOptionsOptions.UDPConnect, }, nil case C.RuleActionTypeDirect: - directDialer, err := dialer.New(router, option.DialerOptions(action.DirectOptions)) + directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions)) if err != nil { return nil, err } diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 566c816e11..a12c63ef85 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -52,7 +52,7 @@ type RuleItem interface { } func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { - action, err := NewRuleAction(router, logger, options.RuleAction) + action, err := NewRuleAction(ctx, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } @@ -254,7 +254,7 @@ type LogicalRule struct { } func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { - action, err := NewRuleAction(router, logger, options.RuleAction) + action, err := NewRuleAction(ctx, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") } diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index f055771827..fb58169140 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -33,23 +33,24 @@ import ( var _ adapter.RuleSet = (*RemoteRuleSet)(nil) type RemoteRuleSet struct { - ctx context.Context - cancel context.CancelFunc - router adapter.Router - logger logger.ContextLogger - options option.RuleSet - metadata adapter.RuleSetMetadata - updateInterval time.Duration - dialer N.Dialer - rules []adapter.HeadlessRule - lastUpdated time.Time - lastEtag string - updateTicker *time.Ticker - cacheFile adapter.CacheFile - pauseManager pause.Manager - callbackAccess sync.Mutex - callbacks list.List[adapter.RuleSetUpdateCallback] - refs atomic.Int32 + ctx context.Context + cancel context.CancelFunc + router adapter.Router + outboundManager adapter.OutboundManager + logger logger.ContextLogger + options option.RuleSet + metadata adapter.RuleSetMetadata + updateInterval time.Duration + dialer N.Dialer + rules []adapter.HeadlessRule + lastUpdated time.Time + lastEtag string + updateTicker *time.Ticker + cacheFile adapter.CacheFile + pauseManager pause.Manager + callbackAccess sync.Mutex + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 } func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { @@ -61,13 +62,14 @@ func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger. updateInterval = 24 * time.Hour } return &RemoteRuleSet{ - ctx: ctx, - cancel: cancel, - router: router, - logger: logger, - options: options, - updateInterval: updateInterval, - pauseManager: service.FromContext[pause.Manager](ctx), + ctx: ctx, + cancel: cancel, + router: router, + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + logger: logger, + options: options, + updateInterval: updateInterval, + pauseManager: service.FromContext[pause.Manager](ctx), } } @@ -83,17 +85,13 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter. s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx) var dialer N.Dialer if s.options.RemoteOptions.DownloadDetour != "" { - outbound, loaded := s.router.Outbound(s.options.RemoteOptions.DownloadDetour) + outbound, loaded := s.outboundManager.Outbound(s.options.RemoteOptions.DownloadDetour) if !loaded { return E.New("download_detour not found: ", s.options.RemoteOptions.DownloadDetour) } dialer = outbound } else { - outbound, err := s.router.DefaultOutbound(N.NetworkTCP) - if err != nil { - return err - } - dialer = outbound + dialer = s.outboundManager.Default() } s.dialer = dialer if s.cacheFile != nil { diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index 8325c37b2f..d5603e9ed5 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -23,6 +23,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" "github.com/insomniacslk/dhcp/dhcpv4" mDNS "github.com/miekg/dns" @@ -53,7 +54,7 @@ func NewTransport(options dns.TransportOptions) (*Transport, error) { if linkURL.Host == "" { return nil, E.New("missing interface name for DHCP") } - router := adapter.RouterFromContext(options.Context) + router := service.FromContext[adapter.Router](options.Context) if router == nil { return nil, E.New("missing router in context") } diff --git a/transport/fakeip/server.go b/transport/fakeip/server.go index 5e0c7eef02..d1bbb2aadd 100644 --- a/transport/fakeip/server.go +++ b/transport/fakeip/server.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/service" mDNS "github.com/miekg/dns" ) @@ -32,7 +33,7 @@ type Transport struct { } func NewTransport(options dns.TransportOptions) (*Transport, error) { - router := adapter.RouterFromContext(options.Context) + router := service.FromContext[adapter.Router](options.Context) if router == nil { return nil, E.New("missing router in context") } From a1be4552020acdd06cc9edb64bf7b2629739ac6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 10 Nov 2024 12:11:21 +0800 Subject: [PATCH 0163/1272] refactor: Modular network manager --- adapter/inbound/manager.go | 4 +- adapter/network.go | 23 ++ adapter/outbound/manager.go | 20 +- adapter/router.go | 19 +- box.go | 56 +-- cmd/sing-box/cmd_rule_set_match.go | 3 +- common/dialer/default.go | 24 +- common/dialer/dialer.go | 25 +- common/settings/proxy_darwin.go | 2 +- experimental/libbox/command_group.go | 4 +- experimental/libbox/command_select.go | 2 +- experimental/libbox/command_urltest.go | 4 +- experimental/libbox/config.go | 2 +- experimental/libbox/monitor.go | 2 +- experimental/libbox/platform/interface.go | 4 +- experimental/libbox/service.go | 10 +- experimental/libbox/service_pause.go | 2 +- include/clashapi_stub.go | 2 +- protocol/direct/loopback_detect.go | 16 +- protocol/tun/inbound.go | 24 +- protocol/wireguard/outbound.go | 2 +- route/geo_resources.go | 10 +- route/network.go | 334 ++++++++++++++++ route/route.go | 34 +- route/router.go | 456 +++------------------- route/rule/rule_default.go | 19 +- route/rule/rule_dns.go | 19 +- route/rule/rule_headless.go | 24 +- route/rule/rule_item_wifi_bssid.go | 12 +- route/rule/rule_item_wifi_ssid.go | 12 +- route/rule/rule_set.go | 6 +- route/rule/rule_set_local.go | 8 +- route/rule/rule_set_remote.go | 6 +- transport/dhcp/server.go | 25 +- transport/wireguard/device_system.go | 4 +- 35 files changed, 614 insertions(+), 605 deletions(-) create mode 100644 adapter/network.go create mode 100644 route/network.go diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go index d2be4b578c..69a3ad46b0 100644 --- a/adapter/inbound/manager.go +++ b/adapter/inbound/manager.go @@ -52,13 +52,13 @@ func (m *Manager) Start(stage adapter.StartStage) error { func (m *Manager) Close() error { m.access.Lock() + defer m.access.Unlock() if !m.started { - panic("not started") + return nil } m.started = false inbounds := m.inbounds m.inbounds = nil - m.access.Unlock() monitor := taskmonitor.New(m.logger, C.StopTimeout) var err error for _, inbound := range inbounds { diff --git a/adapter/network.go b/adapter/network.go new file mode 100644 index 0000000000..0ce2741185 --- /dev/null +++ b/adapter/network.go @@ -0,0 +1,23 @@ +package adapter + +import ( + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/control" +) + +type NetworkManager interface { + NewService + InterfaceFinder() control.InterfaceFinder + UpdateInterfaces() error + DefaultInterface() string + AutoDetectInterface() bool + AutoDetectInterfaceFunc() control.Func + DefaultMark() uint32 + RegisterAutoRedirectOutputMark(mark uint32) error + AutoRedirectOutputMark() uint32 + NetworkMonitor() tun.NetworkUpdateMonitor + InterfaceMonitor() tun.DefaultInterfaceMonitor + PackageManager() tun.PackageManager + WIFIState() WIFIState + ResetNetwork() +} diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index b3e1a170bf..10a89a1c9b 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -48,16 +48,17 @@ func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) { func (m *Manager) Start(stage adapter.StartStage) error { m.access.Lock() - defer m.access.Unlock() if m.started && m.stage >= stage { panic("already started") } m.started = true m.stage = stage + outbounds := m.outbounds + m.access.Unlock() if stage == adapter.StartStateStart { - m.startOutbounds() + return m.startOutbounds(outbounds) } else { - for _, outbound := range m.outbounds { + for _, outbound := range outbounds { err := adapter.LegacyStart(outbound, stage) if err != nil { return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") @@ -67,13 +68,13 @@ func (m *Manager) Start(stage adapter.StartStage) error { return nil } -func (m *Manager) startOutbounds() error { +func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error { monitor := taskmonitor.New(m.logger, C.StartTimeout) started := make(map[string]bool) for { canContinue := false startOne: - for _, outboundToStart := range m.outbounds { + for _, outboundToStart := range outbounds { outboundTag := outboundToStart.Tag() if started[outboundTag] { continue @@ -97,13 +98,13 @@ func (m *Manager) startOutbounds() error { } } } - if len(started) == len(m.outbounds) { + if len(started) == len(outbounds) { break } if canContinue { continue } - currentOutbound := common.Find(m.outbounds, func(it adapter.Outbound) bool { + currentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool { return !started[it.Tag()] }) var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error @@ -114,7 +115,9 @@ func (m *Manager) startOutbounds() error { if common.Contains(oTree, problemOutboundTag) { return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) } + m.access.Lock() problemOutbound := m.outboundByTag[problemOutboundTag] + m.access.Unlock() if problemOutbound == nil { return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]") } @@ -129,7 +132,8 @@ func (m *Manager) Close() error { monitor := taskmonitor.New(m.logger, C.StopTimeout) m.access.Lock() if !m.started { - panic("not started") + m.access.Unlock() + return nil } m.started = false outbounds := m.outbounds diff --git a/adapter/router.go b/adapter/router.go index b8ac51f573..40a461a79e 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -10,8 +10,6 @@ import ( "github.com/sagernet/sing-box/common/geoip" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/control" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" @@ -31,28 +29,13 @@ type Router interface { GeoIPReader() *geoip.Reader LoadGeosite(code string) (Rule, error) - RuleSet(tag string) (RuleSet, bool) - NeedWIFIState() bool Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) ClearDNSCache() - - InterfaceFinder() control.InterfaceFinder - UpdateInterfaces() error - DefaultInterface() string - AutoDetectInterface() bool - AutoDetectInterfaceFunc() control.Func - DefaultMark() uint32 - RegisterAutoRedirectOutputMark(mark uint32) error - AutoRedirectOutputMark() uint32 - NetworkMonitor() tun.NetworkUpdateMonitor - InterfaceMonitor() tun.DefaultInterfaceMonitor - PackageManager() tun.PackageManager - WIFIState() WIFIState Rules() []Rule ClashServer() ClashServer @@ -61,7 +44,7 @@ type Router interface { V2RayServer() V2RayServer SetV2RayServer(server V2RayServer) - ResetNetwork() error + ResetNetwork() } // Deprecated: Use ConnectionRouterEx instead. diff --git a/box.go b/box.go index d44e8ed058..8eac0dfa6c 100644 --- a/box.go +++ b/box.go @@ -34,6 +34,7 @@ type Box struct { router adapter.Router inbound *inbound.Manager outbound *outbound.Manager + network *route.NetworkManager logFactory log.Factory logger log.ContextLogger preServices1 map[string]adapter.Service @@ -109,20 +110,18 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "create log factory") } routeOptions := common.PtrValueOrDefault(options.Route) - inboundManager := inbound.NewManager(logFactory.NewLogger("inbound-manager"), inboundRegistry) - outboundManager := outbound.NewManager(logFactory.NewLogger("outbound-manager"), outboundRegistry, routeOptions.Final) + inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry) + outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final) ctx = service.ContextWith[adapter.InboundManager](ctx, inboundManager) ctx = service.ContextWith[adapter.OutboundManager](ctx, outboundManager) - router, err := route.NewRouter( - ctx, - logFactory, - routeOptions, - common.PtrValueOrDefault(options.DNS), - common.PtrValueOrDefault(options.NTP), - options.Inbounds, - ) + networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions) if err != nil { - return nil, E.Cause(err, "parse route options") + return nil, E.Cause(err, "initialize network manager") + } + ctx = service.ContextWith[adapter.NetworkManager](ctx, networkManager) + router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP)) + if err != nil { + return nil, E.Cause(err, "initialize router") } for i, inboundOptions := range options.Inbounds { var tag string @@ -177,11 +176,8 @@ func New(options Options) (*Box, error) { option.DirectOutboundOptions{}, ), )) - if err != nil { - return nil, err - } if platformInterface != nil { - err = platformInterface.Initialize(ctx, router) + err = platformInterface.Initialize(networkManager) if err != nil { return nil, E.Cause(err, "initialize platform interface") } @@ -219,6 +215,7 @@ func New(options Options) (*Box, error) { router: router, inbound: inboundManager, outbound: outboundManager, + network: networkManager, createdAt: createdAt, logFactory: logFactory, logger: logFactory.Logger(), @@ -295,6 +292,10 @@ func (s *Box) preStart() error { } } } + err = s.network.Start(adapter.StartStateInitialize) + if err != nil { + return E.Cause(err, "initialize network manager") + } err = s.router.Start(adapter.StartStateInitialize) if err != nil { return E.Cause(err, "initialize router") @@ -303,6 +304,10 @@ func (s *Box) preStart() error { if err != nil { return err } + err = s.network.Start(adapter.StartStateStart) + if err != nil { + return err + } return s.router.Start(adapter.StartStateStart) } @@ -337,6 +342,10 @@ func (s *Box) start() error { if err != nil { return err } + err = s.network.Start(adapter.StartStatePostStart) + if err != nil { + return err + } err = s.router.Start(adapter.StartStatePostStart) if err != nil { return err @@ -345,6 +354,10 @@ func (s *Box) start() error { if err != nil { return err } + err = s.network.Start(adapter.StartStateStarted) + if err != nil { + return err + } err = s.router.Start(adapter.StartStateStarted) if err != nil { return err @@ -378,13 +391,8 @@ func (s *Box) Close() error { } errors = E.Errors(errors, s.inbound.Close()) errors = E.Errors(errors, s.outbound.Close()) - monitor.Start("close router") - if err := common.Close(s.router); err != nil { - errors = E.Append(errors, err, func(err error) error { - return E.Cause(err, "close router") - }) - } - monitor.Finish() + errors = E.Errors(errors, s.network.Close()) + errors = E.Errors(errors, s.router.Close()) for serviceName, service := range s.preServices1 { monitor.Start("close ", serviceName) errors = E.Append(errors, service.Close(), func(err error) error { @@ -415,6 +423,10 @@ func (s *Box) Outbound() adapter.OutboundManager { return s.outbound } +func (s *Box) Network() adapter.NetworkManager { + return s.network +} + func (s *Box) Router() adapter.Router { return s.router } diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index b96ff22cf9..f5016117d7 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "io" "os" @@ -83,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error { } for i, ruleOptions := range plainRuleSet.Rules { var currentRule adapter.HeadlessRule - currentRule, err = rule.NewHeadlessRule(nil, ruleOptions) + currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } diff --git a/common/dialer/default.go b/common/dialer/default.go index 0c8cee18fd..daf7995037 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -29,31 +29,31 @@ type DefaultDialer struct { isWireGuardListener bool } -func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) { +func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) { var dialer net.Dialer var listener net.ListenConfig if options.BindInterface != "" { var interfaceFinder control.InterfaceFinder - if router != nil { - interfaceFinder = router.InterfaceFinder() + if networkManager != nil { + interfaceFinder = networkManager.InterfaceFinder() } else { interfaceFinder = control.NewDefaultInterfaceFinder() } bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) - } else if router != nil && router.AutoDetectInterface() { - bindFunc := router.AutoDetectInterfaceFunc() + } else if networkManager != nil && networkManager.AutoDetectInterface() { + bindFunc := networkManager.AutoDetectInterfaceFunc() dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) - } else if router != nil && router.DefaultInterface() != "" { - bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1) + } else if networkManager != nil && networkManager.DefaultInterface() != "" { + bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), networkManager.DefaultInterface(), -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) } var autoRedirectOutputMark uint32 - if router != nil { - autoRedirectOutputMark = router.AutoRedirectOutputMark() + if networkManager != nil { + autoRedirectOutputMark = networkManager.AutoRedirectOutputMark() } if autoRedirectOutputMark > 0 { dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark)) @@ -65,9 +65,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi if autoRedirectOutputMark > 0 { return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`") } - } else if router != nil && router.DefaultMark() > 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark())) - listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark())) + } else if networkManager != nil && networkManager.DefaultMark() > 0 { + dialer.Control = control.Append(dialer.Control, control.RoutingMark(networkManager.DefaultMark())) + listener.Control = control.Append(listener.Control, control.RoutingMark(networkManager.DefaultMark())) if autoRedirectOutputMark > 0 { return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`") } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index fe4c7c1204..047a25146b 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -13,16 +13,16 @@ import ( ) func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { - router := service.FromContext[adapter.Router](ctx) + networkManager := service.FromContext[adapter.NetworkManager](ctx) if options.IsWireGuardListener { - return NewDefault(router, options) + return NewDefault(networkManager, options) } var ( dialer N.Dialer err error ) if options.Detour == "" { - dialer, err = NewDefault(router, options) + dialer, err = NewDefault(networkManager, options) if err != nil { return nil, err } @@ -33,16 +33,19 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { } dialer = NewDetour(outboundManager, options.Detour) } - if router == nil { - return NewDefault(router, options) + if networkManager == nil { + return NewDefault(networkManager, options) } if options.Detour == "" { - dialer = NewResolveDialer( - router, - dialer, - options.Detour == "" && !options.TCPFastOpen, - dns.DomainStrategy(options.DomainStrategy), - time.Duration(options.FallbackDelay)) + router := service.FromContext[adapter.Router](ctx) + if router != nil { + dialer = NewResolveDialer( + router, + dialer, + options.Detour == "" && !options.TCPFastOpen, + dns.DomainStrategy(options.DomainStrategy), + time.Duration(options.FallbackDelay)) + } } return dialer, nil } diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index 2d86fa2d9a..17e82cf59f 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -25,7 +25,7 @@ type DarwinSystemProxy struct { } func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) { - interfaceMonitor := service.FromContext[adapter.Router](ctx).InterfaceMonitor() + interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor() if interfaceMonitor == nil { return nil, E.New("missing interface monitor") } diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go index 0915f56b83..684cac62ce 100644 --- a/experimental/libbox/command_group.go +++ b/experimental/libbox/command_group.go @@ -109,7 +109,7 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) { 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.Router().Outbounds() + outbounds := boxService.instance.Outbound().Outbounds() var iGroups []adapter.OutboundGroup for _, it := range outbounds { if group, isGroup := it.(adapter.OutboundGroup); isGroup { @@ -130,7 +130,7 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { } for _, itemTag := range iGroup.All() { - itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag) + itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag) if !isLoaded { continue } diff --git a/experimental/libbox/command_select.go b/experimental/libbox/command_select.go index f352005d11..6dd74a2d48 100644 --- a/experimental/libbox/command_select.go +++ b/experimental/libbox/command_select.go @@ -43,7 +43,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error { if service == nil { return writeError(conn, E.New("service not ready")) } - outboundGroup, isLoaded := service.instance.Router().Outbound(groupTag) + outboundGroup, isLoaded := service.instance.Outbound().Outbound(groupTag) if !isLoaded { return writeError(conn, E.New("selector not found: ", groupTag)) } diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go index c72ded8a3c..c30d996e6b 100644 --- a/experimental/libbox/command_urltest.go +++ b/experimental/libbox/command_urltest.go @@ -41,7 +41,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error { if serviceNow == nil { return nil } - abstractOutboundGroup, isLoaded := serviceNow.instance.Router().Outbound(groupTag) + abstractOutboundGroup, isLoaded := serviceNow.instance.Outbound().Outbound(groupTag) if !isLoaded { return writeError(conn, E.New("outbound group not found: ", groupTag)) } @@ -55,7 +55,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error { } else { historyStorage := service.PtrFromContext[urltest.HistoryStorage](serviceNow.ctx) outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { - itOutbound, _ := serviceNow.instance.Router().Outbound(it) + itOutbound, _ := serviceNow.instance.Outbound().Outbound(it) return itOutbound }), func(it adapter.Outbound) bool { if it == nil { diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index cc8057f49d..3310b09a58 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -50,7 +50,7 @@ func CheckConfig(configContent string) error { type platformInterfaceStub struct{} -func (s *platformInterfaceStub) Initialize(ctx context.Context, router adapter.Router) error { +func (s *platformInterfaceStub) Initialize(networkManager adapter.NetworkManager) error { return nil } diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index eb56a314af..00a3e0cf6c 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -123,7 +123,7 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s err = m.updateInterfaces() } if err == nil { - err = m.router.UpdateInterfaces() + err = m.networkManager.UpdateInterfaces() } if err != nil { m.logger.Error(E.Cause(err, "update interfaces")) diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 3dc000814a..16eab5abcc 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -1,8 +1,6 @@ package platform import ( - "context" - "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/option" @@ -12,7 +10,7 @@ import ( ) type Interface interface { - Initialize(ctx context.Context, router adapter.Router) error + Initialize(networkManager adapter.NetworkManager) error UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int) error OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 24fbdb4c94..e3d2f95474 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -117,13 +117,13 @@ var ( ) type platformInterfaceWrapper struct { - iif PlatformInterface - useProcFS bool - router adapter.Router + iif PlatformInterface + useProcFS bool + networkManager adapter.NetworkManager } -func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error { - w.router = router +func (w *platformInterfaceWrapper) Initialize(networkManager adapter.NetworkManager) error { + w.networkManager = networkManager return nil } diff --git a/experimental/libbox/service_pause.go b/experimental/libbox/service_pause.go index c4aa8daaff..a7599a767a 100644 --- a/experimental/libbox/service_pause.go +++ b/experimental/libbox/service_pause.go @@ -29,5 +29,5 @@ func (s *BoxService) Wake() { } func (s *BoxService) ResetNetwork() { - _ = s.instance.Router().ResetNetwork() + s.instance.Router().ResetNetwork() } diff --git a/include/clashapi_stub.go b/include/clashapi_stub.go index 26e199b314..e7d5304fa1 100644 --- a/include/clashapi_stub.go +++ b/include/clashapi_stub.go @@ -13,7 +13,7 @@ import ( ) func init() { - experimental.RegisterClashServerConstructor(func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { + experimental.RegisterClashServerConstructor(func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`) }) } diff --git a/protocol/direct/loopback_detect.go b/protocol/direct/loopback_detect.go index 5a184e692a..4bc1be3b04 100644 --- a/protocol/direct/loopback_detect.go +++ b/protocol/direct/loopback_detect.go @@ -11,18 +11,18 @@ import ( ) type loopBackDetector struct { - router adapter.Router + networkManager adapter.NetworkManager connAccess sync.RWMutex packetConnAccess sync.RWMutex connMap map[netip.AddrPort]netip.AddrPort packetConnMap map[uint16]uint16 } -func newLoopBackDetector(router adapter.Router) *loopBackDetector { +func newLoopBackDetector(networkManager adapter.NetworkManager) *loopBackDetector { return &loopBackDetector{ - router: router, - connMap: make(map[netip.AddrPort]netip.AddrPort), - packetConnMap: make(map[uint16]uint16), + networkManager: networkManager, + connMap: make(map[netip.AddrPort]netip.AddrPort), + packetConnMap: make(map[uint16]uint16), } } @@ -33,7 +33,7 @@ func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn { } if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn { if !source.Addr().IsLoopback() { - _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) if err != nil { return conn } @@ -59,7 +59,7 @@ func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Soc return conn } if !source.Addr().IsLoopback() { - _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) if err != nil { return conn } @@ -82,7 +82,7 @@ func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.Ad return false } if !source.Addr().IsLoopback() { - _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) if err != nil { return false } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index f2476223ce..302afb578d 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -36,10 +36,11 @@ func RegisterInbound(registry *inbound.Registry) { } type Inbound struct { - tag string - ctx context.Context - router adapter.Router - logger log.ContextLogger + tag string + ctx context.Context + router adapter.Router + networkManager adapter.NetworkManager + logger log.ContextLogger // Deprecated inboundOptions option.InboundOptions tunOptions tun.Options @@ -168,11 +169,12 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if outputMark == 0 { outputMark = tun.DefaultAutoRedirectOutputMark } - + networkManager := service.FromContext[adapter.NetworkManager](ctx) inbound := &Inbound{ tag: tag, ctx: ctx, router: router, + networkManager: networkManager, logger: logger, inboundOptions: options.InboundOptions, tunOptions: tun.Options{ @@ -198,7 +200,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo IncludeAndroidUser: options.IncludeAndroidUser, IncludePackage: options.IncludePackage, ExcludePackage: options.ExcludePackage, - InterfaceMonitor: router.InterfaceMonitor(), + InterfaceMonitor: networkManager.InterfaceMonitor(), }, endpointIndependentNat: options.EndpointIndependentNat, udpTimeout: udpTimeout, @@ -216,8 +218,8 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo Context: ctx, Handler: (*autoRedirectHandler)(inbound), Logger: logger, - NetworkMonitor: router.NetworkMonitor(), - InterfaceFinder: router.InterfaceFinder(), + NetworkMonitor: networkManager.NetworkMonitor(), + InterfaceFinder: networkManager.InterfaceFinder(), TableName: "sing-box", DisableNFTables: dErr == nil && disableNFTables, RouteAddressSet: &inbound.routeAddressSet, @@ -248,7 +250,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } if markMode { inbound.tunOptions.AutoRedirectMarkMode = true - err = router.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) + err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) if err != nil { return nil, err } @@ -300,7 +302,7 @@ func (t *Inbound) Tag() string { func (t *Inbound) Start() error { if C.IsAndroid && t.platformInterface == nil { - t.tunOptions.BuildAndroidRules(t.router.PackageManager()) + t.tunOptions.BuildAndroidRules(t.networkManager.PackageManager()) } if t.tunOptions.Name == "" { t.tunOptions.Name = tun.CalculateInterfaceName("") @@ -338,7 +340,7 @@ func (t *Inbound) Start() error { Handler: t, Logger: t.logger, ForwarderBindInterface: forwarderBindInterface, - InterfaceFinder: t.router.InterfaceFinder(), + InterfaceFinder: t.networkManager.InterfaceFinder(), IncludeAllNetworks: includeAllNetworks, }) if err != nil { diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 79e8873257..12e53e591a 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -100,7 +100,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if !options.SystemInterface && tun.WithGVisor { wireTunDevice, err = wireguard.NewStackDevice(options.LocalAddress, mtu) } else { - wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, options.LocalAddress, mtu, options.GSO) + wireTunDevice, err = wireguard.NewSystemDevice(service.FromContext[adapter.NetworkManager](ctx), options.InterfaceName, options.LocalAddress, mtu, options.GSO) } if err != nil { return nil, E.Cause(err, "create WireGuard device") diff --git a/route/geo_resources.go b/route/geo_resources.go index 91c06796bc..c5a45ffdee 100644 --- a/route/geo_resources.go +++ b/route/geo_resources.go @@ -33,7 +33,7 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { if err != nil { return nil, err } - rule, err = R.NewDefaultRule(r.ctx, r, nil, geosite.Compile(items)) + rule, err = R.NewDefaultRule(r.ctx, nil, geosite.Compile(items)) if err != nil { return nil, err } @@ -145,13 +145,13 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error { r.logger.Info("downloading geoip database") var detour adapter.Outbound if r.geoIPOptions.DownloadDetour != "" { - outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour) + outbound, loaded := r.outboundManager.Outbound(r.geoIPOptions.DownloadDetour) if !loaded { return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour) } detour = outbound } else { - detour = r.defaultOutboundForConnection + detour = r.outboundManager.Default() } if parentDir := filepath.Dir(savePath); parentDir != "" { @@ -200,13 +200,13 @@ func (r *Router) downloadGeositeDatabase(savePath string) error { r.logger.Info("downloading geosite database") var detour adapter.Outbound if r.geositeOptions.DownloadDetour != "" { - outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour) + outbound, loaded := r.outboundManager.Outbound(r.geositeOptions.DownloadDetour) if !loaded { return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour) } detour = outbound } else { - detour = r.defaultOutboundForConnection + detour = r.outboundManager.Default() } if parentDir := filepath.Dir(savePath); parentDir != "" { diff --git a/route/network.go b/route/network.go new file mode 100644 index 0000000000..912b6621e7 --- /dev/null +++ b/route/network.go @@ -0,0 +1,334 @@ +package route + +import ( + "context" + "errors" + "net/netip" + "os" + "runtime" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/conntrack" + "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/control" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/winpowrprof" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" +) + +var _ adapter.NetworkManager = (*NetworkManager)(nil) + +type NetworkManager struct { + logger logger.ContextLogger + interfaceFinder *control.DefaultInterfaceFinder + autoDetectInterface bool + defaultInterface string + defaultMark uint32 + autoRedirectOutputMark uint32 + networkMonitor tun.NetworkUpdateMonitor + interfaceMonitor tun.DefaultInterfaceMonitor + packageManager tun.PackageManager + powerListener winpowrprof.EventListener + pauseManager pause.Manager + platformInterface platform.Interface + outboundManager adapter.OutboundManager + wifiState adapter.WIFIState + started bool +} + +func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { + nm := &NetworkManager{ + logger: logger, + interfaceFinder: control.NewDefaultInterfaceFinder(), + autoDetectInterface: routeOptions.AutoDetectInterface, + defaultInterface: routeOptions.DefaultInterface, + defaultMark: routeOptions.DefaultMark, + pauseManager: service.FromContext[pause.Manager](ctx), + platformInterface: service.FromContext[platform.Interface](ctx), + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + } + usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil && nm.platformInterface.UsePlatformDefaultInterfaceMonitor() + enforceInterfaceMonitor := routeOptions.AutoDetectInterface + if !usePlatformDefaultInterfaceMonitor { + networkMonitor, err := tun.NewNetworkUpdateMonitor(logger) + if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { + if err != nil { + return nil, E.Cause(err, "create network monitor") + } + nm.networkMonitor = networkMonitor + interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(nm.networkMonitor, logger, tun.DefaultInterfaceMonitorOptions{ + InterfaceFinder: nm.interfaceFinder, + OverrideAndroidVPN: routeOptions.OverrideAndroidVPN, + UnderNetworkExtension: nm.platformInterface != nil && nm.platformInterface.UnderNetworkExtension(), + }) + if err != nil { + return nil, E.New("auto_detect_interface unsupported on current platform") + } + interfaceMonitor.RegisterCallback(nm.notifyNetworkUpdate) + nm.interfaceMonitor = interfaceMonitor + } + } else { + interfaceMonitor := nm.platformInterface.CreateDefaultInterfaceMonitor(logger) + interfaceMonitor.RegisterCallback(nm.notifyNetworkUpdate) + nm.interfaceMonitor = interfaceMonitor + } + return nm, nil +} + +func (r *NetworkManager) Start(stage adapter.StartStage) error { + monitor := taskmonitor.New(r.logger, C.StartTimeout) + switch stage { + case adapter.StartStateInitialize: + if r.interfaceMonitor != nil { + monitor.Start("initialize interface monitor") + err := r.interfaceMonitor.Start() + monitor.Finish() + if err != nil { + return err + } + } + if r.networkMonitor != nil { + monitor.Start("initialize network monitor") + err := r.networkMonitor.Start() + monitor.Finish() + if err != nil { + return err + } + } + case adapter.StartStateStart: + if runtime.GOOS == "windows" { + powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) + if err == nil { + r.powerListener = powerListener + } else { + r.logger.Warn("initialize power listener: ", err) + } + } + if r.powerListener != nil { + monitor.Start("start power listener") + err := r.powerListener.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start power listener") + } + } + if C.IsAndroid && r.platformInterface == nil { + monitor.Start("initialize package manager") + packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ + Callback: r, + Logger: r.logger, + }) + monitor.Finish() + if err != nil { + return E.Cause(err, "create package manager") + } + monitor.Start("start package manager") + err = packageManager.Start() + monitor.Finish() + if err != nil { + r.logger.Warn("initialize package manager: ", err) + } else { + r.packageManager = packageManager + } + } + case adapter.StartStatePostStart: + r.started = true + } + return nil +} + +func (r *NetworkManager) Close() error { + monitor := taskmonitor.New(r.logger, C.StopTimeout) + var err error + if r.interfaceMonitor != nil { + monitor.Start("close interface monitor") + err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { + return E.Cause(err, "close interface monitor") + }) + monitor.Finish() + } + if r.networkMonitor != nil { + monitor.Start("close network monitor") + err = E.Append(err, r.networkMonitor.Close(), func(err error) error { + return E.Cause(err, "close network monitor") + }) + monitor.Finish() + } + if r.packageManager != nil { + monitor.Start("close package manager") + err = E.Append(err, r.packageManager.Close(), func(err error) error { + return E.Cause(err, "close package manager") + }) + monitor.Finish() + } + if r.powerListener != nil { + monitor.Start("close power listener") + err = E.Append(err, r.powerListener.Close(), func(err error) error { + return E.Cause(err, "close power listener") + }) + monitor.Finish() + } + return nil +} + +func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { + return r.interfaceFinder +} + +func (r *NetworkManager) UpdateInterfaces() error { + if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { + return r.interfaceFinder.Update() + } else { + interfaces, err := r.platformInterface.Interfaces() + if err != nil { + return err + } + r.interfaceFinder.UpdateInterfaces(interfaces) + return nil + } +} + +func (r *NetworkManager) DefaultInterface() string { + return r.defaultInterface +} + +func (r *NetworkManager) AutoDetectInterface() bool { + return r.autoDetectInterface +} + +func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { + if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { + return func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return r.platformInterface.AutoDetectInterfaceControl(int(fd)) + }) + } + } else { + if r.interfaceMonitor == nil { + return nil + } + return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { + remoteAddr := M.ParseSocksaddr(address).Addr + if C.IsLinux { + interfaceName, interfaceIndex = r.interfaceMonitor.DefaultInterface(remoteAddr) + if interfaceIndex == -1 { + err = tun.ErrNoRoute + } + } else { + interfaceIndex = r.interfaceMonitor.DefaultInterfaceIndex(remoteAddr) + if interfaceIndex == -1 { + err = tun.ErrNoRoute + } + } + return + }) + } +} + +func (r *NetworkManager) DefaultMark() uint32 { + return r.defaultMark +} + +func (r *NetworkManager) RegisterAutoRedirectOutputMark(mark uint32) error { + if r.autoRedirectOutputMark > 0 { + return E.New("only one auto-redirect can be configured") + } + r.autoRedirectOutputMark = mark + return nil +} + +func (r *NetworkManager) AutoRedirectOutputMark() uint32 { + return r.autoRedirectOutputMark +} + +func (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor { + return r.networkMonitor +} + +func (r *NetworkManager) InterfaceMonitor() tun.DefaultInterfaceMonitor { + return r.interfaceMonitor +} + +func (r *NetworkManager) PackageManager() tun.PackageManager { + return r.packageManager +} + +func (r *NetworkManager) WIFIState() adapter.WIFIState { + return r.wifiState +} + +func (r *NetworkManager) ResetNetwork() { + conntrack.Close() + + for _, outbound := range r.outboundManager.Outbounds() { + listener, isListener := outbound.(adapter.InterfaceUpdateListener) + if isListener { + listener.InterfaceUpdated() + } + } +} + +func (r *NetworkManager) notifyNetworkUpdate(event int) { + if event == tun.EventNoRoute { + r.pauseManager.NetworkPause() + r.logger.Error("missing default interface") + } else { + r.pauseManager.NetworkWake() + if C.IsAndroid && r.platformInterface == nil { + var vpnStatus string + if r.interfaceMonitor.AndroidVPNEnabled() { + vpnStatus = "enabled" + } else { + vpnStatus = "disabled" + } + r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) + } else { + r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) + } + if r.platformInterface != nil { + state := r.platformInterface.ReadWIFIState() + if state != r.wifiState { + r.wifiState = state + if state.SSID == "" && state.BSSID == "" { + r.logger.Info("updated WIFI state: disconnected") + } else { + r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) + } + } + } + } + + if !r.started { + return + } + + r.ResetNetwork() +} + +func (r *NetworkManager) notifyWindowsPowerEvent(event int) { + switch event { + case winpowrprof.EVENT_SUSPEND: + r.pauseManager.DevicePause() + r.ResetNetwork() + case winpowrprof.EVENT_RESUME: + if !r.pauseManager.IsDevicePaused() { + return + } + fallthrough + case winpowrprof.EVENT_RESUME_AUTOMATIC: + r.pauseManager.DeviceWake() + r.ResetNetwork() + } +} + +func (r *NetworkManager) OnPackagesUpdated(packages int, sharedUsers int) { + r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") +} diff --git a/route/route.go b/route/route.go index 91d7f3c9bf..d55b4ec170 100644 --- a/route/route.go +++ b/route/route.go @@ -58,8 +58,8 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { + detour, loaded := r.inboundManager.Get(metadata.InboundDetour) + if !loaded { return E.New("inbound detour not found: ", metadata.InboundDetour) } injectable, isInjectable := detour.(adapter.TCPInjectableInbound) @@ -100,7 +100,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - selectedOutbound, loaded := r.Outbound(action.Outbound) + selectedOutbound, loaded := r.outboundManager.Outbound(action.Outbound) if !loaded { buf.ReleaseMulti(buffers) return E.New("outbound not found: ", action.Outbound) @@ -128,13 +128,14 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad } } if selectedRule == nil { - if r.defaultOutboundForConnection == nil { + defaultOutbound := r.outboundManager.Default() + if !common.Contains(defaultOutbound.Network(), N.NetworkTCP) { buf.ReleaseMulti(buffers) - return E.New("missing default outbound with TCP support") + return E.New("TCP is not supported by default outbound: ", defaultOutbound.Tag()) } - selectedDialer = r.defaultOutboundForConnection - selectedTag = r.defaultOutboundForConnection.Tag() - selectedDescription = F.ToString("outbound/", r.defaultOutboundForConnection.Type(), "[", r.defaultOutboundForConnection.Tag(), "]") + selectedDialer = defaultOutbound + selectedTag = defaultOutbound.Tag() + selectedDescription = F.ToString("outbound/", defaultOutbound.Type(), "[", defaultOutbound.Tag(), "]") } for _, buffer := range buffers { @@ -217,8 +218,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { + detour, loaded := r.inboundManager.Get(metadata.InboundDetour) + if !loaded { return E.New("inbound detour not found: ", metadata.InboundDetour) } injectable, isInjectable := detour.(adapter.UDPInjectableInbound) @@ -254,7 +255,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - selectedOutbound, loaded := r.Outbound(action.Outbound) + selectedOutbound, loaded := r.outboundManager.Outbound(action.Outbound) if !loaded { N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("outbound not found: ", action.Outbound) @@ -279,13 +280,14 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } } if selectedRule == nil || selectReturn { - if r.defaultOutboundForPacketConnection == nil { + defaultOutbound := r.outboundManager.Default() + if !common.Contains(defaultOutbound.Network(), N.NetworkUDP) { N.ReleaseMultiPacketBuffer(packetBuffers) - return E.New("missing default outbound with UDP support") + return E.New("UDP is not supported by outbound: ", defaultOutbound.Tag()) } - selectedDialer = r.defaultOutboundForPacketConnection - selectedTag = r.defaultOutboundForPacketConnection.Tag() - selectedDescription = F.ToString("outbound/", r.defaultOutboundForPacketConnection.Type(), "[", r.defaultOutboundForPacketConnection.Tag(), "]") + selectedDialer = defaultOutbound + selectedTag = defaultOutbound.Tag() + selectedDescription = F.ToString("outbound/", defaultOutbound.Type(), "[", defaultOutbound.Tag(), "]") } for _, buffer := range packetBuffers { conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) diff --git a/route/router.go b/route/router.go index 12255b4d7b..62b374473f 100644 --- a/route/router.go +++ b/route/router.go @@ -2,17 +2,14 @@ package route import ( "context" - "errors" "net/netip" "net/url" "os" "runtime" "strings" - "syscall" "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geosite" @@ -25,16 +22,13 @@ import ( R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/transport/fakeip" "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" 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/task" - "github.com/sagernet/sing/common/winpowrprof" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) @@ -42,69 +36,50 @@ import ( var _ adapter.Router = (*Router)(nil) type Router struct { - ctx context.Context - logger log.ContextLogger - dnsLogger log.ContextLogger - inboundByTag map[string]adapter.Inbound - outbounds []adapter.Outbound - outboundByTag map[string]adapter.Outbound - rules []adapter.Rule - defaultDetour string - defaultOutboundForConnection adapter.Outbound - defaultOutboundForPacketConnection adapter.Outbound - needGeoIPDatabase bool - needGeositeDatabase bool - geoIPOptions option.GeoIPOptions - geositeOptions option.GeositeOptions - geoIPReader *geoip.Reader - geositeReader *geosite.Reader - geositeCache map[string]adapter.Rule - needFindProcess bool - dnsClient *dns.Client - defaultDomainStrategy dns.DomainStrategy - dnsRules []adapter.DNSRule - ruleSets []adapter.RuleSet - ruleSetMap map[string]adapter.RuleSet - defaultTransport dns.Transport - transports []dns.Transport - transportMap map[string]dns.Transport - transportDomainStrategy map[dns.Transport]dns.DomainStrategy - dnsReverseMapping *DNSReverseMapping - fakeIPStore adapter.FakeIPStore - interfaceFinder *control.DefaultInterfaceFinder - autoDetectInterface bool - defaultInterface string - defaultMark uint32 - autoRedirectOutputMark uint32 - networkMonitor tun.NetworkUpdateMonitor - interfaceMonitor tun.DefaultInterfaceMonitor - packageManager tun.PackageManager - powerListener winpowrprof.EventListener - processSearcher process.Searcher - timeService *ntp.Service - pauseManager pause.Manager - clashServer adapter.ClashServer - v2rayServer adapter.V2RayServer - platformInterface platform.Interface - needWIFIState bool - enforcePackageManager bool - wifiState adapter.WIFIState - started bool -} - -func NewRouter( - ctx context.Context, - logFactory log.Factory, - options option.RouteOptions, - dnsOptions option.DNSOptions, - ntpOptions option.NTPOptions, - inbounds []option.Inbound, -) (*Router, error) { + ctx context.Context + logger log.ContextLogger + dnsLogger log.ContextLogger + inboundManager adapter.InboundManager + outboundManager adapter.OutboundManager + networkManager adapter.NetworkManager + rules []adapter.Rule + needGeoIPDatabase bool + needGeositeDatabase bool + geoIPOptions option.GeoIPOptions + geositeOptions option.GeositeOptions + geoIPReader *geoip.Reader + geositeReader *geosite.Reader + geositeCache map[string]adapter.Rule + needFindProcess bool + dnsClient *dns.Client + defaultDomainStrategy dns.DomainStrategy + dnsRules []adapter.DNSRule + ruleSets []adapter.RuleSet + ruleSetMap map[string]adapter.RuleSet + defaultTransport dns.Transport + transports []dns.Transport + transportMap map[string]dns.Transport + transportDomainStrategy map[dns.Transport]dns.DomainStrategy + dnsReverseMapping *DNSReverseMapping + fakeIPStore adapter.FakeIPStore + processSearcher process.Searcher + timeService *ntp.Service + pauseManager pause.Manager + clashServer adapter.ClashServer + v2rayServer adapter.V2RayServer + platformInterface platform.Interface + needWIFIState bool + started bool +} + +func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions, ntpOptions option.NTPOptions) (*Router, error) { router := &Router{ ctx: ctx, logger: logFactory.NewLogger("router"), dnsLogger: logFactory.NewLogger("dns"), - outboundByTag: make(map[string]adapter.Outbound), + inboundManager: service.FromContext[adapter.InboundManager](ctx), + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + networkManager: service.FromContext[adapter.NetworkManager](ctx), rules: make([]adapter.Rule, 0, len(options.Rules)), dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)), ruleSetMap: make(map[string]adapter.RuleSet), @@ -114,22 +89,12 @@ func NewRouter( geositeOptions: common.PtrValueOrDefault(options.Geosite), geositeCache: make(map[string]adapter.Rule), needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, - defaultDetour: options.Final, defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy), - interfaceFinder: control.NewDefaultInterfaceFinder(), - autoDetectInterface: options.AutoDetectInterface, - defaultInterface: options.DefaultInterface, - defaultMark: options.DefaultMark, pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[platform.Interface](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), - enforcePackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { - if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { - return true - } - return false - }), } + ctx = service.ContextWith[adapter.Router](ctx, router) router.dnsClient = dns.NewClient(dns.ClientOptions{ DisableCache: dnsOptions.DNSClientOptions.DisableCache, DisableExpire: dnsOptions.DNSClientOptions.DisableExpire, @@ -147,14 +112,14 @@ func NewRouter( Logger: router.dnsLogger, }) for i, ruleOptions := range options.Rules { - routeRule, err := R.NewRule(ctx, router, router.logger, ruleOptions, true) + routeRule, err := R.NewRule(ctx, router.logger, ruleOptions, true) if err != nil { return nil, E.Cause(err, "parse rule[", i, "]") } router.rules = append(router.rules, routeRule) } for i, dnsRuleOptions := range dnsOptions.Rules { - dnsRule, err := R.NewDNSRule(ctx, router, router.logger, dnsRuleOptions, true) + dnsRule, err := R.NewDNSRule(ctx, router.logger, dnsRuleOptions, true) if err != nil { return nil, E.Cause(err, "parse dns rule[", i, "]") } @@ -164,7 +129,7 @@ func NewRouter( if _, exists := router.ruleSetMap[ruleSetOptions.Tag]; exists { return nil, E.New("duplicate rule-set tag: ", ruleSetOptions.Tag) } - ruleSet, err := R.NewRuleSet(ctx, router, router.logger, ruleSetOptions) + ruleSet, err := R.NewRuleSet(ctx, router.logger, ruleSetOptions) if err != nil { return nil, E.Cause(err, "parse rule-set[", i, "]") } @@ -191,7 +156,6 @@ func NewRouter( transportTags[i] = tag transportTagMap[tag] = true } - ctx = service.ContextWith[adapter.Router](ctx, router) outboundManager := service.FromContext[adapter.OutboundManager](ctx) for { lastLen := len(dummyTransportMap) @@ -298,7 +262,7 @@ func NewRouter( Context: ctx, Name: "local", Address: "local", - Dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{})), + Dialer: common.Must1(dialer.NewDefault(router.networkManager, option.DialerOptions{})), }))) } defaultTransport = transports[0] @@ -327,41 +291,6 @@ func NewRouter( router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range) } - usePlatformDefaultInterfaceMonitor := router.platformInterface != nil && router.platformInterface.UsePlatformDefaultInterfaceMonitor() - enforceInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { - if httpMixedOptions, isHTTPMixed := inbound.Options.(*option.HTTPMixedInboundOptions); isHTTPMixed && httpMixedOptions.SetSystemProxy { - return true - } - if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { - return true - } - return false - }) - - if !usePlatformDefaultInterfaceMonitor { - networkMonitor, err := tun.NewNetworkUpdateMonitor(router.logger) - if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { - if err != nil { - return nil, err - } - router.networkMonitor = networkMonitor - interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ - InterfaceFinder: router.interfaceFinder, - OverrideAndroidVPN: options.OverrideAndroidVPN, - UnderNetworkExtension: router.platformInterface != nil && router.platformInterface.UnderNetworkExtension(), - }) - if err != nil { - return nil, E.New("auto_detect_interface unsupported on current platform") - } - interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) - router.interfaceMonitor = interfaceMonitor - } - } else { - interfaceMonitor := router.platformInterface.CreateDefaultInterfaceMonitor(router.logger) - interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) - router.interfaceMonitor = interfaceMonitor - } - if ntpOptions.Enabled { ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) if err != nil { @@ -381,33 +310,10 @@ func NewRouter( return router, nil } -func (r *Router) Outbounds() []adapter.Outbound { - if !r.started { - return nil - } - return r.outbounds -} - func (r *Router) Start(stage adapter.StartStage) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { case adapter.StartStateInitialize: - if r.interfaceMonitor != nil { - monitor.Start("initialize interface monitor") - err := r.interfaceMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } - if r.networkMonitor != nil { - monitor.Start("initialize network monitor") - err := r.networkMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } if r.fakeIPStore != nil { monitor.Start("initialize fakeip store") err := r.fakeIPStore.Start() @@ -454,49 +360,10 @@ func (r *Router) Start(stage adapter.StartStage) error { r.geositeReader = nil } - if runtime.GOOS == "windows" { - powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) - if err == nil { - r.powerListener = powerListener - } else { - r.logger.Warn("initialize power listener: ", err) - } - } - - if r.powerListener != nil { - monitor.Start("start power listener") - err := r.powerListener.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start power listener") - } - } - monitor.Start("initialize DNS client") r.dnsClient.Start() monitor.Finish() - if C.IsAndroid && r.platformInterface == nil { - monitor.Start("initialize package manager") - packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ - Callback: r, - Logger: r.logger, - }) - monitor.Finish() - if err != nil { - return E.Cause(err, "create package manager") - } - if r.enforcePackageManager { - monitor.Start("start package manager") - err = packageManager.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start package manager") - } - } - r.packageManager = packageManager - } - for i, rule := range r.dnsRules { monitor.Start("initialize DNS rule[", i, "]") err := rule.Start() @@ -549,26 +416,13 @@ func (r *Router) Start(stage adapter.StartStage) error { cacheContext.Close() } needFindProcess := r.needFindProcess - needWIFIState := r.needWIFIState for _, ruleSet := range r.ruleSets { metadata := ruleSet.Metadata() if metadata.ContainsProcessRule { needFindProcess = true } if metadata.ContainsWIFIRule { - needWIFIState = true - } - } - if C.IsAndroid && r.platformInterface == nil && !r.enforcePackageManager { - if needFindProcess { - monitor.Start("start package manager") - err := r.packageManager.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start package manager") - } - } else { - r.packageManager = nil + r.needWIFIState = true } } if needFindProcess { @@ -578,7 +432,7 @@ func (r *Router) Start(stage adapter.StartStage) error { monitor.Start("initialize process searcher") searcher, err := process.NewSearcher(process.Config{ Logger: r.logger, - PackageManager: r.packageManager, + PackageManager: r.networkManager.PackageManager(), }) monitor.Finish() if err != nil { @@ -590,15 +444,6 @@ func (r *Router) Start(stage adapter.StartStage) error { } } } - if needWIFIState && r.platformInterface != nil { - monitor.Start("initialize WIFI state") - r.needWIFIState = true - r.interfaceMonitor.RegisterCallback(func(_ int) { - r.updateWIFIState() - }) - r.updateWIFIState() - monitor.Finish() - } for i, rule := range r.rules { monitor.Start("initialize rule[", i, "]") err := rule.Start() @@ -657,34 +502,6 @@ func (r *Router) Close() error { }) monitor.Finish() } - if r.interfaceMonitor != nil { - monitor.Start("close interface monitor") - err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { - return E.Cause(err, "close interface monitor") - }) - monitor.Finish() - } - if r.networkMonitor != nil { - monitor.Start("close network monitor") - err = E.Append(err, r.networkMonitor.Close(), func(err error) error { - return E.Cause(err, "close network monitor") - }) - monitor.Finish() - } - if r.packageManager != nil { - monitor.Start("close package manager") - err = E.Append(err, r.packageManager.Close(), func(err error) error { - return E.Cause(err, "close package manager") - }) - monitor.Finish() - } - if r.powerListener != nil { - monitor.Start("close power listener") - err = E.Append(err, r.powerListener.Close(), func(err error) error { - return E.Cause(err, "close power listener") - }) - monitor.Finish() - } if r.timeService != nil { monitor.Start("close time service") err = E.Append(err, r.timeService.Close(), func(err error) error { @@ -702,25 +519,6 @@ func (r *Router) Close() error { return err } -func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { - outbound, loaded := r.outboundByTag[tag] - return outbound, loaded -} - -func (r *Router) DefaultOutbound(network string) (adapter.Outbound, error) { - if network == N.NetworkTCP { - if r.defaultOutboundForConnection == nil { - return nil, E.New("missing default outbound for TCP connections") - } - return r.defaultOutboundForConnection, nil - } else { - if r.defaultOutboundForPacketConnection == nil { - return nil, E.New("missing default outbound for UDP connections") - } - return r.defaultOutboundForPacketConnection, nil - } -} - func (r *Router) FakeIPStore() adapter.FakeIPStore { return r.fakeIPStore } @@ -734,96 +532,10 @@ func (r *Router) NeedWIFIState() bool { return r.needWIFIState } -func (r *Router) InterfaceFinder() control.InterfaceFinder { - return r.interfaceFinder -} - -func (r *Router) UpdateInterfaces() error { - if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { - return r.interfaceFinder.Update() - } else { - interfaces, err := r.platformInterface.Interfaces() - if err != nil { - return err - } - r.interfaceFinder.UpdateInterfaces(interfaces) - return nil - } -} - -func (r *Router) AutoDetectInterface() bool { - return r.autoDetectInterface -} - -func (r *Router) AutoDetectInterfaceFunc() control.Func { - if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { - return func(network, address string, conn syscall.RawConn) error { - return control.Raw(conn, func(fd uintptr) error { - return r.platformInterface.AutoDetectInterfaceControl(int(fd)) - }) - } - } else { - if r.interfaceMonitor == nil { - return nil - } - return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int, err error) { - remoteAddr := M.ParseSocksaddr(address).Addr - if C.IsLinux { - interfaceName, interfaceIndex = r.InterfaceMonitor().DefaultInterface(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute - } - } else { - interfaceIndex = r.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute - } - } - return - }) - } -} - -func (r *Router) RegisterAutoRedirectOutputMark(mark uint32) error { - if r.autoRedirectOutputMark > 0 { - return E.New("only one auto-redirect can be configured") - } - r.autoRedirectOutputMark = mark - return nil -} - -func (r *Router) AutoRedirectOutputMark() uint32 { - return r.autoRedirectOutputMark -} - -func (r *Router) DefaultInterface() string { - return r.defaultInterface -} - -func (r *Router) DefaultMark() uint32 { - return r.defaultMark -} - func (r *Router) Rules() []adapter.Rule { return r.rules } -func (r *Router) WIFIState() adapter.WIFIState { - return r.wifiState -} - -func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor { - return r.networkMonitor -} - -func (r *Router) InterfaceMonitor() tun.DefaultInterfaceMonitor { - return r.interfaceMonitor -} - -func (r *Router) PackageManager() tun.PackageManager { - return r.packageManager -} - func (r *Router) ClashServer() adapter.ClashServer { return r.clashServer } @@ -840,10 +552,6 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) { r.v2rayServer = server } -func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) { - r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") -} - func (r *Router) NewError(ctx context.Context, err error) { common.Close(err) if E.IsClosedOrCanceled(err) { @@ -853,75 +561,9 @@ func (r *Router) NewError(ctx context.Context, err error) { r.logger.ErrorContext(ctx, err) } -func (r *Router) notifyNetworkUpdate(event int) { - if event == tun.EventNoRoute { - r.pauseManager.NetworkPause() - r.logger.Error("missing default interface") - } else { - r.pauseManager.NetworkWake() - if C.IsAndroid && r.platformInterface == nil { - var vpnStatus string - if r.interfaceMonitor.AndroidVPNEnabled() { - vpnStatus = "enabled" - } else { - vpnStatus = "disabled" - } - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) - } else { - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) - } - } - - if !r.started { - return - } - - _ = r.ResetNetwork() -} - -func (r *Router) ResetNetwork() error { - conntrack.Close() - - for _, outbound := range r.outbounds { - listener, isListener := outbound.(adapter.InterfaceUpdateListener) - if isListener { - listener.InterfaceUpdated() - } - } - +func (r *Router) ResetNetwork() { + r.networkManager.ResetNetwork() for _, transport := range r.transports { transport.Reset() } - return nil -} - -func (r *Router) updateWIFIState() { - if r.platformInterface == nil { - return - } - state := r.platformInterface.ReadWIFIState() - if state != r.wifiState { - r.wifiState = state - if state.SSID == "" && state.BSSID == "" { - r.logger.Info("updated WIFI state: disconnected") - } else { - r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) - } - } -} - -func (r *Router) notifyWindowsPowerEvent(event int) { - switch event { - case winpowrprof.EVENT_SUSPEND: - r.pauseManager.DevicePause() - _ = r.ResetNetwork() - case winpowrprof.EVENT_RESUME: - if !r.pauseManager.IsDevicePaused() { - return - } - fallthrough - case winpowrprof.EVENT_RESUME_AUTOMATIC: - r.pauseManager.DeviceWake() - _ = r.ResetNetwork() - } } diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index a12c63ef85..33a8e16c35 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -9,9 +9,10 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" ) -func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) { +func NewRule(ctx context.Context, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { @@ -23,7 +24,7 @@ func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogge return nil, E.New("missing outbound field") } } - return NewDefaultRule(ctx, router, logger, options.DefaultOptions) + return NewDefaultRule(ctx, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") @@ -34,7 +35,7 @@ func NewRule(ctx context.Context, router adapter.Router, logger log.ContextLogge return nil, E.New("missing outbound field") } } - return NewLogicalRule(ctx, router, logger, options.LogicalOptions) + return NewLogicalRule(ctx, logger, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -51,7 +52,7 @@ type RuleItem interface { String() string } -func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { +func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { action, err := NewRuleAction(ctx, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") @@ -62,6 +63,8 @@ func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.Conte action: action, }, } + router := service.FromContext[adapter.Router](ctx) + networkManager := service.FromContext[adapter.NetworkManager](ctx) if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) rule.items = append(rule.items, item) @@ -221,12 +224,12 @@ func NewDefaultRule(ctx context.Context, router adapter.Router, logger log.Conte rule.allItems = append(rule.allItems, item) } if len(options.WIFISSID) > 0 { - item := NewWIFISSIDItem(router, options.WIFISSID) + item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } if len(options.WIFIBSSID) > 0 { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } @@ -253,7 +256,7 @@ type LogicalRule struct { abstractLogicalRule } -func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { +func NewLogicalRule(ctx context.Context, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { action, err := NewRuleAction(ctx, logger, options.RuleAction) if err != nil { return nil, E.Cause(err, "action") @@ -274,7 +277,7 @@ func NewLogicalRule(ctx context.Context, router adapter.Router, logger log.Conte return nil, E.New("unknown logical mode: ", options.Mode) } for i, subOptions := range options.Rules { - subRule, err := NewRule(ctx, router, logger, subOptions, false) + subRule, err := NewRule(ctx, logger, subOptions, false) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 2218f6a30c..df5f3f335c 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -10,9 +10,10 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" ) -func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) { +func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { @@ -24,7 +25,7 @@ func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLo return nil, E.New("missing server field") } } - return NewDefaultDNSRule(ctx, router, logger, options.DefaultOptions) + return NewDefaultDNSRule(ctx, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") @@ -35,7 +36,7 @@ func NewDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLo return nil, E.New("missing server field") } } - return NewLogicalDNSRule(ctx, router, logger, options.LogicalOptions) + return NewLogicalDNSRule(ctx, logger, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -47,7 +48,7 @@ type DefaultDNSRule struct { abstractDefaultRule } -func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { +func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { rule := &DefaultDNSRule{ abstractDefaultRule: abstractDefaultRule{ invert: options.Invert, @@ -59,6 +60,8 @@ func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.Co rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + router := service.FromContext[adapter.Router](ctx) + networkManager := service.FromContext[adapter.NetworkManager](ctx) if options.IPVersion > 0 { switch options.IPVersion { case 4, 6: @@ -218,12 +221,12 @@ func NewDefaultDNSRule(ctx context.Context, router adapter.Router, logger log.Co rule.allItems = append(rule.allItems, item) } if len(options.WIFISSID) > 0 { - item := NewWIFISSIDItem(router, options.WIFISSID) + item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } if len(options.WIFIBSSID) > 0 { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } @@ -282,7 +285,7 @@ type LogicalDNSRule struct { abstractLogicalRule } -func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { +func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { r := &LogicalDNSRule{ abstractLogicalRule: abstractLogicalRule{ rules: make([]adapter.HeadlessRule, len(options.Rules)), @@ -299,7 +302,7 @@ func NewLogicalDNSRule(ctx context.Context, router adapter.Router, logger log.Co return nil, E.New("unknown logical mode: ", options.Mode) } for i, subRule := range options.Rules { - rule, err := NewDNSRule(ctx, router, logger, subRule, false) + rule, err := NewDNSRule(ctx, logger, subRule, false) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index 9ea357af8c..99488b2049 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -1,24 +1,27 @@ package rule import ( + "context" + "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" ) -func NewHeadlessRule(router adapter.Router, options option.HeadlessRule) (adapter.HeadlessRule, error) { +func NewHeadlessRule(ctx context.Context, options option.HeadlessRule) (adapter.HeadlessRule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { return nil, E.New("missing conditions") } - return NewDefaultHeadlessRule(router, options.DefaultOptions) + return NewDefaultHeadlessRule(ctx, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") } - return NewLogicalHeadlessRule(router, options.LogicalOptions) + return NewLogicalHeadlessRule(ctx, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -30,7 +33,8 @@ type DefaultHeadlessRule struct { abstractDefaultRule } -func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) { +func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) { + networkManager := service.FromContext[adapter.NetworkManager](ctx) rule := &DefaultHeadlessRule{ abstractDefaultRule{ invert: options.Invert, @@ -137,15 +141,15 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles rule.allItems = append(rule.allItems, item) } if len(options.WIFISSID) > 0 { - if router != nil { - item := NewWIFISSIDItem(router, options.WIFISSID) + if networkManager != nil { + item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } } if len(options.WIFIBSSID) > 0 { - if router != nil { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + if networkManager != nil { + item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } @@ -168,7 +172,7 @@ type LogicalHeadlessRule struct { abstractLogicalRule } -func NewLogicalHeadlessRule(router adapter.Router, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) { +func NewLogicalHeadlessRule(ctx context.Context, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) { r := &LogicalHeadlessRule{ abstractLogicalRule{ rules: make([]adapter.HeadlessRule, len(options.Rules)), @@ -184,7 +188,7 @@ func NewLogicalHeadlessRule(router adapter.Router, options option.LogicalHeadles return nil, E.New("unknown logical mode: ", options.Mode) } for i, subRule := range options.Rules { - rule, err := NewHeadlessRule(router, subRule) + rule, err := NewHeadlessRule(ctx, subRule) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } diff --git a/route/rule/rule_item_wifi_bssid.go b/route/rule/rule_item_wifi_bssid.go index ae94bd6df2..8f887322e5 100644 --- a/route/rule/rule_item_wifi_bssid.go +++ b/route/rule/rule_item_wifi_bssid.go @@ -10,12 +10,12 @@ import ( var _ RuleItem = (*WIFIBSSIDItem)(nil) type WIFIBSSIDItem struct { - bssidList []string - bssidMap map[string]bool - router adapter.Router + bssidList []string + bssidMap map[string]bool + networkManager adapter.NetworkManager } -func NewWIFIBSSIDItem(router adapter.Router, bssidList []string) *WIFIBSSIDItem { +func NewWIFIBSSIDItem(networkManager adapter.NetworkManager, bssidList []string) *WIFIBSSIDItem { bssidMap := make(map[string]bool) for _, bssid := range bssidList { bssidMap[bssid] = true @@ -23,12 +23,12 @@ func NewWIFIBSSIDItem(router adapter.Router, bssidList []string) *WIFIBSSIDItem return &WIFIBSSIDItem{ bssidList, bssidMap, - router, + networkManager, } } func (r *WIFIBSSIDItem) Match(metadata *adapter.InboundContext) bool { - return r.bssidMap[r.router.WIFIState().BSSID] + return r.bssidMap[r.networkManager.WIFIState().BSSID] } func (r *WIFIBSSIDItem) String() string { diff --git a/route/rule/rule_item_wifi_ssid.go b/route/rule/rule_item_wifi_ssid.go index 3a928f77e5..ab9fdd884c 100644 --- a/route/rule/rule_item_wifi_ssid.go +++ b/route/rule/rule_item_wifi_ssid.go @@ -10,12 +10,12 @@ import ( var _ RuleItem = (*WIFISSIDItem)(nil) type WIFISSIDItem struct { - ssidList []string - ssidMap map[string]bool - router adapter.Router + ssidList []string + ssidMap map[string]bool + networkManager adapter.NetworkManager } -func NewWIFISSIDItem(router adapter.Router, ssidList []string) *WIFISSIDItem { +func NewWIFISSIDItem(networkManager adapter.NetworkManager, ssidList []string) *WIFISSIDItem { ssidMap := make(map[string]bool) for _, ssid := range ssidList { ssidMap[ssid] = true @@ -23,12 +23,12 @@ func NewWIFISSIDItem(router adapter.Router, ssidList []string) *WIFISSIDItem { return &WIFISSIDItem{ ssidList, ssidMap, - router, + networkManager, } } func (r *WIFISSIDItem) Match(metadata *adapter.InboundContext) bool { - return r.ssidMap[r.router.WIFIState().SSID] + return r.ssidMap[r.networkManager.WIFIState().SSID] } func (r *WIFISSIDItem) String() string { diff --git a/route/rule/rule_set.go b/route/rule/rule_set.go index be9821c0c3..5e639a47f9 100644 --- a/route/rule/rule_set.go +++ b/route/rule/rule_set.go @@ -13,12 +13,12 @@ import ( "go4.org/netipx" ) -func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) { +func NewRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) { switch options.Type { case C.RuleSetTypeInline, C.RuleSetTypeLocal, "": - return NewLocalRuleSet(ctx, router, logger, options) + return NewLocalRuleSet(ctx, logger, options) case C.RuleSetTypeRemote: - return NewRemoteRuleSet(ctx, router, logger, options), nil + return NewRemoteRuleSet(ctx, logger, options), nil default: return nil, E.New("unknown rule-set type: ", options.Type) } diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index 9186454e68..efbc525ee6 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -26,7 +26,7 @@ import ( var _ adapter.RuleSet = (*LocalRuleSet)(nil) type LocalRuleSet struct { - router adapter.Router + ctx context.Context logger logger.Logger tag string rules []adapter.HeadlessRule @@ -36,9 +36,9 @@ type LocalRuleSet struct { refs atomic.Int32 } -func NewLocalRuleSet(ctx context.Context, router adapter.Router, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { +func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { ruleSet := &LocalRuleSet{ - router: router, + ctx: ctx, logger: logger, tag: options.Tag, fileFormat: options.Format, @@ -130,7 +130,7 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error { rules := make([]adapter.HeadlessRule, len(headlessRules)) var err error for i, ruleOptions := range headlessRules { - rules[i], err = NewHeadlessRule(s.router, ruleOptions) + rules[i], err = NewHeadlessRule(s.ctx, ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index fb58169140..830e19f7cf 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -35,7 +35,6 @@ var _ adapter.RuleSet = (*RemoteRuleSet)(nil) type RemoteRuleSet struct { ctx context.Context cancel context.CancelFunc - router adapter.Router outboundManager adapter.OutboundManager logger logger.ContextLogger options option.RuleSet @@ -53,7 +52,7 @@ type RemoteRuleSet struct { refs atomic.Int32 } -func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { +func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { ctx, cancel := context.WithCancel(ctx) var updateInterval time.Duration if options.RemoteOptions.UpdateInterval > 0 { @@ -64,7 +63,6 @@ func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger. return &RemoteRuleSet{ ctx: ctx, cancel: cancel, - router: router, outboundManager: service.FromContext[adapter.OutboundManager](ctx), logger: logger, options: options, @@ -180,7 +178,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { } rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) for i, ruleOptions := range plainRuleSet.Rules { - rules[i], err = NewHeadlessRule(s.router, ruleOptions) + rules[i], err = NewHeadlessRule(s.ctx, ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index d5603e9ed5..dfe33d86c2 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -38,6 +38,7 @@ func init() { type Transport struct { options dns.TransportOptions router adapter.Router + networkManager adapter.NetworkManager interfaceName string autoInterface bool interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] @@ -54,15 +55,11 @@ func NewTransport(options dns.TransportOptions) (*Transport, error) { if linkURL.Host == "" { return nil, E.New("missing interface name for DHCP") } - router := service.FromContext[adapter.Router](options.Context) - if router == nil { - return nil, E.New("missing router in context") - } transport := &Transport{ - options: options, - router: router, - interfaceName: linkURL.Host, - autoInterface: linkURL.Host == "auto", + options: options, + networkManager: service.FromContext[adapter.NetworkManager](options.Context), + interfaceName: linkURL.Host, + autoInterface: linkURL.Host == "auto", } return transport, nil } @@ -77,7 +74,7 @@ func (t *Transport) Start() error { return err } if t.autoInterface { - t.interfaceCallback = t.router.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) + t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) } return nil } @@ -93,7 +90,7 @@ func (t *Transport) Close() error { transport.Close() } if t.interfaceCallback != nil { - t.router.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) + t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) } return nil } @@ -125,10 +122,10 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, func (t *Transport) fetchInterface() (*net.Interface, error) { interfaceName := t.interfaceName if t.autoInterface { - if t.router.InterfaceMonitor() == nil { + if t.networkManager.InterfaceMonitor() == nil { return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface") } - interfaceName = t.router.InterfaceMonitor().DefaultInterfaceName(netip.Addr{}) + interfaceName = t.networkManager.InterfaceMonitor().DefaultInterfaceName(netip.Addr{}) } if interfaceName == "" { return nil, E.New("missing default interface") @@ -177,7 +174,7 @@ func (t *Transport) interfaceUpdated(int) { func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error { var listener net.ListenConfig - listener.Control = control.Append(listener.Control, control.BindToInterface(t.router.InterfaceFinder(), iface.Name, iface.Index)) + listener.Control = control.Append(listener.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), iface.Name, iface.Index)) listener.Control = control.Append(listener.Control, control.ReuseAddr()) listenAddr := "0.0.0.0:68" if runtime.GOOS == "linux" || runtime.GOOS == "android" { @@ -255,7 +252,7 @@ func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Ad return it.String() }), ","), "]") } - serverDialer := common.Must1(dialer.NewDefault(t.router, option.DialerOptions{ + serverDialer := common.Must1(dialer.NewDefault(t.networkManager, option.DialerOptions{ BindInterface: iface.Name, UDPFragmentDefault: true, })) diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index 36c270f9d3..8a54a75ef9 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -34,7 +34,7 @@ type SystemDevice struct { closeOnce sync.Once } -func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes []netip.Prefix, mtu uint32, gso bool) (*SystemDevice, error) { +func NewSystemDevice(networkManager adapter.NetworkManager, interfaceName string, localPrefixes []netip.Prefix, mtu uint32, gso bool) (*SystemDevice, error) { var inet4Addresses []netip.Prefix var inet6Addresses []netip.Prefix for _, prefixes := range localPrefixes { @@ -49,7 +49,7 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes } return &SystemDevice{ - dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{ + dialer: common.Must1(dialer.NewDefault(networkManager, option.DialerOptions{ BindInterface: interfaceName, })), name: interfaceName, From 9afe75586a22c909b16a67edff3d0e6439163b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 10 Nov 2024 16:46:59 +0800 Subject: [PATCH 0164/1272] refactor: Extract services form router --- adapter/experimental.go | 30 +-- adapter/inbound.go | 2 +- adapter/inbound/manager.go | 4 +- adapter/lifecycle.go | 35 ++- adapter/lifecycle_legacy.go | 34 ++- adapter/network.go | 2 +- adapter/outbound.go | 2 +- adapter/outbound/manager.go | 4 +- adapter/router.go | 13 +- box.go | 240 +++++++----------- experimental/cachefile/cache.go | 21 +- experimental/clashapi/server.go | 81 +++--- .../clashapi/trafficontrol/tracker.go | 34 +-- experimental/libbox/command_clash_mode.go | 14 +- .../libbox/command_close_connection.go | 6 +- experimental/libbox/command_connections.go | 6 +- experimental/libbox/command_server.go | 2 +- experimental/libbox/command_status.go | 10 +- experimental/libbox/service.go | 8 +- experimental/v2rayapi/server.go | 11 +- experimental/v2rayapi/stats.go | 12 +- protocol/group/urltest.go | 29 +-- route/route.go | 77 ++---- route/router.go | 66 +---- route/rule/rule_default.go | 2 +- route/rule/rule_dns.go | 2 +- route/rule/rule_item_clash_mode.go | 23 +- 27 files changed, 310 insertions(+), 460 deletions(-) diff --git a/adapter/experimental.go b/adapter/experimental.go index bee24c4f8a..f22ff9b22a 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -4,28 +4,28 @@ import ( "bytes" "context" "encoding/binary" - "net" "time" "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-dns" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/varbin" ) type ClashServer interface { - Service - LegacyPreStarter + LifecycleService + ConnectionTracker Mode() string ModeList() []string HistoryStorage() *urltest.HistoryStorage - RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker) - RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker) +} + +type V2RayServer interface { + LifecycleService + StatsService() ConnectionTracker } type CacheFile interface { - Service - LegacyPreStarter + LifecycleService StoreFakeIP() bool FakeIPStorage @@ -94,10 +94,6 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error { return nil } -type Tracker interface { - Leave() -} - type OutboundGroup interface { Outbound Now() string @@ -115,13 +111,3 @@ func OutboundTag(detour Outbound) string { } return detour.Tag() } - -type V2RayServer interface { - Service - StatsService() V2RayStatsService -} - -type V2RayStatsService interface { - RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn - RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn -} diff --git a/adapter/inbound.go b/adapter/inbound.go index d80e59f7b9..7932237d03 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -32,7 +32,7 @@ type InboundRegistry interface { } type InboundManager interface { - NewService + Lifecycle Inbounds() []Inbound Get(tag string) (Inbound, bool) Remove(tag string) error diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go index 69a3ad46b0..d2be0f365e 100644 --- a/adapter/inbound/manager.go +++ b/adapter/inbound/manager.go @@ -44,7 +44,7 @@ func (m *Manager) Start(stage adapter.StartStage) error { for _, inbound := range m.inbounds { err := adapter.LegacyStart(inbound, stage) if err != nil { - return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]") } } return nil @@ -118,7 +118,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log. for _, stage := range adapter.ListStartStages { err = adapter.LegacyStart(inbound, stage) if err != nil { - return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]") } } } diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go index 85de425d5a..aff9fadbac 100644 --- a/adapter/lifecycle.go +++ b/adapter/lifecycle.go @@ -1,5 +1,7 @@ package adapter +import E "github.com/sagernet/sing/common/exceptions" + type StartStage uint8 const ( @@ -16,7 +18,7 @@ var ListStartStages = []StartStage{ StartStateStarted, } -func (s StartStage) Action() string { +func (s StartStage) String() string { switch s { case StartStateInitialize: return "initialize" @@ -25,17 +27,38 @@ func (s StartStage) Action() string { case StartStatePostStart: return "post-start" case StartStateStarted: - return "start-after-started" + return "finish-start" default: panic("unknown stage") } } -type NewService interface { - NewStarter +type Lifecycle interface { + Start(stage StartStage) error Close() error } -type NewStarter interface { - Start(stage StartStage) error +type LifecycleService interface { + Name() string + Lifecycle +} + +func Start(stage StartStage, services ...Lifecycle) error { + for _, service := range services { + err := service.Start(stage) + if err != nil { + return err + } + } + return nil +} + +func StartNamed(stage StartStage, services []LifecycleService) error { + for _, service := range services { + err := service.Start(stage) + if err != nil { + return E.Cause(err, stage.String(), " ", service.Name()) + } + } + return nil } diff --git a/adapter/lifecycle_legacy.go b/adapter/lifecycle_legacy.go index 5968131bb4..0c8c75daed 100644 --- a/adapter/lifecycle_legacy.go +++ b/adapter/lifecycle_legacy.go @@ -1,13 +1,5 @@ package adapter -type LegacyPreStarter interface { - PreStart() error -} - -type LegacyPostStarter interface { - PostStart() error -} - func LegacyStart(starter any, stage StartStage) error { switch stage { case StartStateInitialize: @@ -22,7 +14,7 @@ func LegacyStart(starter any, stage StartStage) error { }); isStarter { return starter.Start() } - case StartStatePostStart: + case StartStateStarted: if postStarter, isPostStarter := starter.(interface { PostStart() error }); isPostStarter { @@ -31,3 +23,27 @@ func LegacyStart(starter any, stage StartStage) error { } return nil } + +type lifecycleServiceWrapper struct { + Service + name string +} + +func NewLifecycleService(service Service, name string) LifecycleService { + return &lifecycleServiceWrapper{ + Service: service, + name: name, + } +} + +func (l *lifecycleServiceWrapper) Name() string { + return l.name +} + +func (l *lifecycleServiceWrapper) Start(stage StartStage) error { + return LegacyStart(l.Service, stage) +} + +func (l *lifecycleServiceWrapper) Close() error { + return l.Service.Close() +} diff --git a/adapter/network.go b/adapter/network.go index 0ce2741185..533bfced22 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -6,7 +6,7 @@ import ( ) type NetworkManager interface { - NewService + Lifecycle InterfaceFinder() control.InterfaceFinder UpdateInterfaces() error DefaultInterface() string diff --git a/adapter/outbound.go b/adapter/outbound.go index b170398a96..2c2b109100 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -24,7 +24,7 @@ type OutboundRegistry interface { } type OutboundManager interface { - NewService + Lifecycle Outbounds() []Outbound Outbound(tag string) (Outbound, bool) Default() Outbound diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index 10a89a1c9b..84a105c59e 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -61,7 +61,7 @@ func (m *Manager) Start(stage adapter.StartStage) error { for _, outbound := range outbounds { err := adapter.LegacyStart(outbound, stage) if err != nil { - return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]") } } } @@ -234,7 +234,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log. for _, stage := range adapter.ListStartStages { err = adapter.LegacyStart(outbound, stage) if err != nil { - return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]") } } } diff --git a/adapter/router.go b/adapter/router.go index 40a461a79e..6dd39357b4 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -19,7 +19,7 @@ import ( ) type Router interface { - NewService + Lifecycle FakeIPStore() FakeIPStore @@ -38,15 +38,16 @@ type Router interface { ClearDNSCache() Rules() []Rule - ClashServer() ClashServer - SetClashServer(server ClashServer) - - V2RayServer() V2RayServer - SetV2RayServer(server V2RayServer) + SetTracker(tracker ConnectionTracker) ResetNetwork() } +type ConnectionTracker interface { + RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn + RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn +} + // Deprecated: Use ConnectionRouterEx instead. type ConnectionRouter interface { RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error diff --git a/box.go b/box.go index 8eac0dfa6c..3b69617f64 100644 --- a/box.go +++ b/box.go @@ -11,6 +11,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental" @@ -23,6 +24,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) @@ -30,17 +32,15 @@ import ( var _ adapter.Service = (*Box)(nil) type Box struct { - createdAt time.Time - router adapter.Router - inbound *inbound.Manager - outbound *outbound.Manager - network *route.NetworkManager - logFactory log.Factory - logger log.ContextLogger - preServices1 map[string]adapter.Service - preServices2 map[string]adapter.Service - postServices map[string]adapter.Service - done chan struct{} + createdAt time.Time + logFactory log.Factory + logger log.ContextLogger + network *route.NetworkManager + router *route.Router + inbound *inbound.Manager + outbound *outbound.Manager + services []adapter.LifecycleService + done chan struct{} } type Options struct { @@ -49,7 +49,11 @@ type Options struct { PlatformLogWriter log.PlatformWriter } -func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context { +func Context( + ctx context.Context, + inboundRegistry adapter.InboundRegistry, + outboundRegistry adapter.OutboundRegistry, +) context.Context { if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || service.FromContext[adapter.InboundRegistry](ctx) == nil { ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry) @@ -70,14 +74,17 @@ func New(options Options) (*Box, error) { ctx = context.Background() } ctx = service.ContextWithDefaultRegistry(ctx) + inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) if inboundRegistry == nil { return nil, E.New("missing inbound registry in context") } + outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) if outboundRegistry == nil { return nil, E.New("missing outbound registry in context") } + ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) @@ -109,17 +116,19 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "create log factory") } + routeOptions := common.PtrValueOrDefault(options.Route) inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry) outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final) - ctx = service.ContextWith[adapter.InboundManager](ctx, inboundManager) - ctx = service.ContextWith[adapter.OutboundManager](ctx, outboundManager) + service.MustRegister[adapter.InboundManager](ctx, inboundManager) + service.MustRegister[adapter.OutboundManager](ctx, outboundManager) + networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions) if err != nil { return nil, E.Cause(err, "initialize network manager") } - ctx = service.ContextWith[adapter.NetworkManager](ctx, networkManager) - router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP)) + service.MustRegister[adapter.NetworkManager](ctx, networkManager) + router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS)) if err != nil { return nil, E.Cause(err, "initialize router") } @@ -182,47 +191,61 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "initialize platform interface") } } - preServices1 := make(map[string]adapter.Service) - preServices2 := make(map[string]adapter.Service) - postServices := make(map[string]adapter.Service) + var services []adapter.LifecycleService if needCacheFile { - cacheFile := service.FromContext[adapter.CacheFile](ctx) - if cacheFile == nil { - cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile)) - service.MustRegister[adapter.CacheFile](ctx, cacheFile) - } - preServices1["cache file"] = cacheFile + cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile)) + service.MustRegister[adapter.CacheFile](ctx, cacheFile) + services = append(services, cacheFile) } if needClashAPI { clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions) if err != nil { - return nil, E.Cause(err, "create clash api server") + return nil, E.Cause(err, "create clash-server") } - router.SetClashServer(clashServer) - preServices2["clash api"] = clashServer + router.SetTracker(clashServer) + service.MustRegister[adapter.ClashServer](ctx, clashServer) + services = append(services, clashServer) } if needV2RayAPI { v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) if err != nil { - return nil, E.Cause(err, "create v2ray api server") + return nil, E.Cause(err, "create v2ray-server") + } + if v2rayServer.StatsService() != nil { + router.SetTracker(v2rayServer.StatsService()) + services = append(services, v2rayServer) + service.MustRegister[adapter.V2RayServer](ctx, v2rayServer) + } + } + ntpOptions := common.PtrValueOrDefault(options.NTP) + if ntpOptions.Enabled { + ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) + if err != nil { + return nil, E.Cause(err, "create NTP service") } - router.SetV2RayServer(v2rayServer) - preServices2["v2ray api"] = v2rayServer + timeService := ntp.NewService(ntp.Options{ + Context: ctx, + Dialer: ntpDialer, + Logger: logFactory.NewLogger("ntp"), + Server: ntpOptions.ServerOptions.Build(), + Interval: time.Duration(ntpOptions.Interval), + WriteToSystem: ntpOptions.WriteToSystem, + }) + service.MustRegister[ntp.TimeService](ctx, timeService) + services = append(services, adapter.NewLifecycleService(timeService, "ntp service")) } return &Box{ - router: router, - inbound: inboundManager, - outbound: outboundManager, - network: networkManager, - createdAt: createdAt, - logFactory: logFactory, - logger: logFactory.Logger(), - preServices1: preServices1, - preServices2: preServices2, - postServices: postServices, - done: make(chan struct{}), + network: networkManager, + router: router, + inbound: inboundManager, + outbound: outboundManager, + createdAt: createdAt, + logFactory: logFactory, + logger: logFactory.Logger(), + services: services, + done: make(chan struct{}), }, nil } @@ -272,43 +295,19 @@ func (s *Box) preStart() error { if err != nil { return E.Cause(err, "start logger") } - for serviceName, service := range s.preServices1 { - if preService, isPreService := service.(adapter.LegacyPreStarter); isPreService { - monitor.Start("pre-start ", serviceName) - err := preService.PreStart() - monitor.Finish() - if err != nil { - return E.Cause(err, "pre-start ", serviceName) - } - } - } - for serviceName, service := range s.preServices2 { - if preService, isPreService := service.(adapter.LegacyPreStarter); isPreService { - monitor.Start("pre-start ", serviceName) - err := preService.PreStart() - monitor.Finish() - if err != nil { - return E.Cause(err, "pre-start ", serviceName) - } - } - } - err = s.network.Start(adapter.StartStateInitialize) - if err != nil { - return E.Cause(err, "initialize network manager") - } - err = s.router.Start(adapter.StartStateInitialize) + err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api if err != nil { - return E.Cause(err, "initialize router") + return err } - err = s.outbound.Start(adapter.StartStateStart) + err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound) if err != nil { return err } - err = s.network.Start(adapter.StartStateStart) + err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router) if err != nil { return err } - return s.router.Start(adapter.StartStateStart) + return nil } func (s *Box) start() error { @@ -316,57 +315,27 @@ func (s *Box) start() error { if err != nil { return err } - for serviceName, service := range s.preServices1 { - err = service.Start() - if err != nil { - return E.Cause(err, "start ", serviceName) - } - } - for serviceName, service := range s.preServices2 { - err = service.Start() - if err != nil { - return E.Cause(err, "start ", serviceName) - } - } - err = s.inbound.Start(adapter.StartStateStart) + err = adapter.StartNamed(adapter.StartStateStart, s.services) if err != nil { return err } - for serviceName, service := range s.postServices { - err := service.Start() - if err != nil { - return E.Cause(err, "start ", serviceName) - } - } - err = s.outbound.Start(adapter.StartStatePostStart) - if err != nil { - return err - } - err = s.network.Start(adapter.StartStatePostStart) - if err != nil { - return err - } - err = s.router.Start(adapter.StartStatePostStart) - if err != nil { - return err - } - err = s.inbound.Start(adapter.StartStatePostStart) + err = s.inbound.Start(adapter.StartStateStart) if err != nil { return err } - err = s.network.Start(adapter.StartStateStarted) + err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound) if err != nil { return err } - err = s.router.Start(adapter.StartStateStarted) + err = adapter.StartNamed(adapter.StartStatePostStart, s.services) if err != nil { return err } - err = s.outbound.Start(adapter.StartStateStarted) + err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound) if err != nil { return err } - err = s.inbound.Start(adapter.StartStateStarted) + err = adapter.StartNamed(adapter.StartStateStarted, s.services) if err != nil { return err } @@ -380,47 +349,18 @@ func (s *Box) Close() error { default: close(s.done) } - monitor := taskmonitor.New(s.logger, C.StopTimeout) - var errors error - for serviceName, service := range s.postServices { - monitor.Start("close ", serviceName) - errors = E.Append(errors, service.Close(), func(err error) error { - return E.Cause(err, "close ", serviceName) - }) - monitor.Finish() - } - errors = E.Errors(errors, s.inbound.Close()) - errors = E.Errors(errors, s.outbound.Close()) - errors = E.Errors(errors, s.network.Close()) - errors = E.Errors(errors, s.router.Close()) - for serviceName, service := range s.preServices1 { - monitor.Start("close ", serviceName) - errors = E.Append(errors, service.Close(), func(err error) error { - return E.Cause(err, "close ", serviceName) - }) - monitor.Finish() - } - for serviceName, service := range s.preServices2 { - monitor.Start("close ", serviceName) - errors = E.Append(errors, service.Close(), func(err error) error { - return E.Cause(err, "close ", serviceName) + err := common.Close( + s.inbound, s.outbound, s.router, s.network, + ) + for _, lifecycleService := range s.services { + err = E.Append(err, lifecycleService.Close(), func(err error) error { + return E.Cause(err, "close ", lifecycleService.Name()) }) - monitor.Finish() } - if err := common.Close(s.logFactory); err != nil { - errors = E.Append(errors, err, func(err error) error { - return E.Cause(err, "close logger") - }) - } - return errors -} - -func (s *Box) Inbound() adapter.InboundManager { - return s.inbound -} - -func (s *Box) Outbound() adapter.OutboundManager { - return s.outbound + err = E.Append(err, s.logFactory.Close(), func(err error) error { + return E.Cause(err, "close logger") + }) + return err } func (s *Box) Network() adapter.NetworkManager { @@ -430,3 +370,11 @@ func (s *Box) Network() adapter.NetworkManager { func (s *Box) Router() adapter.Router { return s.router } + +func (s *Box) Inbound() adapter.InboundManager { + return s.inbound +} + +func (s *Box) Outbound() adapter.OutboundManager { + return s.outbound +} diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 1027588fc9..498b9474fd 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -93,7 +93,18 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { } } -func (c *CacheFile) start() error { +func (c *CacheFile) Name() string { + return "cache-file" +} + +func (c *CacheFile) Dependencies() []string { + return nil +} + +func (c *CacheFile) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateInitialize { + return nil + } const fileMode = 0o666 options := bbolt.Options{Timeout: time.Second} var ( @@ -151,14 +162,6 @@ func (c *CacheFile) start() error { return nil } -func (c *CacheFile) PreStart() error { - return c.start() -} - -func (c *CacheFile) Start() error { - return nil -} - func (c *CacheFile) Close() error { if c.DB == nil { return nil diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 89f33de348..106bc73ff7 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -133,45 +133,50 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op return s, nil } -func (s *Server) PreStart() error { - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - mode := cacheFile.LoadMode() - if common.Any(s.modeList, func(it string) bool { - return strings.EqualFold(it, mode) - }) { - s.mode = mode - } - } - return nil +func (s *Server) Name() string { + return "clash server" } -func (s *Server) Start() error { - if s.externalController { - s.checkAndDownloadExternalUI() - var ( - listener net.Listener - err error - ) - for i := 0; i < 3; i++ { - listener, err = net.Listen("tcp", s.httpServer.Addr) - if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) { - time.Sleep(100 * time.Millisecond) - continue +func (s *Server) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + cacheFile := service.FromContext[adapter.CacheFile](s.ctx) + if cacheFile != nil { + mode := cacheFile.LoadMode() + if common.Any(s.modeList, func(it string) bool { + return strings.EqualFold(it, mode) + }) { + s.mode = mode } - break - } - if err != nil { - return E.Cause(err, "external controller listen error") } - s.logger.Info("restful api listening at ", listener.Addr()) - go func() { - err = s.httpServer.Serve(listener) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - s.logger.Error("external controller serve error: ", err) + case adapter.StartStateStarted: + if s.externalController { + s.checkAndDownloadExternalUI() + var ( + listener net.Listener + err error + ) + for i := 0; i < 3; i++ { + listener, err = net.Listen("tcp", s.httpServer.Addr) + if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) { + time.Sleep(100 * time.Millisecond) + continue + } + break + } + if err != nil { + return E.Cause(err, "external controller listen error") } - }() + s.logger.Info("restful api listening at ", listener.Addr()) + go func() { + err = s.httpServer.Serve(listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error("external controller serve error: ", err) + } + }() + } } + return nil } @@ -233,14 +238,12 @@ func (s *Server) TrafficManager() *trafficontrol.Manager { return s.trafficManager } -func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) { - tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule) - return tracker, tracker +func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn { + return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound) } -func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) { - tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule) - return tracker, tracker +func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn { + return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound) } func authentication(serverSecret string) func(next http.Handler) http.Handler { diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index df5437fafa..e324be206e 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -5,7 +5,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" @@ -88,7 +87,6 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) { } type Tracker interface { - adapter.Tracker Metadata() TrackerMetadata Close() error } @@ -108,10 +106,6 @@ func (tt *TCPConn) Close() error { return tt.ExtendedConn.Close() } -func (tt *TCPConn) Leave() { - tt.manager.Leave(tt) -} - func (tt *TCPConn) Upstream() any { return tt.ExtendedConn } @@ -124,7 +118,7 @@ func (tt *TCPConn) WriterReplaceable() bool { return true } -func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, rule adapter.Rule) *TCPConn { +func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *TCPConn { id, _ := uuid.NewV4() var ( chain []string @@ -132,12 +126,8 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont outbound string outboundType string ) - var action adapter.RuleAction - if rule != nil { - action = rule.Action() - } - if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { - next = routeAction.Outbound + if matchOutbound != nil { + next = matchOutbound.Tag() } else { next = outboundManager.Default().Tag() } @@ -172,7 +162,7 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont Upload: upload, Download: download, Chain: common.Reverse(chain), - Rule: rule, + Rule: matchRule, Outbound: outbound, OutboundType: outboundType, }, @@ -197,10 +187,6 @@ func (ut *UDPConn) Close() error { return ut.PacketConn.Close() } -func (ut *UDPConn) Leave() { - ut.manager.Leave(ut) -} - func (ut *UDPConn) Upstream() any { return ut.PacketConn } @@ -213,7 +199,7 @@ func (ut *UDPConn) WriterReplaceable() bool { return true } -func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, rule adapter.Rule) *UDPConn { +func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *UDPConn { id, _ := uuid.NewV4() var ( chain []string @@ -221,12 +207,8 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound outbound string outboundType string ) - var action adapter.RuleAction - if rule != nil { - action = rule.Action() - } - if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction { - next = routeAction.Outbound + if matchOutbound != nil { + next = matchOutbound.Tag() } else { next = outboundManager.Default().Tag() } @@ -261,7 +243,7 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound Upload: upload, Download: download, Chain: common.Reverse(chain), - Rule: rule, + Rule: matchRule, Outbound: outbound, OutboundType: outboundType, }, diff --git a/experimental/libbox/command_clash_mode.go b/experimental/libbox/command_clash_mode.go index 1b6eb47085..af69047fd3 100644 --- a/experimental/libbox/command_clash_mode.go +++ b/experimental/libbox/command_clash_mode.go @@ -38,11 +38,7 @@ func (s *CommandServer) handleSetClashMode(conn net.Conn) error { if service == nil { return writeError(conn, E.New("service not ready")) } - clashServer := service.instance.Router().ClashServer() - if clashServer == nil { - return writeError(conn, E.New("Clash API disabled")) - } - clashServer.(*clashapi.Server).SetMode(newMode) + service.clashServer.(*clashapi.Server).SetMode(newMode) return writeError(conn, nil) } @@ -69,18 +65,14 @@ func (s *CommandServer) handleModeConn(conn net.Conn) error { return ctx.Err() } } - clashServer := s.service.instance.Router().ClashServer() - if clashServer == nil { - return binary.Write(conn, binary.BigEndian, uint16(0)) - } - err := writeClashModeList(conn, clashServer) + err := writeClashModeList(conn, s.service.clashServer) if err != nil { return err } for { select { case <-s.modeUpdate: - err = varbin.Write(conn, binary.BigEndian, clashServer.Mode()) + err = varbin.Write(conn, binary.BigEndian, s.service.clashServer.Mode()) if err != nil { return err } diff --git a/experimental/libbox/command_close_connection.go b/experimental/libbox/command_close_connection.go index a2b05e5619..46f7023fa1 100644 --- a/experimental/libbox/command_close_connection.go +++ b/experimental/libbox/command_close_connection.go @@ -45,11 +45,7 @@ func (s *CommandServer) handleCloseConnection(conn net.Conn) error { if service == nil { return writeError(conn, E.New("service not ready")) } - clashServer := service.instance.Router().ClashServer() - if clashServer == nil { - return writeError(conn, E.New("Clash API disabled")) - } - targetConn := clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId)) + targetConn := service.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId)) if targetConn == nil { return writeError(conn, E.New("connection already closed")) } diff --git a/experimental/libbox/command_connections.go b/experimental/libbox/command_connections.go index b51c735249..39d9303cb5 100644 --- a/experimental/libbox/command_connections.go +++ b/experimental/libbox/command_connections.go @@ -49,11 +49,7 @@ func (s *CommandServer) handleConnectionsConn(conn net.Conn) error { for { service := s.service if service != nil { - clashServer := service.instance.Router().ClashServer() - if clashServer == nil { - return E.New("Clash API disabled") - } - trafficManager = clashServer.(*clashapi.Server).TrafficManager() + trafficManager = service.clashServer.(*clashapi.Server).TrafficManager() break } select { diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 26b4aa79b1..798a52bddf 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -60,7 +60,7 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ func (s *CommandServer) SetService(newService *BoxService) { if newService != nil { service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) - newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) + newService.clashServer.(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) } s.service = newService s.notifyURLTestUpdate() diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go index a6280d0f7c..f8709ef04e 100644 --- a/experimental/libbox/command_status.go +++ b/experimental/libbox/command_status.go @@ -31,12 +31,10 @@ func (s *CommandServer) readStatus() StatusMessage { message.ConnectionsOut = int32(conntrack.Count()) if s.service != nil { - if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil { - message.TrafficAvailable = true - trafficManager := clashServer.(*clashapi.Server).TrafficManager() - message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() - message.ConnectionsIn = int32(trafficManager.ConnectionsLen()) - } + message.TrafficAvailable = true + trafficManager := s.service.clashServer.(*clashapi.Server).TrafficManager() + message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() + message.ConnectionsIn = int32(trafficManager.ConnectionsLen()) } return message diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index e3d2f95474..9b7181c8b8 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -34,17 +34,18 @@ import ( type BoxService struct { ctx context.Context cancel context.CancelFunc + urlTestHistoryStorage *urltest.HistoryStorage instance *box.Box + clashServer adapter.ClashServer pauseManager pause.Manager - urlTestHistoryStorage *urltest.HistoryStorage servicePauseFields } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) - ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) + service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) options, err := parseConfig(ctx, configContent) if err != nil { return nil, err @@ -54,7 +55,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} - ctx = service.ContextWith[platform.Interface](ctx, platformWrapper) + service.MustRegister[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ Context: ctx, Options: options, @@ -71,6 +72,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box instance: instance, urlTestHistoryStorage: urlTestHistoryStorage, pauseManager: service.FromContext[pause.Manager](ctx), + clashServer: service.FromContext[adapter.ClashServer](ctx), }, nil } diff --git a/experimental/v2rayapi/server.go b/experimental/v2rayapi/server.go index 8b4b43855e..8ebae1c4f7 100644 --- a/experimental/v2rayapi/server.go +++ b/experimental/v2rayapi/server.go @@ -44,7 +44,14 @@ func NewServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2Ray return server, nil } -func (s *Server) Start() error { +func (s *Server) Name() string { + return "v2ray server" +} + +func (s *Server) Start(stage adapter.StartStage) error { + if stage != adapter.StartStatePostStart { + return nil + } listener, err := net.Listen("tcp", s.listen) if err != nil { return err @@ -70,6 +77,6 @@ func (s *Server) Close() error { ) } -func (s *Server) StatsService() adapter.V2RayStatsService { +func (s *Server) StatsService() adapter.ConnectionTracker { return s.statsService } diff --git a/experimental/v2rayapi/stats.go b/experimental/v2rayapi/stats.go index 38b9a301fd..6c44518fd5 100644 --- a/experimental/v2rayapi/stats.go +++ b/experimental/v2rayapi/stats.go @@ -22,7 +22,7 @@ func init() { } var ( - _ adapter.V2RayStatsService = (*StatsService)(nil) + _ adapter.ConnectionTracker = (*StatsService)(nil) _ StatsServiceServer = (*StatsService)(nil) ) @@ -60,7 +60,10 @@ func NewStatsService(options option.V2RayStatsServiceOptions) *StatsService { } } -func (s *StatsService) RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn { +func (s *StatsService) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn { + inbound := metadata.Inbound + user := metadata.User + outbound := matchOutbound.Tag() var readCounter []*atomic.Int64 var writeCounter []*atomic.Int64 countInbound := inbound != "" && s.inbounds[inbound] @@ -86,7 +89,10 @@ func (s *StatsService) RoutedConnection(inbound string, outbound string, user st return bufio.NewInt64CounterConn(conn, readCounter, writeCounter) } -func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn { +func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn { + inbound := metadata.Inbound + user := metadata.User + outbound := matchOutbound.Tag() var readCounter []*atomic.Int64 var writeCounter []*atomic.Int64 countInbound := inbound != "" && s.inbounds[inbound] diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index 4d76a31c6a..f1a84b5044 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -76,18 +76,7 @@ func (s *URLTest) Start() error { } outbounds = append(outbounds, detour) } - group, err := NewURLTestGroup( - s.ctx, - s.router, - s.outboundManager, - s.logger, - outbounds, - s.link, - s.interval, - s.tolerance, - s.idleTimeout, - s.interruptExternalConnections, - ) + group, err := NewURLTestGroup(s.ctx, s.outboundManager, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections) if err != nil { return err } @@ -215,18 +204,7 @@ type URLTestGroup struct { lastActive atomic.TypedValue[time.Time] } -func NewURLTestGroup( - ctx context.Context, - router adapter.Router, - outboundManager adapter.OutboundManager, - logger log.Logger, - outbounds []adapter.Outbound, - link string, - interval time.Duration, - tolerance uint16, - idleTimeout time.Duration, - interruptExternalConnections bool, -) (*URLTestGroup, error) { +func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) { if interval == 0 { interval = C.DefaultURLTestInterval } @@ -241,14 +219,13 @@ func NewURLTestGroup( } var history *urltest.HistoryStorage if history = service.PtrFromContext[urltest.HistoryStorage](ctx); history != nil { - } else if clashServer := router.ClashServer(); clashServer != nil { + } else if clashServer := service.FromContext[adapter.ClashServer](ctx); clashServer != nil { history = clashServer.HistoryStorage() } else { history = urltest.NewHistoryStorage() } return &URLTestGroup{ ctx: ctx, - router: router, outboundManager: outboundManager, logger: logger, outbounds: outbounds, diff --git a/route/route.go b/route/route.go index d55b4ec170..1c4da4b7ad 100644 --- a/route/route.go +++ b/route/route.go @@ -91,16 +91,12 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if err != nil { return err } - var ( - // selectedOutbound adapter.Outbound - selectedDialer N.Dialer - selectedTag string - selectedDescription string - ) + var selectedOutbound adapter.Outbound if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - selectedOutbound, loaded := r.outboundManager.Outbound(action.Outbound) + var loaded bool + selectedOutbound, loaded = r.outboundManager.Outbound(action.Outbound) if !loaded { buf.ReleaseMulti(buffers) return E.New("outbound not found: ", action.Outbound) @@ -109,12 +105,6 @@ 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()) } - selectedDialer = selectedOutbound - selectedTag = selectedOutbound.Tag() - selectedDescription = F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") - case *rule.RuleActionDirect: - selectedDialer = action.Dialer - selectedDescription = action.String() case *rule.RuleActionReject: buf.ReleaseMulti(buffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) @@ -133,25 +123,16 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad buf.ReleaseMulti(buffers) return E.New("TCP is not supported by default outbound: ", defaultOutbound.Tag()) } - selectedDialer = defaultOutbound - selectedTag = defaultOutbound.Tag() - selectedDescription = F.ToString("outbound/", defaultOutbound.Type(), "[", defaultOutbound.Tag(), "]") + selectedOutbound = defaultOutbound } for _, buffer := range buffers { conn = bufio.NewCachedConn(conn, buffer) } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, selectedRule) - defer tracker.Leave() - conn = trackerConn + if r.tracker != nil { + conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound) } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedConnection(metadata.Inbound, selectedTag, metadata.User, conn) - } - } - legacyOutbound, isLegacy := selectedDialer.(adapter.ConnectionHandler) + legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler) if isLegacy { err = legacyOutbound.NewConnection(ctx, conn, metadata) if err != nil { @@ -159,7 +140,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if onClose != nil { onClose(err) } - return E.Cause(err, selectedDescription) + return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) } else { if onClose != nil { onClose(nil) @@ -168,13 +149,13 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad return nil } // TODO - err = outbound.NewConnection(ctx, selectedDialer, conn, metadata) + err = outbound.NewConnection(ctx, selectedOutbound, conn, metadata) if err != nil { conn.Close() if onClose != nil { onClose(err) } - return E.Cause(err, selectedDescription) + return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) } else { if onClose != nil { onClose(nil) @@ -246,16 +227,13 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if err != nil { return err } - var ( - selectedDialer N.Dialer - selectedTag string - selectedDescription string - ) + var selectedOutbound adapter.Outbound var selectReturn bool if selectedRule != nil { switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: - selectedOutbound, loaded := r.outboundManager.Outbound(action.Outbound) + var loaded bool + selectedOutbound, loaded = r.outboundManager.Outbound(action.Outbound) if !loaded { N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("outbound not found: ", action.Outbound) @@ -264,12 +242,6 @@ 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()) } - selectedDialer = selectedOutbound - selectedTag = selectedOutbound.Tag() - selectedDescription = F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]") - case *rule.RuleActionDirect: - selectedDialer = action.Dialer - selectedDescription = action.String() case *rule.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) @@ -285,41 +257,32 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("UDP is not supported by outbound: ", defaultOutbound.Tag()) } - selectedDialer = defaultOutbound - selectedTag = defaultOutbound.Tag() - selectedDescription = F.ToString("outbound/", defaultOutbound.Type(), "[", defaultOutbound.Tag(), "]") + selectedOutbound = defaultOutbound } for _, buffer := range packetBuffers { conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) N.PutPacketBuffer(buffer) } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, selectedRule) - defer tracker.Leave() - conn = trackerConn - } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedPacketConnection(metadata.Inbound, selectedTag, metadata.User, conn) - } + if r.tracker != nil { + conn = r.tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound) } if metadata.FakeIP { conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) } - legacyOutbound, isLegacy := selectedDialer.(adapter.PacketConnectionHandler) + legacyOutbound, isLegacy := selectedOutbound.(adapter.PacketConnectionHandler) if isLegacy { err = legacyOutbound.NewPacketConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - return E.Cause(err, selectedDescription) + return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) } return nil } // TODO - err = outbound.NewPacketConnection(ctx, selectedDialer, conn, metadata) + err = outbound.NewPacketConnection(ctx, selectedOutbound, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { - return E.Cause(err, selectedDescription) + return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) } return nil } diff --git a/route/router.go b/route/router.go index 62b374473f..1f760a86a0 100644 --- a/route/router.go +++ b/route/router.go @@ -27,7 +27,6 @@ import ( F "github.com/sagernet/sing/common/format" 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/task" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" @@ -63,16 +62,14 @@ type Router struct { dnsReverseMapping *DNSReverseMapping fakeIPStore adapter.FakeIPStore processSearcher process.Searcher - timeService *ntp.Service pauseManager pause.Manager - clashServer adapter.ClashServer - v2rayServer adapter.V2RayServer + tracker adapter.ConnectionTracker platformInterface platform.Interface needWIFIState bool started bool } -func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions, ntpOptions option.NTPOptions) (*Router, error) { +func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) { router := &Router{ ctx: ctx, logger: logFactory.NewLogger("router"), @@ -94,7 +91,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route platformInterface: service.FromContext[platform.Interface](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), } - ctx = service.ContextWith[adapter.Router](ctx, router) + service.MustRegister[adapter.Router](ctx, router) router.dnsClient = dns.NewClient(dns.ClientOptions{ DisableCache: dnsOptions.DNSClientOptions.DisableCache, DisableExpire: dnsOptions.DNSClientOptions.DisableExpire, @@ -290,23 +287,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route } router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range) } - - if ntpOptions.Enabled { - ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) - if err != nil { - return nil, E.Cause(err, "create NTP service") - } - timeService := ntp.NewService(ntp.Options{ - Context: ctx, - Dialer: ntpDialer, - Logger: logFactory.NewLogger("ntp"), - Server: ntpOptions.ServerOptions.Build(), - Interval: time.Duration(ntpOptions.Interval), - WriteToSystem: ntpOptions.WriteToSystem, - }) - service.MustRegister[ntp.TimeService](ctx, timeService) - router.timeService = timeService - } return router, nil } @@ -380,14 +360,6 @@ func (r *Router) Start(stage adapter.StartStage) error { return E.Cause(err, "initialize DNS server[", i, "]") } } - if r.timeService != nil { - monitor.Start("initialize time service") - err := r.timeService.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize time service") - } - } case adapter.StartStatePostStart: var cacheContext *adapter.HTTPStartContext if len(r.ruleSets) > 0 { @@ -502,13 +474,6 @@ func (r *Router) Close() error { }) monitor.Finish() } - if r.timeService != nil { - monitor.Start("close time service") - err = E.Append(err, r.timeService.Close(), func(err error) error { - return E.Cause(err, "close time service") - }) - monitor.Finish() - } if r.fakeIPStore != nil { monitor.Start("close fakeip store") err = E.Append(err, r.fakeIPStore.Close(), func(err error) error { @@ -536,29 +501,8 @@ func (r *Router) Rules() []adapter.Rule { return r.rules } -func (r *Router) ClashServer() adapter.ClashServer { - return r.clashServer -} - -func (r *Router) SetClashServer(server adapter.ClashServer) { - r.clashServer = server -} - -func (r *Router) V2RayServer() adapter.V2RayServer { - return r.v2rayServer -} - -func (r *Router) SetV2RayServer(server adapter.V2RayServer) { - r.v2rayServer = server -} - -func (r *Router) NewError(ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - r.logger.DebugContext(ctx, "connection closed: ", err) - return - } - r.logger.ErrorContext(ctx, err) +func (r *Router) SetTracker(tracker adapter.ConnectionTracker) { + r.tracker = tracker } func (r *Router) ResetNetwork() { diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 33a8e16c35..12d9e96ae8 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -219,7 +219,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.allItems = append(rule.allItems, item) } if options.ClashMode != "" { - item := NewClashModeItem(router, options.ClashMode) + item := NewClashModeItem(ctx, options.ClashMode) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index df5f3f335c..1ec652b22a 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -216,7 +216,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if options.ClashMode != "" { - item := NewClashModeItem(router, options.ClashMode) + item := NewClashModeItem(ctx, options.ClashMode) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_item_clash_mode.go b/route/rule/rule_item_clash_mode.go index aa5126cbde..fe2347a06f 100644 --- a/route/rule/rule_item_clash_mode.go +++ b/route/rule/rule_item_clash_mode.go @@ -1,31 +1,38 @@ package rule import ( + "context" "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/service" ) var _ RuleItem = (*ClashModeItem)(nil) type ClashModeItem struct { - router adapter.Router - mode string + ctx context.Context + clashServer adapter.ClashServer + mode string } -func NewClashModeItem(router adapter.Router, mode string) *ClashModeItem { +func NewClashModeItem(ctx context.Context, mode string) *ClashModeItem { return &ClashModeItem{ - router: router, - mode: mode, + ctx: ctx, + mode: mode, } } +func (r *ClashModeItem) Start() error { + r.clashServer = service.FromContext[adapter.ClashServer](r.ctx) + return nil +} + func (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool { - clashServer := r.router.ClashServer() - if clashServer == nil { + if r.clashServer == nil { return false } - return strings.EqualFold(clashServer.Mode(), r.mode) + return strings.EqualFold(r.clashServer.Mode(), r.mode) } func (r *ClashModeItem) String() string { From ecf82d197c8669d9d64eba1ca89e4bc8533e25fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:23:45 +0800 Subject: [PATCH 0165/1272] refactor: Platform Interfaces --- adapter/network.go | 19 ++ adapter/router.go | 9 - common/settings/proxy_darwin.go | 7 +- constant/network.go | 8 + constant/os.go | 2 +- experimental/libbox/config.go | 18 +- experimental/libbox/link_flags_stub.go | 2 +- ...link_flags_linux.go => link_flags_unix.go} | 2 + experimental/libbox/monitor.go | 161 ++++------------ experimental/libbox/platform.go | 16 +- experimental/libbox/platform/interface.go | 5 +- experimental/libbox/service.go | 55 ++++-- protocol/direct/loopback_detect.go | 6 +- route/network.go | 179 +++++++++++++----- transport/dhcp/server.go | 21 +- 15 files changed, 263 insertions(+), 247 deletions(-) create mode 100644 constant/network.go rename experimental/libbox/{link_flags_linux.go => link_flags_unix.go} (97%) diff --git a/adapter/network.go b/adapter/network.go index 533bfced22..6c09a0a3e1 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -9,6 +9,8 @@ type NetworkManager interface { Lifecycle InterfaceFinder() control.InterfaceFinder UpdateInterfaces() error + DefaultNetworkInterface() *NetworkInterface + NetworkInterfaces() []NetworkInterface DefaultInterface() string AutoDetectInterface() bool AutoDetectInterfaceFunc() control.Func @@ -21,3 +23,20 @@ type NetworkManager interface { WIFIState() WIFIState ResetNetwork() } + +type InterfaceUpdateListener interface { + InterfaceUpdated() +} + +type WIFIState struct { + SSID string + BSSID string +} + +type NetworkInterface struct { + control.Interface + Type string + DNSServers []string + Expensive bool + Constrained bool +} diff --git a/adapter/router.go b/adapter/router.go index 6dd39357b4..a637e5068d 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -119,12 +119,3 @@ func (c *HTTPStartContext) Close() { client.CloseIdleConnections() } } - -type InterfaceUpdateListener interface { - InterfaceUpdated() -} - -type WIFIState struct { - SSID string - BSSID string -} diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index 17e82cf59f..3c06a85369 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -2,7 +2,6 @@ package settings import ( "context" - "net/netip" "strconv" "strings" @@ -77,14 +76,14 @@ func (p *DarwinSystemProxy) update(event int) { } func (p *DarwinSystemProxy) update0() error { - newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified()) - if p.interfaceName == newInterfaceName { + newInterface := p.monitor.DefaultInterface() + if p.interfaceName == newInterface.Name { return nil } if p.interfaceName != "" { _ = p.Disable() } - p.interfaceName = newInterfaceName + p.interfaceName = newInterface.Name interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName) if err != nil { return err diff --git a/constant/network.go b/constant/network.go new file mode 100644 index 0000000000..f5ac2a4e47 --- /dev/null +++ b/constant/network.go @@ -0,0 +1,8 @@ +package constant + +const ( + InterfaceTypeWIFI = "wifi" + InterfaceTypeCellular = "cellular" + InterfaceTypeEthernet = "ethernet" + InterfaceTypeOther = "other" +) diff --git a/constant/os.go b/constant/os.go index 63c6c41cd2..6142767c9c 100644 --- a/constant/os.go +++ b/constant/os.go @@ -6,7 +6,7 @@ import ( const IsAndroid = goos.IsAndroid == 1 -const IsDarwin = goos.IsDarwin == 1 +const IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1 const IsDragonfly = goos.IsDragonfly == 1 diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 3310b09a58..02d5b884b5 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -74,11 +74,7 @@ func (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logg return (*interfaceMonitorStub)(nil) } -func (s *platformInterfaceStub) UsePlatformInterfaceGetter() bool { - return true -} - -func (s *platformInterfaceStub) Interfaces() ([]control.Interface, error) { +func (s *platformInterfaceStub) Interfaces() ([]adapter.NetworkInterface, error) { return nil, os.ErrInvalid } @@ -111,16 +107,8 @@ func (s *interfaceMonitorStub) Close() error { return os.ErrInvalid } -func (s *interfaceMonitorStub) DefaultInterfaceName(destination netip.Addr) string { - return "" -} - -func (s *interfaceMonitorStub) DefaultInterfaceIndex(destination netip.Addr) int { - return -1 -} - -func (s *interfaceMonitorStub) DefaultInterface(destination netip.Addr) (string, int) { - return "", -1 +func (s *interfaceMonitorStub) DefaultInterface() *control.Interface { + return nil } func (s *interfaceMonitorStub) OverrideAndroidVPN() bool { diff --git a/experimental/libbox/link_flags_stub.go b/experimental/libbox/link_flags_stub.go index 306754f3f2..ce3d1eb805 100644 --- a/experimental/libbox/link_flags_stub.go +++ b/experimental/libbox/link_flags_stub.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !unix package libbox diff --git a/experimental/libbox/link_flags_linux.go b/experimental/libbox/link_flags_unix.go similarity index 97% rename from experimental/libbox/link_flags_linux.go rename to experimental/libbox/link_flags_unix.go index aa9ad46d20..04f41d641f 100644 --- a/experimental/libbox/link_flags_linux.go +++ b/experimental/libbox/link_flags_unix.go @@ -1,3 +1,5 @@ +//go:build unix + package libbox import ( diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 00a3e0cf6c..d144fa07e4 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -1,15 +1,10 @@ package libbox import ( - "net" - "net/netip" - "sync" - "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" - M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/x/list" ) @@ -20,19 +15,9 @@ var ( type platformDefaultInterfaceMonitor struct { *platformInterfaceWrapper - networkAddresses []networkAddress - defaultInterfaceName string - defaultInterfaceIndex int - element *list.Element[tun.NetworkUpdateCallback] - access sync.Mutex - callbacks list.List[tun.DefaultInterfaceUpdateCallback] - logger logger.Logger -} - -type networkAddress struct { - interfaceName string - interfaceIndex int - addresses []netip.Prefix + element *list.Element[tun.NetworkUpdateCallback] + callbacks list.List[tun.DefaultInterfaceUpdateCallback] + logger logger.Logger } func (m *platformDefaultInterfaceMonitor) Start() error { @@ -43,37 +28,10 @@ func (m *platformDefaultInterfaceMonitor) Close() error { return m.iif.CloseDefaultInterfaceMonitor(m) } -func (m *platformDefaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) string { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceName - } - } - } - return m.defaultInterfaceName -} - -func (m *platformDefaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr) int { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceIndex - } - } - } - return m.defaultInterfaceIndex -} - -func (m *platformDefaultInterfaceMonitor) DefaultInterface(destination netip.Addr) (string, int) { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceName, address.interfaceIndex - } - } - } - return m.defaultInterfaceName, m.defaultInterfaceIndex +func (m *platformDefaultInterfaceMonitor) DefaultInterface() *control.Interface { + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() + return m.defaultInterface } func (m *platformDefaultInterfaceMonitor) OverrideAndroidVPN() bool { @@ -85,104 +43,57 @@ func (m *platformDefaultInterfaceMonitor) AndroidVPNEnabled() bool { } func (m *platformDefaultInterfaceMonitor) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] { - m.access.Lock() - defer m.access.Unlock() + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() return m.callbacks.PushBack(callback) } func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { - m.access.Lock() - defer m.access.Unlock() + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() m.callbacks.Remove(element) } -func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) { +func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) { if sFixAndroidStack { - go m.updateDefaultInterface(interfaceName, interfaceIndex32) + go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained) } else { - m.updateDefaultInterface(interfaceName, interfaceIndex32) + m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained) } } -func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName string, interfaceIndex32 int32) { - if interfaceName == "" || interfaceIndex32 == -1 { - m.defaultInterfaceName = "" - m.defaultInterfaceIndex = -1 - m.access.Lock() +func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) { + m.isExpensive = isExpensive + m.isConstrained = isConstrained + err := m.networkManager.UpdateInterfaces() + if err != nil { + m.logger.Error(E.Cause(err, "update interfaces")) + } + m.defaultInterfaceAccess.Lock() + if interfaceIndex32 == -1 { + m.defaultInterface = nil callbacks := m.callbacks.Array() - m.access.Unlock() + m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { - callback(tun.EventNoRoute) + callback(tun.EventInterfaceUpdate) } return } - var err error - if m.iif.UsePlatformInterfaceGetter() { - err = m.updateInterfacesPlatform() - } else { - err = m.updateInterfaces() - } - if err == nil { - err = m.networkManager.UpdateInterfaces() - } + oldInterface := m.defaultInterface + newInterface, err := m.networkManager.InterfaceFinder().ByIndex(int(interfaceIndex32)) if err != nil { - m.logger.Error(E.Cause(err, "update interfaces")) + m.defaultInterfaceAccess.Unlock() + m.logger.Error(E.Cause(err, "find updated interface: ", interfaceName)) + return } - interfaceIndex := int(interfaceIndex32) - if m.defaultInterfaceName == interfaceName && m.defaultInterfaceIndex == interfaceIndex { + m.defaultInterface = newInterface + if oldInterface != nil && oldInterface.Name == m.defaultInterface.Name && oldInterface.Index == m.defaultInterface.Index { + m.defaultInterfaceAccess.Unlock() return } - m.defaultInterfaceName = interfaceName - m.defaultInterfaceIndex = interfaceIndex - m.access.Lock() callbacks := m.callbacks.Array() - m.access.Unlock() + m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { callback(tun.EventInterfaceUpdate) } } - -func (m *platformDefaultInterfaceMonitor) updateInterfaces() error { - interfaces, err := net.Interfaces() - if err != nil { - return err - } - var addresses []networkAddress - for _, iif := range interfaces { - var netAddresses []net.Addr - netAddresses, err = iif.Addrs() - if err != nil { - return err - } - var address networkAddress - address.interfaceName = iif.Name - address.interfaceIndex = iif.Index - address.addresses = common.Map(common.FilterIsInstance(netAddresses, func(it net.Addr) (*net.IPNet, bool) { - value, loaded := it.(*net.IPNet) - return value, loaded - }), func(it *net.IPNet) netip.Prefix { - bits, _ := it.Mask.Size() - return netip.PrefixFrom(M.AddrFromIP(it.IP), bits) - }) - addresses = append(addresses, address) - } - m.networkAddresses = addresses - return nil -} - -func (m *platformDefaultInterfaceMonitor) updateInterfacesPlatform() error { - interfaces, err := m.Interfaces() - if err != nil { - return err - } - var addresses []networkAddress - for _, iif := range interfaces { - var address networkAddress - address.interfaceName = iif.Name - address.interfaceIndex = iif.Index - // address.addresses = common.Map(iif.Addresses, netip.MustParsePrefix) - addresses = append(addresses, address) - } - m.networkAddresses = addresses - return nil -} diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 1976ad70fe..a61243458c 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -1,6 +1,7 @@ package libbox import ( + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" ) @@ -13,10 +14,8 @@ type PlatformInterface interface { FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) PackageNameByUid(uid int32) (string, error) UIDByPackageName(packageName string) (int32, error) - UsePlatformDefaultInterfaceMonitor() bool StartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error - UsePlatformInterfaceGetter() bool GetInterfaces() (NetworkInterfaceIterator, error) UnderNetworkExtension() bool IncludeAllNetworks() bool @@ -31,15 +30,26 @@ type TunInterface interface { } type InterfaceUpdateListener interface { - UpdateDefaultInterface(interfaceName string, interfaceIndex int32) + UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool) } +const ( + InterfaceTypeWIFI = C.InterfaceTypeWIFI + InterfaceTypeCellular = C.InterfaceTypeCellular + InterfaceTypeEthernet = C.InterfaceTypeEthernet + InterfaceTypeOther = C.InterfaceTypeOther +) + type NetworkInterface struct { Index int32 MTU int32 Name string Addresses StringIterator Flags int32 + + Type string + DNSServer StringIterator + Metered bool } type WIFIState struct { diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 16eab5abcc..ef37daea22 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -5,7 +5,6 @@ import ( "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/logger" ) @@ -14,10 +13,8 @@ type Interface interface { UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int) error OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) - UsePlatformDefaultInterfaceMonitor() bool CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor - UsePlatformInterfaceGetter() bool - Interfaces() ([]control.Interface, error) + Interfaces() ([]adapter.NetworkInterface, error) UnderNetworkExtension() bool IncludeAllNetworks() bool ClearDNSCache() diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 9b7181c8b8..28ae03d3c0 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -6,6 +6,7 @@ import ( "os" "runtime" runtimeDebug "runtime/debug" + "sync" "syscall" "time" @@ -54,7 +55,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box ctx, cancel := context.WithCancel(ctx) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) - platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} + platformWrapper := &platformInterfaceWrapper{ + iif: platformInterface, + useProcFS: platformInterface.UseProcFS(), + } service.MustRegister[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ Context: ctx, @@ -119,9 +123,14 @@ var ( ) type platformInterfaceWrapper struct { - iif PlatformInterface - useProcFS bool - networkManager adapter.NetworkManager + iif PlatformInterface + useProcFS bool + networkManager adapter.NetworkManager + myTunName string + defaultInterfaceAccess sync.Mutex + defaultInterface *control.Interface + isExpensive bool + isConstrained bool } func (w *platformInterfaceWrapper) Initialize(networkManager adapter.NetworkManager) error { @@ -161,38 +170,42 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions return nil, E.Cause(err, "dup tun file descriptor") } options.FileDescriptor = dupFd + w.myTunName = options.Name return tun.New(*options) } -func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool { - return w.iif.UsePlatformDefaultInterfaceMonitor() -} - func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, - defaultInterfaceIndex: -1, logger: logger, } } -func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool { - return w.iif.UsePlatformInterfaceGetter() -} - -func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) { +func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, error) { interfaceIterator, err := w.iif.GetInterfaces() if err != nil { return nil, err } - var interfaces []control.Interface + var interfaces []adapter.NetworkInterface for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) { - interfaces = append(interfaces, control.Interface{ - Index: int(netInterface.Index), - MTU: int(netInterface.MTU), - Name: netInterface.Name, - Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), - Flags: linkFlags(uint32(netInterface.Flags)), + if netInterface.Name == w.myTunName { + continue + } + w.defaultInterfaceAccess.Lock() + isDefault := w.defaultInterface != nil && int(netInterface.Index) == w.defaultInterface.Index + w.defaultInterfaceAccess.Unlock() + interfaces = append(interfaces, adapter.NetworkInterface{ + Interface: control.Interface{ + Index: int(netInterface.Index), + MTU: int(netInterface.MTU), + Name: netInterface.Name, + Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), + Flags: linkFlags(uint32(netInterface.Flags)), + }, + Type: netInterface.Type, + DNSServers: iteratorToArray[string](netInterface.DNSServer), + Expensive: netInterface.Metered || isDefault && w.isExpensive, + Constrained: isDefault && w.isConstrained, }) } return interfaces, nil diff --git a/protocol/direct/loopback_detect.go b/protocol/direct/loopback_detect.go index 4bc1be3b04..7a62164ef5 100644 --- a/protocol/direct/loopback_detect.go +++ b/protocol/direct/loopback_detect.go @@ -33,7 +33,7 @@ func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn { } if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn { if !source.Addr().IsLoopback() { - _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr()) if err != nil { return conn } @@ -59,7 +59,7 @@ func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Soc return conn } if !source.Addr().IsLoopback() { - _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr()) if err != nil { return conn } @@ -82,7 +82,7 @@ func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.Ad return false } if !source.Addr().IsLoopback() { - _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr()) if err != nil { return false } diff --git a/route/network.go b/route/network.go index 912b6621e7..d4b8adcf41 100644 --- a/route/network.go +++ b/route/network.go @@ -3,9 +3,10 @@ package route import ( "context" "errors" - "net/netip" + "net" "os" "runtime" + "strings" "syscall" "github.com/sagernet/sing-box/adapter" @@ -15,33 +16,41 @@ import ( "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/winpowrprof" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" + + "golang.org/x/exp/slices" ) var _ adapter.NetworkManager = (*NetworkManager)(nil) type NetworkManager struct { - logger logger.ContextLogger - interfaceFinder *control.DefaultInterfaceFinder + logger logger.ContextLogger + interfaceFinder *control.DefaultInterfaceFinder + networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface] + autoDetectInterface bool defaultInterface string defaultMark uint32 autoRedirectOutputMark uint32 - networkMonitor tun.NetworkUpdateMonitor - interfaceMonitor tun.DefaultInterfaceMonitor - packageManager tun.PackageManager - powerListener winpowrprof.EventListener - pauseManager pause.Manager - platformInterface platform.Interface - outboundManager adapter.OutboundManager - wifiState adapter.WIFIState - started bool + + networkMonitor tun.NetworkUpdateMonitor + interfaceMonitor tun.DefaultInterfaceMonitor + packageManager tun.PackageManager + powerListener winpowrprof.EventListener + pauseManager pause.Manager + platformInterface platform.Interface + outboundManager adapter.OutboundManager + wifiState adapter.WIFIState + started bool } func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { @@ -55,7 +64,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp platformInterface: service.FromContext[platform.Interface](ctx), outboundManager: service.FromContext[adapter.OutboundManager](ctx), } - usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil && nm.platformInterface.UsePlatformDefaultInterfaceMonitor() + usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil enforceInterfaceMonitor := routeOptions.AutoDetectInterface if !usePlatformDefaultInterfaceMonitor { networkMonitor, err := tun.NewNetworkUpdateMonitor(logger) @@ -87,17 +96,17 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { case adapter.StartStateInitialize: - if r.interfaceMonitor != nil { - monitor.Start("initialize interface monitor") - err := r.interfaceMonitor.Start() + if r.networkMonitor != nil { + monitor.Start("initialize network monitor") + err := r.networkMonitor.Start() monitor.Finish() if err != nil { return err } } - if r.networkMonitor != nil { - monitor.Start("initialize network monitor") - err := r.networkMonitor.Start() + if r.interfaceMonitor != nil { + monitor.Start("initialize interface monitor") + err := r.interfaceMonitor.Start() monitor.Finish() if err != nil { return err @@ -148,20 +157,6 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { func (r *NetworkManager) Close() error { monitor := taskmonitor.New(r.logger, C.StopTimeout) var err error - if r.interfaceMonitor != nil { - monitor.Start("close interface monitor") - err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { - return E.Cause(err, "close interface monitor") - }) - monitor.Finish() - } - if r.networkMonitor != nil { - monitor.Start("close network monitor") - err = E.Append(err, r.networkMonitor.Close(), func(err error) error { - return E.Cause(err, "close network monitor") - }) - monitor.Finish() - } if r.packageManager != nil { monitor.Start("close package manager") err = E.Append(err, r.packageManager.Close(), func(err error) error { @@ -176,6 +171,20 @@ func (r *NetworkManager) Close() error { }) monitor.Finish() } + if r.interfaceMonitor != nil { + monitor.Start("close interface monitor") + err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { + return E.Cause(err, "close interface monitor") + }) + monitor.Finish() + } + if r.networkMonitor != nil { + monitor.Start("close network monitor") + err = E.Append(err, r.networkMonitor.Close(), func(err error) error { + return E.Cause(err, "close network monitor") + }) + monitor.Finish() + } return nil } @@ -184,18 +193,75 @@ func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { } func (r *NetworkManager) UpdateInterfaces() error { - if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { + if r.platformInterface == nil { return r.interfaceFinder.Update() } else { interfaces, err := r.platformInterface.Interfaces() if err != nil { return err } - r.interfaceFinder.UpdateInterfaces(interfaces) + if C.IsDarwin { + err = r.interfaceFinder.Update() + if err != nil { + return err + } + // NEInterface only provides name,index and type + interfaces = common.Map(interfaces, func(it adapter.NetworkInterface) adapter.NetworkInterface { + iif, _ := r.interfaceFinder.ByIndex(it.Index) + if iif != nil { + it.Interface = *iif + } + return it + }) + } else { + r.interfaceFinder.UpdateInterfaces(common.Map(interfaces, func(it adapter.NetworkInterface) control.Interface { return it.Interface })) + } + oldInterfaces := r.networkInterfaces.Load() + newInterfaces := common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return it.Flags&net.FlagUp != 0 + }) + r.networkInterfaces.Store(newInterfaces) + if len(newInterfaces) > 0 && !slices.EqualFunc(oldInterfaces, newInterfaces, func(oldInterface adapter.NetworkInterface, newInterface adapter.NetworkInterface) bool { + return oldInterface.Interface.Index == newInterface.Interface.Index && + oldInterface.Interface.Name == newInterface.Interface.Name && + oldInterface.Interface.Flags == newInterface.Interface.Flags && + oldInterface.Type == newInterface.Type && + oldInterface.Expensive == newInterface.Expensive && + oldInterface.Constrained == newInterface.Constrained + }) { + r.logger.Info("updated available networks: ", strings.Join(common.Map(newInterfaces, func(it adapter.NetworkInterface) string { + var options []string + options = append(options, F.ToString(it.Type)) + if it.Expensive { + options = append(options, "expensive") + } + if it.Constrained { + options = append(options, "constrained") + } + return F.ToString(it.Name, " (", strings.Join(options, ", "), ")") + }), ", ")) + } return nil } } +func (r *NetworkManager) DefaultNetworkInterface() *adapter.NetworkInterface { + iif := r.interfaceMonitor.DefaultInterface() + if iif == nil { + return nil + } + for _, it := range r.networkInterfaces.Load() { + if it.Interface.Index == iif.Index { + return &it + } + } + return &adapter.NetworkInterface{Interface: *iif} +} + +func (r *NetworkManager) NetworkInterfaces() []adapter.NetworkInterface { + return r.networkInterfaces.Load() +} + func (r *NetworkManager) DefaultInterface() string { return r.defaultInterface } @@ -217,18 +283,17 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { } return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { remoteAddr := M.ParseSocksaddr(address).Addr - if C.IsLinux { - interfaceName, interfaceIndex = r.interfaceMonitor.DefaultInterface(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute - } - } else { - interfaceIndex = r.interfaceMonitor.DefaultInterfaceIndex(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute + if remoteAddr.IsValid() { + iif, err := r.interfaceFinder.ByAddr(remoteAddr) + if err == nil { + return iif.Name, iif.Index, nil } } - return + defaultInterface := r.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return "", -1, tun.ErrNoRoute + } + return defaultInterface.Name, defaultInterface.Index, nil }) } } @@ -282,6 +347,12 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) { r.logger.Error("missing default interface") } else { r.pauseManager.NetworkWake() + defaultInterface := r.DefaultNetworkInterface() + if defaultInterface == nil { + panic("invalid interface context") + } + var options []string + options = append(options, F.ToString("index ", defaultInterface.Index)) if C.IsAndroid && r.platformInterface == nil { var vpnStatus string if r.interfaceMonitor.AndroidVPNEnabled() { @@ -289,17 +360,24 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) { } else { vpnStatus = "disabled" } - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) + options = append(options, "vpn "+vpnStatus) } else { - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) + if defaultInterface.Type != "" { + options = append(options, F.ToString("type ", defaultInterface.Type)) + } + if defaultInterface.Expensive { + options = append(options, "expensive") + } + if defaultInterface.Constrained { + options = append(options, "constrained") + } } + r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) if r.platformInterface != nil { state := r.platformInterface.ReadWIFIState() if state != r.wifiState { r.wifiState = state - if state.SSID == "" && state.BSSID == "" { - r.logger.Info("updated WIFI state: disconnected") - } else { + if state.SSID != "" { r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) } } @@ -309,7 +387,6 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) { if !r.started { return } - r.ResetNetwork() } diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index dfe33d86c2..9a06ac1716 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -119,18 +119,19 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, return nil, err } -func (t *Transport) fetchInterface() (*net.Interface, error) { - interfaceName := t.interfaceName +func (t *Transport) fetchInterface() (*control.Interface, error) { if t.autoInterface { if t.networkManager.InterfaceMonitor() == nil { return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface") } - interfaceName = t.networkManager.InterfaceMonitor().DefaultInterfaceName(netip.Addr{}) - } - if interfaceName == "" { - return nil, E.New("missing default interface") + defaultInterface := t.networkManager.InterfaceMonitor().DefaultInterface() + if defaultInterface == nil { + return nil, E.New("missing default interface") + } + return defaultInterface, nil + } else { + return t.networkManager.InterfaceFinder().ByName(t.interfaceName) } - return net.InterfaceByName(interfaceName) } func (t *Transport) fetchServers() error { @@ -172,7 +173,7 @@ func (t *Transport) interfaceUpdated(int) { } } -func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error { +func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface) error { var listener net.ListenConfig listener.Control = control.Append(listener.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), iface.Name, iface.Index)) listener.Control = control.Append(listener.Control, control.ReuseAddr()) @@ -206,7 +207,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) err return group.Run(ctx) } -func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error { +func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error { buffer := buf.NewSize(dhcpv4.MaxMessageSize) defer buffer.Release() @@ -246,7 +247,7 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa } } -func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error { +func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []netip.Addr) error { if len(serverAddrs) > 0 { t.options.Logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string { return it.String() From c098f282b1c853ec7c8c0063dfa55cad0cd71c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:32:34 +0800 Subject: [PATCH 0166/1272] Merge route options to route actions --- experimental/deprecated/constants.go | 10 ------ option/rule_action.go | 43 ++++++------------------- route/rule/rule_action.go | 48 +++++++++++++++++++++------- 3 files changed, 45 insertions(+), 56 deletions(-) diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 8f7eee11ff..918b698df9 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -104,15 +104,6 @@ var OptionInboundOptions = Note{ MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions", } -var OptionLegacyDNSRouteOptions = Note{ - Name: "legacy-dns-route-options", - Description: "legacy dns route options", - DeprecatedVersion: "1.11.0", - ScheduledVersion: "1.12.0", - EnvName: "LEGACY_DNS_ROUTE_OPTIONS", - MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-dns-route-options-to-rule-actions", -} - var Options = []Note{ OptionBadMatchSource, OptionGEOIP, @@ -120,5 +111,4 @@ var Options = []Note{ OptionTUNAddressX, OptionSpecialOutbounds, OptionInboundOptions, - OptionLegacyDNSRouteOptions, } diff --git a/option/rule_action.go b/option/rule_action.go index 3b4e8edb5f..9bc1303933 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -7,8 +7,7 @@ import ( "time" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -137,18 +136,10 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e return badjson.UnmarshallExcludedContext(ctx, data, (*_DNSRuleAction)(r), v) } -type _RouteActionOptions struct { - Outbound string `json:"outbound,omitempty"` -} - -type RouteActionOptions _RouteActionOptions - -func (r *RouteActionOptions) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, (*_RouteActionOptions)(r)) - if err != nil { - return err - } - return nil +type RouteActionOptions struct { + Outbound string `json:"outbound,omitempty"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` } type _RouteOptionsActionOptions struct { @@ -169,29 +160,13 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { return nil } -type _DNSRouteActionOptions struct { - Server string `json:"server,omitempty"` - // Deprecated: Use DNSRouteOptionsActionOptions instead. - DisableCache bool `json:"disable_cache,omitempty"` - // Deprecated: Use DNSRouteOptionsActionOptions instead. - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - // Deprecated: Use DNSRouteOptionsActionOptions instead. +type DNSRouteActionOptions struct { + Server string `json:"server,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } -type DNSRouteActionOptions _DNSRouteActionOptions - -func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data []byte) error { - err := json.Unmarshal(data, (*_DNSRouteActionOptions)(r)) - if err != nil { - return err - } - if r.DisableCache || r.RewriteTTL != nil || r.ClientSubnet != nil { - deprecated.Report(ctx, deprecated.OptionLegacyDNSRouteOptions) - } - return nil -} - type _DNSRouteOptionsActionOptions struct { DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 00579c18d5..d44e36eeac 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -29,6 +29,10 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti case C.RuleActionTypeRoute: return &RuleActionRoute{ Outbound: action.RouteOptions.Outbound, + RuleActionRouteOptions: RuleActionRouteOptions{ + UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, + UDPConnect: action.RouteOptions.UDPConnect, + }, }, nil case C.RuleActionTypeRouteOptions: return &RuleActionRouteOptions{ @@ -85,10 +89,12 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) return nil case C.RuleActionTypeRoute: return &RuleActionDNSRoute{ - Server: action.RouteOptions.Server, - DisableCache: action.RouteOptions.DisableCache, - RewriteTTL: action.RouteOptions.RewriteTTL, - ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), + Server: action.RouteOptions.Server, + RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ + DisableCache: action.RouteOptions.DisableCache, + RewriteTTL: action.RouteOptions.RewriteTTL, + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), + }, } case C.RuleActionTypeRouteOptions: return &RuleActionDNSRouteOptions{ @@ -109,6 +115,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) type RuleActionRoute struct { Outbound string + RuleActionRouteOptions } func (r *RuleActionRoute) Type() string { @@ -116,7 +123,15 @@ func (r *RuleActionRoute) Type() string { } func (r *RuleActionRoute) String() string { - return F.ToString("route(", r.Outbound, ")") + var descriptions []string + descriptions = append(descriptions, r.Outbound) + if r.UDPDisableDomainUnmapping { + descriptions = append(descriptions, "udp-disable-domain-unmapping") + } + if r.UDPConnect { + descriptions = append(descriptions, "udp-connect") + } + return F.ToString("route(", strings.Join(descriptions, ","), ")") } type RuleActionRouteOptions struct { @@ -140,10 +155,8 @@ func (r *RuleActionRouteOptions) String() string { } type RuleActionDNSRoute struct { - Server string - DisableCache bool - RewriteTTL *uint32 - ClientSubnet netip.Prefix + Server string + RuleActionDNSRouteOptions } func (r *RuleActionDNSRoute) Type() string { @@ -151,7 +164,18 @@ func (r *RuleActionDNSRoute) Type() string { } func (r *RuleActionDNSRoute) String() string { - return F.ToString("route(", r.Server, ")") + var descriptions []string + descriptions = append(descriptions, r.Server) + if r.DisableCache { + descriptions = append(descriptions, "disable-cache") + } + if r.RewriteTTL != nil { + descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL)) + } + if r.ClientSubnet.IsValid() { + descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet)) + } + return F.ToString("route(", strings.Join(descriptions, ","), ")") } type RuleActionDNSRouteOptions struct { @@ -170,10 +194,10 @@ func (r *RuleActionDNSRouteOptions) String() string { descriptions = append(descriptions, "disable-cache") } if r.RewriteTTL != nil { - descriptions = append(descriptions, F.ToString("rewrite-ttl(", *r.RewriteTTL, ")")) + descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL)) } if r.ClientSubnet.IsValid() { - descriptions = append(descriptions, F.ToString("client-subnet(", r.ClientSubnet, ")")) + descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet)) } return F.ToString("route-options(", strings.Join(descriptions, ","), ")") } From 05ea0ca00effea4a8e58acdc6c239e153218acfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:30:25 +0800 Subject: [PATCH 0167/1272] Add `network_[type/is_expensive/is_constrained]` rule items --- .../convertor/adguard/convertor_test.go | 7 +- common/srs/binary.go | 30 ++ constant/rule.go | 3 +- docs/configuration/dns/rule.md | 43 ++- docs/configuration/dns/rule.zh.md | 91 ++++-- docs/configuration/route/rule.md | 43 ++- docs/configuration/route/rule.zh.md | 50 +++- docs/configuration/rule-set/adguard.zh.md | 70 +++++ docs/configuration/rule-set/headless-rule.md | 48 ++++ .../rule-set/headless-rule.zh.md | 258 ++++++++++++++++++ docs/configuration/rule-set/index.md | 2 +- docs/configuration/rule-set/index.zh.md | 117 ++++++++ docs/configuration/rule-set/source-format.md | 21 +- .../rule-set/source-format.zh.md | 46 ++++ option/rule.go | 3 + option/rule_dns.go | 3 + option/rule_set.go | 47 ++-- route/rule/rule_default.go | 15 + route/rule/rule_dns.go | 15 + route/rule/rule_headless.go | 25 +- .../rule/rule_item_network_is_constrained.go | 29 ++ route/rule/rule_item_network_is_expensive.go | 29 ++ route/rule/rule_item_network_type.go | 39 +++ 23 files changed, 967 insertions(+), 67 deletions(-) create mode 100644 docs/configuration/rule-set/adguard.zh.md create mode 100644 docs/configuration/rule-set/headless-rule.zh.md create mode 100644 docs/configuration/rule-set/index.zh.md create mode 100644 docs/configuration/rule-set/source-format.zh.md create mode 100644 route/rule/rule_item_network_is_constrained.go create mode 100644 route/rule/rule_item_network_is_expensive.go create mode 100644 route/rule/rule_item_network_type.go diff --git a/cmd/sing-box/internal/convertor/adguard/convertor_test.go b/cmd/sing-box/internal/convertor/adguard/convertor_test.go index 6098485cf0..212c21703a 100644 --- a/cmd/sing-box/internal/convertor/adguard/convertor_test.go +++ b/cmd/sing-box/internal/convertor/adguard/convertor_test.go @@ -1,6 +1,7 @@ package adguard import ( + "context" "strings" "testing" @@ -26,7 +27,7 @@ example.arpa `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := rule.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) require.NoError(t, err) matchDomain := []string{ "example.org", @@ -85,7 +86,7 @@ func TestHosts(t *testing.T) { `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := rule.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) require.NoError(t, err) matchDomain := []string{ "google.com", @@ -115,7 +116,7 @@ www.example.org `)) require.NoError(t, err) require.Len(t, rules, 1) - rule, err := rule.NewHeadlessRule(nil, rules[0]) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) require.NoError(t, err) matchDomain := []string{ "example.com", diff --git a/common/srs/binary.go b/common/srs/binary.go index dd670fc83e..fbed78adc5 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -38,6 +38,9 @@ const ( ruleItemWIFIBSSID ruleItemAdGuardDomain ruleItemProcessPathRegex + ruleItemNetworkType + ruleItemNetworkIsExpensive + ruleItemNetworkIsConstrained ruleItemFinal uint8 = 0xFF ) @@ -222,6 +225,12 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea return } rule.AdGuardDomainMatcher = matcher + case ruleItemNetworkType: + rule.NetworkType, err = readRuleItemString(reader) + case ruleItemNetworkIsExpensive: + rule.NetworkIsExpensive = true + case ruleItemNetworkIsConstrained: + rule.NetworkIsConstrained = true case ruleItemFinal: err = binary.Read(reader, binary.BigEndian, &rule.Invert) return @@ -336,6 +345,27 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen return err } } + if len(rule.NetworkType) > 0 { + if generateVersion < C.RuleSetVersion3 { + return E.New("network_type rule item is only supported in version 3 or later") + } + err = writeRuleItemString(writer, ruleItemNetworkType, rule.NetworkType) + if err != nil { + return err + } + } + if rule.NetworkIsExpensive { + err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive) + if err != nil { + return err + } + } + if rule.NetworkIsConstrained { + err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained) + if err != nil { + return err + } + } if len(rule.WIFISSID) > 0 { err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID) if err != nil { diff --git a/constant/rule.go b/constant/rule.go index b1f91c608c..c4a778384f 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -21,7 +21,8 @@ const ( const ( RuleSetVersion1 = 1 + iota RuleSetVersion2 - RuleSetVersionCurrent = RuleSetVersion2 + RuleSetVersion3 + RuleSetVersionCurrent = RuleSetVersion3 ) const ( diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 5cb24e8197..1f04b2999d 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -8,7 +8,10 @@ icon: material/new-box :material-alert: [server](#server) :material-alert: [disable_cache](#disable_cache) :material-alert: [rewrite_ttl](#rewrite_ttl) - :material-alert: [client_subnet](#client_subnet) + :material-alert: [client_subnet](#client_subnet) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) !!! quote "Changes in sing-box 1.10.0" @@ -125,6 +128,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -310,6 +318,39 @@ Match user id. Match Clash mode. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 205b01ae00..e904f8cd02 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -2,6 +2,17 @@ icon: material/new-box --- +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [action](#action) + :material-alert: [server](#server) + :material-alert: [disable_cache](#disable_cache) + :material-alert: [rewrite_ttl](#rewrite_ttl) + :material-alert: [client_subnet](#client_subnet) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + !!! quote "sing-box 1.10.0 中的更改" :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) @@ -117,6 +128,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -135,17 +151,15 @@ icon: material/new-box "outbound": [ "direct" ], - "server": "local", - "disable_cache": false, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" }, { "type": "logical", "mode": "and", "rules": [], - "server": "local", - "disable_cache": false, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" } ] } @@ -304,6 +318,39 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 匹配 Clash 模式。 +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + #### wifi_ssid !!! quote "" @@ -352,29 +399,35 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 `any` 可作为值用于匹配任意出站。 -#### server +#### action ==必填== -目标 DNS 服务器的标签。 +参阅 [规则动作](../rule_action/)。 + +#### server + +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### disable_cache -在此查询中禁用缓存。 +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### rewrite_ttl -重写 DNS 回应中的 TTL。 +!!! failure "已在 sing-box 1.11.0 废弃" -#### client_subnet + 已移动到 [DNS 规则动作](../rule_action#route). -!!! question "自 sing-box 1.9.0 起" - -默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 +#### client_subnet -如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 +!!! failure "已在 sing-box 1.11.0 废弃" -将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 + 已移动到 [DNS 规则动作](../rule_action#route). ### 地址筛选字段 @@ -420,8 +473,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 #### mode +==必填== + `and` 或 `or` #### rules -包括的规则。 +==必填== + +包括的规则。 \ No newline at end of file diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index fe40d56518..43954a78c9 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -5,7 +5,10 @@ icon: material/new-box !!! quote "Changes in sing-box 1.11.0" :material-plus: [action](#action) - :material-alert: [outbound](#outbound) + :material-alert: [outbound](#outbound) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) !!! quote "Changes in sing-box 1.10.0" @@ -120,6 +123,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -322,6 +330,39 @@ Match user id. Match Clash mode. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 316339f677..e5c5f01776 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -5,7 +5,10 @@ icon: material/new-box !!! quote "sing-box 1.11.0 中的更改" :material-plus: [action](#action) - :material-alert: [outbound](#outbound) + :material-alert: [outbound](#outbound) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) !!! quote "sing-box 1.10.0 中的更改" @@ -13,7 +16,6 @@ icon: material/new-box :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [process_path_regex](#process_path_regex) - !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) @@ -118,6 +120,11 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -153,7 +160,7 @@ icon: material/new-box 当内容只有一项时,可以忽略 JSON 数组 [] 标签。 -### Default Fields +### 默认字段 !!! note "" @@ -320,6 +327,39 @@ icon: material/new-box 匹配 Clash 模式。 +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + #### wifi_ssid !!! quote "" @@ -366,13 +406,13 @@ icon: material/new-box ==必填== -参阅 [规则行动](../rule_action/)。 +参阅 [规则动作](../rule_action/)。 #### outbound !!! failure "已在 sing-box 1.11.0 废弃" - 已移动到 [规则行动](../rule_action#route). + 已移动到 [规则动作](../rule_action#route). ### 逻辑字段 diff --git a/docs/configuration/rule-set/adguard.zh.md b/docs/configuration/rule-set/adguard.zh.md new file mode 100644 index 0000000000..b08ab34b4d --- /dev/null +++ b/docs/configuration/rule-set/adguard.zh.md @@ -0,0 +1,70 @@ +--- +icon: material/new-box +--- + +# AdGuard DNS Filter + +!!! question "自 sing-box 1.10.0 起" + +sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box, +目前只有 AdGuard DNS Filter。 + +这些格式不直接作为源格式支持, +而是需要将它们转换为二进制规则集。 + +## 转换 + +使用 `sing-box rule-set convert --type adguard [--output .srs] .txt` 以转换为二进制规则集。 + +## 性能 + +AdGuard 将所有规则保存在内存中并按顺序匹配, +而 sing-box 选择高性能和较小的内存使用量。 +作为权衡,您无法知道匹配了哪个规则项。 + +## 兼容性 + +[AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter) +中的几乎所有规则以及 [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list) +中列出的规则集中的规则均受支持。 + +## 支持的格式 + +### AdGuard Filter + +#### 基本规则语法 + +| 语法 | 支持 | +|--------|------------------| +| `@@` | :material-check: | +| `\|\|` | :material-check: | +| `\|` | :material-check: | +| `^` | :material-check: | +| `*` | :material-check: | + +#### 主机语法 + +| 语法 | 示例 | 支持 | +|-------------|--------------------------|--------------------------| +| Scheme | `https://` | :material-alert: Ignored | +| Domain Host | `example.org` | :material-check: | +| IP Host | `1.1.1.1`, `10.0.0.` | :material-close: | +| Regexp | `/regexp/` | :material-check: | +| Port | `example.org:80` | :material-close: | +| Path | `example.org/path/ad.js` | :material-close: | + +#### 描述符语法 + +| 描述符 | 支持 | +|-----------------------|--------------------------| +| `$important` | :material-check: | +| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored | +| 任何其他描述符 | :material-close: | + +### Hosts + +只有 IP 地址为 `0.0.0.0` 的条目将被接受。 + +### 简易 + +当所有行都是有效域时,它们被视为简单的逐行域规则, 与 hosts 一样,只匹配完全相同的域。 \ No newline at end of file diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index 891b127840..bdad22f0dc 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -1,3 +1,13 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + ### Structure !!! question "Since sing-box 1.8.0" @@ -63,6 +73,11 @@ "package_name": [ "com.termux" ], + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, "wifi_ssid": [ "My WIFI" ], @@ -177,6 +192,39 @@ Match process path using regular expression. Match android package name. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/rule-set/headless-rule.zh.md b/docs/configuration/rule-set/headless-rule.zh.md new file mode 100644 index 0000000000..c5281504f6 --- /dev/null +++ b/docs/configuration/rule-set/headless-rule.zh.md @@ -0,0 +1,258 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [network_type](#network_type) + :material-alert: [network_is_expensive](#network_is_expensive) + :material-alert: [network_is_constrained](#network_is_constrained) + +### 结构 + +!!! question "自 sing-box 1.8.0 起" + +```json +{ + "rules": [ + { + "query_type": [ + "A", + "HTTPS", + 32768 + ], + "network": [ + "tcp" + ], + "domain": [ + "test.com" + ], + "domain_suffix": [ + ".cn" + ], + "domain_keyword": [ + "test" + ], + "domain_regex": [ + "^stun\\..+" + ], + "source_ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "source_port": [ + 12345 + ], + "source_port_range": [ + "1000:2000", + ":3000", + "4000:" + ], + "port": [ + 80, + 443 + ], + "port_range": [ + "1000:2000", + ":3000", + "4000:" + ], + "process_name": [ + "curl" + ], + "process_path": [ + "/usr/bin/curl" + ], + "process_path_regex": [ + "^/usr/bin/.+" + ], + "package_name": [ + "com.termux" + ], + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, + "wifi_ssid": [ + "My WIFI" + ], + "wifi_bssid": [ + "00:00:00:00:00:00" + ], + "invert": false + }, + { + "type": "logical", + "mode": "and", + "rules": [], + "invert": false + } + ] +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签。 + +### Default Fields + +!!! note "" + + 默认规则使用以下匹配逻辑: + (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `ip_cidr`) && + (`port` || `port_range`) && + (`source_port` || `source_port_range`) && + `other fields` + +#### query_type + +DNS 查询类型。值可以为整数或者类型名称字符串。 + +#### network + +`tcp` 或 `udp`。 + +#### domain + +匹配完整域名。 + +#### domain_suffix + +匹配域名后缀。 + +#### domain_keyword + +匹配域名关键字。 + +#### domain_regex + +匹配域名正则表达式。 + +#### source_ip_cidr + +匹配源 IP CIDR。 + +#### ip_cidr + +匹配 IP CIDR。 + +#### source_port + +匹配源端口。 + +#### source_port_range + +匹配源端口范围。 + +#### port + +匹配端口。 + +#### port_range + +匹配端口范围。 + +#### process_name + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS。 + +匹配进程名称。 + +#### process_path + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配进程路径。 + +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + +#### package_name + +匹配 Android 应用包名。 + +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + +#### wifi_ssid + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配 WiFi SSID。 + +#### wifi_bssid + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +#### invert + +反选匹配结果。 + +### 逻辑字段 + +#### type + +`logical` + +#### mode + +==必填== + +`and` 或 `or` + +#### rules + +==必填== + +包括的规则。 \ No newline at end of file diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index dfe71d9e39..bed3fb5466 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -74,7 +74,7 @@ Tag of rule-set. ==Required== -List of [Headless Rule](./headless-rule.md/). +List of [Headless Rule](../headless-rule/). ### Local or Remote Fields diff --git a/docs/configuration/rule-set/index.zh.md b/docs/configuration/rule-set/index.zh.md new file mode 100644 index 0000000000..083c06bd3a --- /dev/null +++ b/docs/configuration/rule-set/index.zh.md @@ -0,0 +1,117 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: `type: inline` + +# 规则集 + +!!! question "自 sing-box 1.8.0 起" + +### 结构 + +=== "内联" + + !!! question "自 sing-box 1.10.0 起" + + ```json + { + "type": "inline", // 可选 + "tag": "", + "rules": [] + } + ``` + +=== "本地文件" + + ```json + { + "type": "local", + "tag": "", + "format": "source", // or binary + "path": "" + } + ``` + +=== "远程文件" + + !!! info "" + + 远程规则集将被缓存如果 `experimental.cache_file.enabled` 已启用。 + + ```json + { + "type": "remote", + "tag": "", + "format": "source", // or binary + "url": "", + "download_detour": "", // 可选 + "update_interval": "" // 可选 + } + ``` + +### 字段 + +#### type + +==必填== + +规则集类型, `local` 或 `remote`。 + +#### tag + +==必填== + +规则集的标签。 + +### 内联字段 + +!!! question "自 sing-box 1.10.0 起" + +#### rules + +==必填== + +一组 [无头规则](../headless-rule/). + +### 本地或远程字段 + +#### format + +==必填== + +规则集格式, `source` 或 `binary`。 + +### 本地字段 + +#### path + +==必填== + +!!! note "" + + 自 sing-box 1.10.0 起,文件更改时将自动重新加载。 + +规则集的文件路径。 + +### 远程字段 + +#### url + +==必填== + +规则集的下载 URL。 + +#### download_detour + +用于下载规则集的出站的标签。 + +如果为空,将使用默认出站。 + +#### update_interval + +规则集的更新间隔。 + +默认使用 `1d`。 diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index 8c46cbf02b..43e8ea4535 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -4,6 +4,10 @@ icon: material/new-box # Source Format +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: version `3` + !!! quote "Changes in sing-box 1.10.0" :material-plus: version `2` @@ -14,7 +18,7 @@ icon: material/new-box ```json { - "version": 2, + "version": 3, "rules": [] } ``` @@ -29,19 +33,14 @@ Use `sing-box rule-set compile [--output .srs] .json` to c ==Required== -Version of rule-set, one of `1` or `2`. - -* 1: Initial rule-set version, since sing-box 1.8.0. -* 2: Optimized memory usages of `domain_suffix` rules. - -The new rule-set version `2` does not make any changes to the format, only affecting `binary` rule-sets compiled by command `rule-set compile` - -Since 1.10.0, the optimization is always applied to `source` rule-sets even if version is set to `1`. +Version of rule-set. -It is recommended to upgrade to `2` after sing-box 1.10.0 becomes a stable version. +* 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. #### rules ==Required== -List of [Headless Rule](./headless-rule.md/). +List of [Headless Rule](../headless-rule/). diff --git a/docs/configuration/rule-set/source-format.zh.md b/docs/configuration/rule-set/source-format.zh.md new file mode 100644 index 0000000000..0e8fc0cb96 --- /dev/null +++ b/docs/configuration/rule-set/source-format.zh.md @@ -0,0 +1,46 @@ +--- +icon: material/new-box +--- + +# 源文件格式 + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: version `3` + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: version `2` + +!!! question "自 sing-box 1.8.0 起" + +### 结构 + +```json +{ + "version": 3, + "rules": [] +} +``` + +### 编译 + +使用 `sing-box rule-set compile [--output .srs] .json` 以编译源文件为二进制规则集。 + +### 字段 + +#### version + +==必填== + +规则集版本。 + +* 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` 规则项。 + +#### rules + +==必填== + +一组 [无头规则](../headless-rule/). diff --git a/option/rule.go b/option/rule.go index d5ff9925a6..82a53f75a4 100644 --- a/option/rule.go +++ b/option/rule.go @@ -95,6 +95,9 @@ type RawDefaultRule struct { User badoption.Listable[string] `json:"user,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"` ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[string] `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"` diff --git a/option/rule_dns.go b/option/rule_dns.go index 6c9755fda8..33dc816c53 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -97,6 +97,9 @@ type RawDefaultDNSRule struct { UserID badoption.Listable[int32] `json:"user_id,omitempty"` Outbound badoption.Listable[string] `json:"outbound,omitempty"` ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[string] `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"` diff --git a/option/rule_set.go b/option/rule_set.go index fbb527ca55..9ccca4759b 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -146,25 +146,28 @@ 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"` - 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[string] `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"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` @@ -200,7 +203,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { var v any switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: v = r.Options default: return nil, E.New("unknown rule-set version: ", r.Version) @@ -215,7 +218,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { } var v any switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: v = &r.Options case 0: return E.New("missing rule-set version") @@ -231,7 +234,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) { switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: default: return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version)) } diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 12d9e96ae8..a5d47b4445 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -223,6 +223,21 @@ 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.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, options.NetworkType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.WIFISSID) > 0 { item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 1ec652b22a..4ac08a5a44 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -220,6 +220,21 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, options.NetworkType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.WIFISSID) > 0 { item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index 99488b2049..7f2dc5fca7 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -140,18 +140,33 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } - if len(options.WIFISSID) > 0 { - if networkManager != nil { + if networkManager != nil { + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, options.NetworkType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.WIFISSID) > 0 { item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) + } - } - if len(options.WIFIBSSID) > 0 { - if networkManager != nil { + if len(options.WIFIBSSID) > 0 { item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) + } } if len(options.AdGuardDomain) > 0 { diff --git a/route/rule/rule_item_network_is_constrained.go b/route/rule/rule_item_network_is_constrained.go new file mode 100644 index 0000000000..e0368b753f --- /dev/null +++ b/route/rule/rule_item_network_is_constrained.go @@ -0,0 +1,29 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*NetworkIsConstrainedItem)(nil) + +type NetworkIsConstrainedItem struct { + networkManager adapter.NetworkManager +} + +func NewNetworkIsConstrainedItem(networkManager adapter.NetworkManager) *NetworkIsConstrainedItem { + return &NetworkIsConstrainedItem{ + networkManager: networkManager, + } +} + +func (r *NetworkIsConstrainedItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return networkInterface.Constrained +} + +func (r *NetworkIsConstrainedItem) String() string { + return "network_is_expensive=true" +} diff --git a/route/rule/rule_item_network_is_expensive.go b/route/rule/rule_item_network_is_expensive.go new file mode 100644 index 0000000000..83e4f96f88 --- /dev/null +++ b/route/rule/rule_item_network_is_expensive.go @@ -0,0 +1,29 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*NetworkIsExpensiveItem)(nil) + +type NetworkIsExpensiveItem struct { + networkManager adapter.NetworkManager +} + +func NewNetworkIsExpensiveItem(networkManager adapter.NetworkManager) *NetworkIsExpensiveItem { + return &NetworkIsExpensiveItem{ + networkManager: networkManager, + } +} + +func (r *NetworkIsExpensiveItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return networkInterface.Expensive +} + +func (r *NetworkIsExpensiveItem) String() string { + return "network_is_expensive=true" +} diff --git a/route/rule/rule_item_network_type.go b/route/rule/rule_item_network_type.go new file mode 100644 index 0000000000..8ebdb25e9d --- /dev/null +++ b/route/rule/rule_item_network_type.go @@ -0,0 +1,39 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*NetworkTypeItem)(nil) + +type NetworkTypeItem struct { + networkManager adapter.NetworkManager + networkType []string +} + +func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []string) *NetworkTypeItem { + return &NetworkTypeItem{ + networkManager: networkManager, + networkType: networkType, + } +} + +func (r *NetworkTypeItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return common.Contains(r.networkType, networkInterface.Type) +} + +func (r *NetworkTypeItem) String() string { + if len(r.networkType) == 1 { + return F.ToString("network_type=", r.networkType[0]) + } else { + return F.ToString("network_type=", "["+strings.Join(F.MapToString(r.networkType), " ")+"]") + } +} From dcb10c21a10d0faa95e23a526f90682eb45cf02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 17:24:15 +0800 Subject: [PATCH 0168/1272] documentation: Merge route options to route actions --- docs/configuration/dns/rule_action.md | 32 ++++++------- docs/configuration/dns/rule_action.zh.md | 31 ++++++------- docs/configuration/route/rule_action.md | 28 ++++++------ docs/configuration/route/rule_action.zh.md | 25 ++++++----- docs/deprecated.md | 8 ---- docs/deprecated.zh.md | 7 --- docs/migration.md | 52 ---------------------- docs/migration.zh.md | 52 ---------------------- 8 files changed, 55 insertions(+), 180 deletions(-) diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index 8943b65351..cccc139679 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -12,8 +12,6 @@ icon: material/new-box { "action": "route", // default "server": "", - - // for compatibility "disable_cache": false, "rewrite_ttl": 0, "client_subnet": null @@ -28,23 +26,6 @@ icon: material/new-box Tag of target server. -#### disable_cache/rewrite_ttl/client_subnet - -!!! failure "Deprecated in sing-box 1.11.0" - - Legacy route options is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-legacy-dns-route-options-to-rule-actions). - -### route-options - -```json -{ - "action": "route-options", - "disable_cache": false, - "rewrite_ttl": null, - "client_subnet": null -} -``` - #### disable_cache Disable cache and save cache in this query. @@ -61,6 +42,19 @@ If value is an IP address instead of prefix, `/32` or `/128` will be appended au Will overrides `dns.client_subnet` and `servers.[].client_subnet`. +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null +} +``` + +`route-options` set options for routing. + ### reject ```json diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 8a9dc07eea..8fad238169 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -28,24 +28,6 @@ icon: material/new-box 目标 DNS 服务器的标签。 -#### disable_cache/rewrite_ttl/client_subnet - -!!! failure "自 sing-box 1.11.0 起" - - 旧的路由选项已弃用,且将在 sing-box 1.12.0 中移除,参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions). - -### route-options - -```json -{ - "action": "route-options", - "disable_cache": false, - "rewrite_ttl": null, - "client_subnet": null -} -``` - - #### disable_cache 在此查询中禁用缓存。 @@ -62,6 +44,19 @@ icon: material/new-box 将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null +} +``` + +`route-options` 为路由设置选项。 + ### reject ```json diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 843d756366..7804f5869b 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -13,7 +13,9 @@ icon: material/new-box ```json { "action": "route", // default - "outbound": "" + "outbound": "", + "udp_disable_domain_unmapping": false, + "udp_connect": false } ``` @@ -25,18 +27,6 @@ icon: material/new-box Tag of target outbound. -### route-options - -```json -{ - "action": "route-options", - "udp_disable_domain_unmapping": false, - "udp_connect": false -} -``` - -`route-options` set options for routing. - #### udp_disable_domain_unmapping If enabled, for UDP proxy requests addressed to a domain, @@ -49,6 +39,18 @@ do not support receiving UDP packets with domain addresses, such as Surge. If enabled, attempts to connect UDP connection to the destination instead of listen. +### route-options + +```json +{ + "action": "route-options", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +`route-options` set options for routing. + ### reject ```json diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index ae16d85fde..103a04bd5c 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -14,7 +14,8 @@ icon: material/new-box { "action": "route", // 默认 "outbound": "", - "udp_disable_domain_unmapping": false + "udp_disable_domain_unmapping": false, + "udp_connect": false } ``` @@ -26,16 +27,6 @@ icon: material/new-box 目标出站的标签。 -### route-options - -```json -{ - "action": "route-options", - "udp_disable_domain_unmapping": false, - "udp_connect": false -} -``` - #### udp_disable_domain_unmapping 如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 @@ -46,6 +37,18 @@ icon: material/new-box 如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 +### route-options + +```json +{ + "action": "route-options", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +`route-options` 为路由设置选项。 + ### reject ```json diff --git a/docs/deprecated.md b/docs/deprecated.md index f057319ade..d57c93ade4 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -22,14 +22,6 @@ check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions). Old fields will be removed in sing-box 1.13.0. -#### Legacy DNS route options - -Legacy DNS route options (`disable_cache`, `rewrite_ttl`, `client_subnet`) are deprecated -and can be replaced by rule actions, -check [Migration](../migration/#migrate-legacy-dns-route-options-to-rule-actions). - -Old fields will be removed in sing-box 1.12.0. - ## 1.10.0 #### TUN address fields are merged diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 2f7b28f7bd..76a426d279 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -20,13 +20,6 @@ icon: material/delete-alert 旧字段将在 sing-box 1.13.0 中被移除。 -#### 旧的 DNS 路由参数 - -旧的 DNS 路由参数(`disable_cache`、`rewrite_ttl`、`client_subnet`)已废弃且可以通过规则动作替代, -参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions)。 - -旧字段将在 sing-box 1.12.0 中被移除。 - ## 1.10.0 #### Match source 规则项已重命名 diff --git a/docs/migration.md b/docs/migration.md index 9207db5bc9..8f01c4f031 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -156,58 +156,6 @@ Inbound fields are deprecated and can be replaced by rule actions. } ``` -### Migrate legacy DNS route options to rule actions - -Legacy DNS route options are deprecated and can be replaced by rule actions. - -!!! info "References" - - [DNS Rule](/configuration/dns/rule/) / - [DNS Rule Action](/configuration/dns/rule_action/) - -=== ":material-card-remove: Deprecated" - - ```json - { - "dns": { - "rules": [ - { - ..., - - "server": "local", - "disable_cache": true, - "rewrite_ttl": 600, - "client_subnet": "1.1.1.1/24" - } - ] - } - } - ``` - -=== ":material-card-multiple: New" - - ```json - { - "dns": { - "rules": [ - { - ..., - - "action": "route-options", - "disable_cache": true, - "rewrite_ttl": 600, - "client_subnet": "1.1.1.1/24" - }, - { - ..., - - "server": "local" - } - ] - } - } - ``` - ## 1.10.0 ### TUN address fields are merged diff --git a/docs/migration.zh.md b/docs/migration.zh.md index f51860f720..dc62f37003 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -156,58 +156,6 @@ icon: material/arrange-bring-forward } ``` -### 迁移旧的 DNS 路由选项到规则动作 - -旧的 DNS 路由选项已被弃用,且可以被规则动作替代。 - -!!! info "参考" - - [DNS 规则](/zh/configuration/dns/rule/) / - [DNS 规则动作](/zh/configuration/dns/rule_action/) - -=== ":material-card-remove: 弃用的" - - ```json - { - "dns": { - "rules": [ - { - ..., - - "server": "local", - "disable_cache": true, - "rewrite_ttl": 600, - "client_subnet": "1.1.1.1/24" - } - ] - } - } - ``` - -=== ":material-card-multiple: 新的" - - ```json - { - "dns": { - "rules": [ - { - ..., - - "action": "route-options", - "disable_cache": true, - "rewrite_ttl": 600, - "client_subnet": "1.1.1.1/24" - }, - { - ..., - - "server": "local" - } - ] - } - } - ``` - ## 1.10.0 ### TUN 地址字段已合并 From bb46cdb2b3f1171c2f0841ac9529aec634f840ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 19:37:10 +0800 Subject: [PATCH 0169/1272] Add multi network dialing --- adapter/inbound.go | 4 + adapter/network.go | 14 +- adapter/outbound/default.go | 116 ++-------- common/dialer/default.go | 208 ++++++++++++----- common/dialer/default_go1.20.go | 4 + common/dialer/default_nongo1.20.go | 4 + common/dialer/default_parallel_interface.go | 241 ++++++++++++++++++++ common/dialer/default_parallel_network.go | 122 ++++++++++ common/dialer/dialer.go | 36 +++ common/dialer/resolve.go | 89 +++++++- common/settings/proxy_darwin.go | 10 +- constant/network.go | 42 ++++ docs/configuration/route/index.md | 45 +++- docs/configuration/route/index.zh.md | 30 ++- docs/configuration/route/rule_action.md | 29 ++- docs/configuration/route/rule_action.zh.md | 29 ++- docs/configuration/shared/dial.md | 57 ++++- docs/configuration/shared/dial.zh.md | 59 ++++- experimental/libbox/monitor.go | 4 +- option/outbound.go | 32 +-- option/route.go | 24 +- option/rule_action.go | 14 +- option/types.go | 21 ++ protocol/direct/outbound.go | 131 +++++++++-- protocol/socks/outbound.go | 21 -- protocol/wireguard/outbound.go | 13 -- route/network.go | 121 ++++++---- route/route.go | 4 + route/rule/rule_action.go | 6 + transport/dhcp/server.go | 2 +- 30 files changed, 1182 insertions(+), 350 deletions(-) create mode 100644 common/dialer/default_parallel_interface.go create mode 100644 common/dialer/default_parallel_network.go diff --git a/adapter/inbound.go b/adapter/inbound.go index 7932237d03..33b1b4d164 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -3,8 +3,10 @@ package adapter import ( "context" "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" M "github.com/sagernet/sing/common/metadata" @@ -66,6 +68,8 @@ type InboundContext struct { InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool UDPConnect bool + NetworkStrategy C.NetworkStrategy + FallbackDelay time.Duration DNSServer string diff --git a/adapter/network.go b/adapter/network.go index 6c09a0a3e1..dd924c339f 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -1,6 +1,9 @@ package adapter import ( + "time" + + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" ) @@ -11,10 +14,10 @@ type NetworkManager interface { UpdateInterfaces() error DefaultNetworkInterface() *NetworkInterface NetworkInterfaces() []NetworkInterface - DefaultInterface() string AutoDetectInterface() bool AutoDetectInterfaceFunc() control.Func - DefaultMark() uint32 + ProtectFunc() control.Func + DefaultOptions() NetworkOptions RegisterAutoRedirectOutputMark(mark uint32) error AutoRedirectOutputMark() uint32 NetworkMonitor() tun.NetworkUpdateMonitor @@ -24,6 +27,13 @@ type NetworkManager interface { ResetNetwork() } +type NetworkOptions struct { + DefaultNetworkStrategy C.NetworkStrategy + DefaultFallbackDelay time.Duration + DefaultInterface string + DefaultMark uint32 +} + type InterfaceUpdateListener interface { InterfaceUpdated() } diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go index bb58ff54d9..84be8abaf1 100644 --- a/adapter/outbound/default.go +++ b/adapter/outbound/default.go @@ -8,8 +8,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -25,35 +25,11 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a var outConn net.Conn var err error if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) - } else { - outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - err = N.ReportConnHandshakeSuccess(conn, outConn) - if err != nil { - outConn.Close() - return err - } - return CopyEarlyConn(ctx, conn, outConn) -} - -func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { - defer conn.Close() - ctx = adapter.WithContext(ctx, &metadata) - var outConn net.Conn - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) - if err != nil { - return N.ReportHandshakeFailure(conn, err) + if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { + outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + } else { + outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) } - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, destinationAddresses) } else { outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) } @@ -79,7 +55,11 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, ) if metadata.UDPConnect { if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { + outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + } else { + outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + } } else { outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) } @@ -93,7 +73,11 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, } } else { if len(metadata.DestinationAddresses) > 0 { - outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { + outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + } else { + outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) + } } else { outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) } @@ -129,76 +113,6 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn)) } -func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { - defer conn.Close() - ctx = adapter.WithContext(ctx, &metadata) - var ( - outPacketConn net.PacketConn - outConn net.Conn - destinationAddress netip.Addr - err error - ) - if metadata.UDPConnect { - if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, destinationAddresses) - } else { - outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr()) - if connRemoteAddr != metadata.Destination.Addr { - destinationAddress = connRemoteAddr - } - } else { - if len(metadata.DestinationAddresses) > 0 { - outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses) - } else { - outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - } - err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn) - if err != nil { - outPacketConn.Close() - return err - } - if destinationAddress.IsValid() { - if metadata.Destination.IsFqdn() { - outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) - } - if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { - natConn.UpdateDestination(destinationAddress) - } - } - switch metadata.Protocol { - case C.ProtocolSTUN: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout) - case C.ProtocolQUIC: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout) - case C.ProtocolDNS: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) - } - return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn)) -} - func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error { if cachedReader, isCached := conn.(N.CachedReader); isCached { payload := cachedReader.ReadCached() diff --git a/common/dialer/default.go b/common/dialer/default.go index daf7995037..c7329f2e1a 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -10,66 +10,93 @@ import ( "github.com/sagernet/sing-box/common/conntrack" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var _ WireGuardListener = (*DefaultDialer)(nil) +var ( + _ ParallelInterfaceDialer = (*DefaultDialer)(nil) + _ WireGuardListener = (*DefaultDialer)(nil) +) type DefaultDialer struct { - dialer4 tcpDialer - dialer6 tcpDialer - udpDialer4 net.Dialer - udpDialer6 net.Dialer - udpListener net.ListenConfig - udpAddr4 string - udpAddr6 string - isWireGuardListener bool + dialer4 tcpDialer + dialer6 tcpDialer + udpDialer4 net.Dialer + udpDialer6 net.Dialer + udpListener net.ListenConfig + udpAddr4 string + udpAddr6 string + isWireGuardListener bool + networkManager adapter.NetworkManager + networkStrategy C.NetworkStrategy + networkFallbackDelay time.Duration + networkLastFallback atomic.TypedValue[time.Time] } func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) { - var dialer net.Dialer - var listener net.ListenConfig + var ( + dialer net.Dialer + listener net.ListenConfig + interfaceFinder control.InterfaceFinder + networkStrategy C.NetworkStrategy + networkFallbackDelay time.Duration + ) + if networkManager != nil { + interfaceFinder = networkManager.InterfaceFinder() + } else { + interfaceFinder = control.NewDefaultInterfaceFinder() + } if options.BindInterface != "" { - var interfaceFinder control.InterfaceFinder - if networkManager != nil { - interfaceFinder = networkManager.InterfaceFinder() - } else { - interfaceFinder = control.NewDefaultInterfaceFinder() - } bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) - } else if networkManager != nil && networkManager.AutoDetectInterface() { - bindFunc := networkManager.AutoDetectInterfaceFunc() - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) - } else if networkManager != nil && networkManager.DefaultInterface() != "" { - bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), networkManager.DefaultInterface(), -1) - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) - } - var autoRedirectOutputMark uint32 - if networkManager != nil { - autoRedirectOutputMark = networkManager.AutoRedirectOutputMark() - } - if autoRedirectOutputMark > 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark)) - listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark)) } if options.RoutingMark > 0 { dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) + } + if networkManager != nil { + autoRedirectOutputMark := networkManager.AutoRedirectOutputMark() if autoRedirectOutputMark > 0 { - return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`") + if options.RoutingMark > 0 { + return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set") + } + dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark)) + listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark)) } - } else if networkManager != nil && networkManager.DefaultMark() > 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(networkManager.DefaultMark())) - listener.Control = control.Append(listener.Control, control.RoutingMark(networkManager.DefaultMark())) - if autoRedirectOutputMark > 0 { - return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`") + } + if C.NetworkStrategy(options.NetworkStrategy) != C.NetworkStrategyDefault { + if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil { + return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`") + } + networkStrategy = C.NetworkStrategy(options.NetworkStrategy) + networkFallbackDelay = time.Duration(options.NetworkFallbackDelay) + if networkManager == nil || !networkManager.AutoDetectInterface() { + return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") + } + } + if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil { + defaultOptions := networkManager.DefaultOptions() + if defaultOptions.DefaultInterface != "" { + bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.DefaultInterface, -1) + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } else if networkManager.AutoDetectInterface() { + if defaultOptions.DefaultNetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault { + networkStrategy = defaultOptions.DefaultNetworkStrategy + networkFallbackDelay = defaultOptions.DefaultFallbackDelay + bindFunc := networkManager.ProtectFunc() + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } else { + bindFunc := networkManager.AutoDetectInterfaceFunc() + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } } } if options.ReuseAddr { @@ -130,6 +157,9 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti listener.Control = control.Append(listener.Control, controlFn) } } + if networkStrategy != C.NetworkStrategyDefault && options.TCPFastOpen { + return nil, E.New("`tcp_fast_open` is conflict with `network_strategy` or `route.default_network_strategy`") + } tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen) if err != nil { return nil, err @@ -139,14 +169,17 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti return nil, err } return &DefaultDialer{ - tcpDialer4, - tcpDialer6, - udpDialer4, - udpDialer6, - listener, - udpAddr4, - udpAddr6, - options.IsWireGuardListener, + dialer4: tcpDialer4, + dialer6: tcpDialer6, + udpDialer4: udpDialer4, + udpDialer6: udpDialer6, + udpListener: listener, + udpAddr4: udpAddr4, + udpAddr6: udpAddr6, + isWireGuardListener: options.IsWireGuardListener, + networkManager: networkManager, + networkStrategy: networkStrategy, + networkFallbackDelay: networkFallbackDelay, }, nil } @@ -154,33 +187,88 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address if !address.IsValid() { return nil, E.New("invalid address") } - switch N.NetworkName(network) { - case N.NetworkUDP: + if d.networkStrategy == C.NetworkStrategyDefault { + switch N.NetworkName(network) { + case N.NetworkUDP: + if !address.IsIPv6() { + return trackConn(d.udpDialer4.DialContext(ctx, network, address.String())) + } else { + return trackConn(d.udpDialer6.DialContext(ctx, network, address.String())) + } + } if !address.IsIPv6() { - return trackConn(d.udpDialer4.DialContext(ctx, network, address.String())) + return trackConn(DialSlowContext(&d.dialer4, ctx, network, address)) } else { - return trackConn(d.udpDialer6.DialContext(ctx, network, address.String())) + return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) } + } else { + return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkFallbackDelay) + } +} + +func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + if strategy == C.NetworkStrategyDefault { + return d.DialContext(ctx, network, address) } - if !address.IsIPv6() { - return trackConn(DialSlowContext(&d.dialer4, ctx, network, address)) + if !d.networkManager.AutoDetectInterface() { + return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") + } + var dialer net.Dialer + if N.NetworkName(network) == N.NetworkTCP { + dialer = dialerFromTCPDialer(d.dialer4) + } else { + dialer = d.udpDialer4 + } + fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout + var ( + conn net.Conn + isPrimary bool + err error + ) + if !fastFallback { + conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, fallbackDelay) } else { - return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) + conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, fallbackDelay, d.networkLastFallback.Store) } + if err != nil { + return nil, err + } + if !fastFallback && !isPrimary { + d.networkLastFallback.Store(time.Now()) + } + return trackConn(conn, nil) } func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - if destination.IsIPv6() { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)) - } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)) + if d.networkStrategy == C.NetworkStrategyDefault { + if destination.IsIPv6() { + return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)) + } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { + return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)) + } else { + return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)) + } } else { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)) + return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkFallbackDelay) + } +} + +func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { + if strategy == C.NetworkStrategyDefault { + return d.ListenPacket(ctx, destination) + } + if !d.networkManager.AutoDetectInterface() { + return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") + } + network := N.NetworkUDP + if destination.IsIPv4() && !destination.Addr.IsUnspecified() { + network += "4" } + return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, fallbackDelay)) } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - return d.udpListener.ListenPacket(context.Background(), network, address) + return d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkFallbackDelay) } func trackConn(conn net.Conn, err error) (net.Conn, error) { diff --git a/common/dialer/default_go1.20.go b/common/dialer/default_go1.20.go index a9f7b612b8..9dde955f86 100644 --- a/common/dialer/default_go1.20.go +++ b/common/dialer/default_go1.20.go @@ -13,3 +13,7 @@ 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_nongo1.20.go b/common/dialer/default_nongo1.20.go index 215024245a..b2e4638d16 100644 --- a/common/dialer/default_nongo1.20.go +++ b/common/dialer/default_nongo1.20.go @@ -16,3 +16,7 @@ func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { } return dialer, nil } + +func dialerFromTCPDialer(dialer tcpDialer) net.Dialer { + return dialer +} diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go new file mode 100644 index 0000000000..baf0349ec8 --- /dev/null +++ b/common/dialer/default_parallel_interface.go @@ -0,0 +1,241 @@ +package dialer + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + N "github.com/sagernet/sing/common/network" +) + +func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, bool, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) + if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { + return nil, false, E.New("no available network interface") + } + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + returned := make(chan struct{}) + defer close(returned) + type dialResult struct { + net.Conn + error + primary bool + } + results := make(chan dialResult) // unbuffered + startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) { + perNetDialer := dialer + perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index)) + conn, err := perNetDialer.DialContext(ctx, network, addr) + if err != nil { + select { + case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}: + case <-returned: + } + } else { + select { + case results <- dialResult{Conn: conn}: + case <-returned: + conn.Close() + } + } + } + primaryCtx, primaryCancel := context.WithCancel(ctx) + defer primaryCancel() + for _, iif := range primaryInterfaces { + go startRacer(primaryCtx, true, iif) + } + var ( + fallbackTimer *time.Timer + fallbackChan <-chan time.Time + ) + if len(fallbackInterfaces) > 0 { + fallbackTimer = time.NewTimer(fallbackDelay) + defer fallbackTimer.Stop() + fallbackChan = fallbackTimer.C + } + var errors []error + for { + select { + case <-fallbackChan: + fallbackCtx, fallbackCancel := context.WithCancel(ctx) + defer fallbackCancel() + for _, iif := range fallbackInterfaces { + go startRacer(fallbackCtx, false, iif) + } + case res := <-results: + if res.error == nil { + return res.Conn, res.primary, nil + } + errors = append(errors, res.error) + if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) { + return nil, false, E.Errors(errors...) + } + if res.primary && fallbackTimer != nil && fallbackTimer.Stop() { + fallbackTimer.Reset(0) + } + } + } +} + +func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) + if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { + return nil, false, E.New("no available network interface") + } + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + returned := make(chan struct{}) + defer close(returned) + type dialResult struct { + net.Conn + error + primary bool + } + startAt := time.Now() + results := make(chan dialResult) // unbuffered + startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) { + perNetDialer := dialer + perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index)) + conn, err := perNetDialer.DialContext(ctx, network, addr) + if err != nil { + select { + case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}: + case <-returned: + } + } else { + select { + case results <- dialResult{Conn: conn}: + case <-returned: + if primary && time.Since(startAt) <= fallbackDelay { + resetFastFallback(time.Time{}) + } + conn.Close() + } + } + } + for _, iif := range primaryInterfaces { + go startRacer(ctx, true, iif) + } + fallbackCtx, fallbackCancel := context.WithCancel(ctx) + defer fallbackCancel() + for _, iif := range fallbackInterfaces { + go startRacer(fallbackCtx, false, iif) + } + var errors []error + for { + select { + case res := <-results: + if res.error == nil { + return res.Conn, res.primary, nil + } + errors = append(errors, res.error) + if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) { + return nil, false, E.Errors(errors...) + } + } + } +} + +func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) + if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { + return nil, E.New("no available network interface") + } + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + var errors []error + for _, primaryInterface := range primaryInterfaces { + perNetListener := listener + perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index)) + conn, err := perNetListener.ListenPacket(ctx, network, addr) + if err == nil { + return conn, nil + } + errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Name, ")")) + } + for _, fallbackInterface := range fallbackInterfaces { + perNetListener := listener + perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index)) + conn, err := perNetListener.ListenPacket(ctx, network, addr) + if err == nil { + return conn, nil + } + errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Name, ")")) + } + return nil, E.Errors(errors...) +} + +func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) { + interfaces := networkManager.NetworkInterfaces() + switch strategy { + case C.NetworkStrategyFallback: + defaultIf := networkManager.InterfaceMonitor().DefaultInterface() + if defaultIf != nil { + for _, iif := range interfaces { + if iif.Index == defaultIf.Index { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } + } + } else { + primaryInterfaces = interfaces + } + case C.NetworkStrategyHybrid: + primaryInterfaces = interfaces + case C.NetworkStrategyWIFI: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeWIFI { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } + } + case C.NetworkStrategyCellular: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeCellular { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } + } + case C.NetworkStrategyEthernet: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeEthernet { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } + } + case C.NetworkStrategyWIFIOnly: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeWIFI { + primaryInterfaces = append(primaryInterfaces, iif) + } + } + case C.NetworkStrategyCellularOnly: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeCellular { + primaryInterfaces = append(primaryInterfaces, iif) + } + } + case C.NetworkStrategyEthernetOnly: + for _, iif := range interfaces { + if iif.Type == C.InterfaceTypeEthernet { + primaryInterfaces = append(primaryInterfaces, iif) + } + } + default: + panic(F.ToString("unknown network strategy: ", strategy)) + } + return primaryInterfaces, fallbackInterfaces +} diff --git a/common/dialer/default_parallel_network.go b/common/dialer/default_parallel_network.go new file mode 100644 index 0000000000..f42d9330c7 --- /dev/null +++ b/common/dialer/default_parallel_network.go @@ -0,0 +1,122 @@ +package dialer + +import ( + "context" + "net" + "net/netip" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { + return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, fallbackDelay) + } + var errors []error + for _, address := range destinationAddresses { + conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay) + if err == nil { + return conn, nil + } + errors = append(errors, err) + } + return nil, E.Errors(errors...) +} + +func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + + returned := make(chan struct{}) + defer close(returned) + + addresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool { + return address.Is4() || address.Is4In6() + }) + addresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool { + return address.Is6() && !address.Is4In6() + }) + if len(addresses4) == 0 || len(addresses6) == 0 { + return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, fallbackDelay) + } + var primaries, fallbacks []netip.Addr + if preferIPv6 { + primaries = addresses6 + fallbacks = addresses4 + } else { + primaries = addresses4 + fallbacks = addresses6 + } + type dialResult struct { + net.Conn + error + primary bool + done bool + } + results := make(chan dialResult) // unbuffered + startRacer := func(ctx context.Context, primary bool) { + ras := primaries + if !primary { + ras = fallbacks + } + c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, fallbackDelay) + select { + case results <- dialResult{Conn: c, error: err, primary: primary, done: true}: + case <-returned: + if c != nil { + c.Close() + } + } + } + var primary, fallback dialResult + primaryCtx, primaryCancel := context.WithCancel(ctx) + defer primaryCancel() + go startRacer(primaryCtx, true) + fallbackTimer := time.NewTimer(fallbackDelay) + defer fallbackTimer.Stop() + for { + select { + case <-fallbackTimer.C: + fallbackCtx, fallbackCancel := context.WithCancel(ctx) + defer fallbackCancel() + go startRacer(fallbackCtx, false) + + case res := <-results: + if res.error == nil { + return res.Conn, nil + } + if res.primary { + primary = res + } else { + fallback = res + } + if primary.done && fallback.done { + return nil, primary.error + } + if res.primary && fallbackTimer.Stop() { + fallbackTimer.Reset(0) + } + } + } +} + +func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { + if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { + return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, fallbackDelay) + } + var errors []error + for _, address := range destinationAddresses { + conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay) + if err == nil { + return conn, address, nil + } + errors = append(errors, err) + } + return nil, netip.Addr{}, E.Errors(errors...) +} diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 047a25146b..b3305d7397 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -2,12 +2,16 @@ package dialer import ( "context" + "net" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" ) @@ -49,3 +53,35 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { } return dialer, nil } + +func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInterfaceDialer, error) { + if options.Detour != "" { + return nil, E.New("`detour` is not supported in direct context") + } + networkManager := service.FromContext[adapter.NetworkManager](ctx) + if options.IsWireGuardListener { + return NewDefault(networkManager, options) + } + dialer, err := NewDefault(networkManager, options) + if err != nil { + return nil, err + } + return NewResolveParallelInterfaceDialer( + service.FromContext[adapter.Router](ctx), + dialer, + true, + dns.DomainStrategy(options.DomainStrategy), + time.Duration(options.FallbackDelay), + ), nil +} + +type ParallelInterfaceDialer interface { + N.Dialer + DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) +} + +type ParallelNetworkDialer interface { + DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) +} diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index f2ee50db5a..ce17923c40 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -7,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/bufio" @@ -14,7 +15,12 @@ import ( N "github.com/sagernet/sing/common/network" ) -type ResolveDialer struct { +var ( + _ N.Dialer = (*resolveDialer)(nil) + _ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil) +) + +type resolveDialer struct { dialer N.Dialer parallel bool router adapter.Router @@ -22,8 +28,8 @@ type ResolveDialer struct { fallbackDelay time.Duration } -func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer { - return &ResolveDialer{ +func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) N.Dialer { + return &resolveDialer{ dialer, parallel, router, @@ -32,7 +38,25 @@ func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, str } } -func (d *ResolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +type resolveParallelNetworkDialer struct { + resolveDialer + dialer ParallelInterfaceDialer +} + +func NewResolveParallelInterfaceDialer(router adapter.Router, dialer ParallelInterfaceDialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer { + return &resolveParallelNetworkDialer{ + resolveDialer{ + dialer, + parallel, + router, + strategy, + fallbackDelay, + }, + dialer, + } +} + +func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } @@ -57,7 +81,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina } } -func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } @@ -82,6 +106,59 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } -func (d *ResolveDialer) Upstream() any { +func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + if !destination.IsFqdn() { + return d.dialer.DialContext(ctx, network, destination) + } + ctx, metadata := adapter.ExtendContext(ctx) + ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) + metadata.Destination = destination + metadata.Domain = "" + var addresses []netip.Addr + var err error + if d.strategy == dns.DomainStrategyAsIS { + addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) + } else { + addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) + } + if err != nil { + return nil, err + } + if fallbackDelay == 0 { + fallbackDelay = d.fallbackDelay + } + if d.parallel { + return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, fallbackDelay) + } else { + return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, fallbackDelay) + } +} + +func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { + if !destination.IsFqdn() { + return d.dialer.ListenPacket(ctx, destination) + } + ctx, metadata := adapter.ExtendContext(ctx) + ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) + metadata.Destination = destination + metadata.Domain = "" + var addresses []netip.Addr + var err error + if d.strategy == dns.DomainStrategyAsIS { + addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) + } else { + addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) + } + if err != nil { + return nil, err + } + conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, fallbackDelay) + if err != nil { + return nil, err + } + return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil +} + +func (d *resolveDialer) Upstream() any { return d.dialer } diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index 3c06a85369..53ed0fe03c 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/shell" @@ -33,7 +34,7 @@ func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bo serverAddr: serverAddr, supportSOCKS: supportSOCKS, } - proxy.element = interfaceMonitor.RegisterCallback(proxy.update) + proxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate) return proxy, nil } @@ -65,11 +66,8 @@ func (p *DarwinSystemProxy) Disable() error { return err } -func (p *DarwinSystemProxy) update(event int) { - if event&tun.EventInterfaceUpdate == 0 { - return - } - if !p.isEnabled { +func (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) { + if !p.isEnabled || defaultInterface == nil { return } _ = p.update0() diff --git a/constant/network.go b/constant/network.go index f5ac2a4e47..c026b7b15b 100644 --- a/constant/network.go +++ b/constant/network.go @@ -1,8 +1,50 @@ package constant +import ( + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" +) + const ( InterfaceTypeWIFI = "wifi" InterfaceTypeCellular = "cellular" InterfaceTypeEthernet = "ethernet" InterfaceTypeOther = "other" ) + +type NetworkStrategy int + +const ( + NetworkStrategyDefault NetworkStrategy = iota + NetworkStrategyFallback + NetworkStrategyHybrid + NetworkStrategyWIFI + NetworkStrategyCellular + NetworkStrategyEthernet + NetworkStrategyWIFIOnly + NetworkStrategyCellularOnly + NetworkStrategyEthernetOnly +) + +var ( + NetworkStrategyToString = map[NetworkStrategy]string{ + NetworkStrategyDefault: "default", + NetworkStrategyFallback: "fallback", + NetworkStrategyHybrid: "hybrid", + NetworkStrategyWIFI: "wifi", + NetworkStrategyCellular: "cellular", + NetworkStrategyEthernet: "ethernet", + NetworkStrategyWIFIOnly: "wifi_only", + NetworkStrategyCellularOnly: "cellular_only", + NetworkStrategyEthernetOnly: "ethernet_only", + } + StringToNetworkStrategy = common.ReverseMap(NetworkStrategyToString) +) + +func (s NetworkStrategy) String() string { + name, loaded := NetworkStrategyToString[s] + if !loaded { + return F.ToString(int(s)) + } + return name +} diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 507cb140b6..2b035eb426 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -1,5 +1,14 @@ +--- +icon: material/new-box +--- + # Route +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [default_network_strategy](#default_network_strategy) + :material-alert: [default_fallback_delay](#default_fallback_delay) + !!! quote "Changes in sing-box 1.8.0" :material-plus: [rule_set](#rule_set) @@ -18,16 +27,18 @@ "final": "", "auto_detect_interface": false, "override_android_vpn": false, - "default_interface": "en0", - "default_mark": 233 + "default_interface": "", + "default_mark": 0, + "default_network_strategy": "", + "default_fallback_delay": "" } } ``` ### Fields -| Key | Format | -|-----------|----------------------| +| Key | Format | +|-----------|-----------------------| | `geoip` | [GeoIP](./geoip/) | | `geosite` | [Geosite](./geosite/) | @@ -81,4 +92,28 @@ Takes no effect if `auto_detect_interface` is set. Set routing mark by default. -Takes no effect if `outbound.routing_mark` is set. \ No newline at end of file +Takes no effect if `outbound.routing_mark` is set. + +#### default_network_strategy + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. + +Strategy for selecting network interfaces. + +Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set. + +Can be overrides by `outbound.network_strategy`. + +Conflicts with `default_interface`. + +See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values. + +#### default_fallback_delay + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set. + +See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. \ No newline at end of file diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md index 68d4f66d91..b00237c450 100644 --- a/docs/configuration/route/index.zh.md +++ b/docs/configuration/route/index.zh.md @@ -18,8 +18,10 @@ "final": "", "auto_detect_interface": false, "override_android_vpn": false, - "default_interface": "en0", - "default_mark": 233 + "default_interface": "", + "default_mark": 0, + "default_network_strategy": "", + "default_fallback_delay": "" } } ``` @@ -82,3 +84,27 @@ 默认为出站连接设置路由标记。 如果设置了 `outbound.routing_mark` 设置,则不生效。 + +#### network_strategy + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`。 + +选择网络接口的策略。 + +当 `outbound.bind_interface`, `outbound.inet4_bind_address` 或 `outbound.inet6_bind_address` 已设置时不生效。 + +可以被 `outbound.network_strategy` 覆盖。 + +与 `default_interface` 冲突。 + +可用值请参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 + +#### fallback_delay + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface` 且 `network_strategy` 已设置。 + +详情请参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 7804f5869b..5e9cfbc220 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -2,10 +2,6 @@ icon: material/new-box --- -# Rule Action - -!!! question "Since sing-box 1.11.0" - ## Final actions ### route @@ -14,6 +10,8 @@ icon: material/new-box { "action": "route", // default "outbound": "", + "network_strategy": "", + "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } @@ -27,6 +25,27 @@ icon: material/new-box Tag of target outbound. +#### network_strategy + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. + +Strategy for selecting network interfaces. + +Only take effect if outbound is direct without `outbound.bind_interface`, +`outbound.inet4_bind_address` and `outbound.inet6_bind_address` set. + +See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values. + +#### fallback_delay + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set. + +See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. + #### udp_disable_domain_unmapping If enabled, for UDP proxy requests addressed to a domain, @@ -44,6 +63,8 @@ If enabled, attempts to connect UDP connection to the destination instead of lis ```json { "action": "route-options", + "network_strategy": "", + "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 103a04bd5c..52965fd240 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -2,10 +2,6 @@ icon: material/new-box --- -# 规则动作 - -!!! question "自 sing-box 1.11.0 起" - ## 最终动作 ### route @@ -14,6 +10,8 @@ icon: material/new-box { "action": "route", // 默认 "outbound": "", + "network_strategy": "", + "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } @@ -27,6 +25,27 @@ icon: material/new-box 目标出站的标签。 +#### network_strategy + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`。 + +选择网络接口的策略。 + +仅当出站为 `direct` 且 `outbound.bind_interface`, `outbound.inet4_bind_address` +且 `outbound.inet6_bind_address` 未设置时生效。 + +可用值参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 + +#### fallback_delay + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface` 且 `network_strategy` 已设置。 + +详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 + #### udp_disable_domain_unmapping 如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 @@ -42,6 +61,8 @@ icon: material/new-box ```json { "action": "route-options", + "network_strategy": "", + "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 8139c7518c..e67bf61623 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [network_strategy](#network_strategy) + :material-alert: [fallback_delay](#fallback_delay) + ### Structure ```json @@ -13,20 +22,19 @@ "tcp_multi_path": false, "udp_fragment": false, "domain_strategy": "prefer_ipv6", + "network_strategy": "default", "fallback_delay": "300ms" } ``` ### Fields -| Field | Available Context | -|------------------------------------------------------------------------------------------------------------------------------------------|-------------------| -| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_multi_path` / `udp_fragment` /`connect_timeout` | `detour` not set | - #### detour The tag of the upstream outbound. +If enabled, all other fields will be ignored. + #### bind_interface The network interface to bind to. @@ -78,7 +86,7 @@ Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". #### domain_strategy -One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. +Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. If set, the requested domain name will be resolved to IP before connect. @@ -87,11 +95,44 @@ If set, the requested domain name will be resolved to IP before connect. | `direct` | Domain in request | Take `inbound.domain_strategy` if not set | | others | Domain in server address | / | +#### network_strategy + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. + +Strategy for selecting network interfaces. + +Available values: + +- `default` (default): Connect to the default interface. +- `fallback`: Try all other interfaces when timeout. +- `hybrid`: Connect to all interfaces concurrently and choose the fastest one. +- `wifi`: Prioritize WIFI, but try all other interfaces when unavailable or timeout. +- `cellular`: Prioritize Cellular, but try all other interfaces when unavailable or timeout. +- `ethernet`: Prioritize Ethernet, but try all other interfaces when unavailable or timeout. +- `wifi_only`: Connect to WIFI only. +- `cellular_only`: Connect to Cellular only. +- `ethernet_only`: Connect to Ethernet only. + +For fallback strategies, when preferred interfaces fails or times out, +it will enter a 15s fast fallback state (upgraded to `hybrid`), +and exit immediately if recovers. + +Conflicts with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`. + #### fallback_delay The length of time to wait before spawning a RFC 6555 Fast Fallback connection. -That is, is the amount of time to wait for connection to succeed before assuming + +For `domain_strategy`, is the amount of time to wait for connection to succeed before assuming that IPv4/IPv6 is misconfigured and falling back to other type of addresses. -If zero, a default delay of 300ms is used. -Only take effect when `domain_strategy` is set. \ No newline at end of file +For `network_strategy`, is the amount of time to wait for connection to succeed before falling +back to other interfaces. + +Only take effect when `domain_strategy` or `network_strategy` is set. + +`300ms` is used by default. diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index ccb61c7a44..4e20f106e8 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [network_strategy](#network_strategy) + :material-alert: [fallback_delay](#fallback_delay) + ### 结构 ```json @@ -13,17 +22,13 @@ "tcp_multi_path": false, "udp_fragment": false, "domain_strategy": "prefer_ipv6", + "network_strategy": "", "fallback_delay": "300ms" } ``` ### 字段 -| 字段 | 可用上下文 | -|------------------------------------------------------------------------------------------------------------------------------------------|--------------| -| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_mutli_path` / `udp_fragment` /`connect_timeout` | `detour` 未设置 | - - #### detour 上游出站的标签。 @@ -83,15 +88,45 @@ 如果设置,域名将在请求发出之前解析为 IP。 -| 出站 | 受影响的域名 | 默认回退值 | -|----------|--------------------------|-------------------------------------------| -| `direct` | 请求中的域名 | `inbound.domain_strategy` | -| others | 服务器地址中的域名 | / | +| 出站 | 受影响的域名 | 默认回退值 | +|----------|-----------|---------------------------| +| `direct` | 请求中的域名 | `inbound.domain_strategy` | +| others | 服务器地址中的域名 | / | + +#### network_strategy + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 iOS 平台图形客户端中支持。 + +用于选择网络接口的策略。 + +可用值: + +- `default` (默认): 连接到默认接口, +- `fallback`: 如果超时,尝试所有剩余接口。 +- `hybrid`: 同时尝试所有接口,选择最快的一个。 +- `wifi`: 优先使用 WIFI,但在不可用或超时时尝试所有其他接口。 +- `cellular`: 优先使用蜂窝数据,但在不可用或超时时尝试所有其他接口。 +- `ethernet`: 优先使用以太网,但在不可用或超时时尝试所有其他接口。 +- `wifi_only`: 仅连接到 WIFI。 +- `cellular_only`: 仅连接到蜂窝数据。 +- `ethernet_only`: 仅连接到以太网。 + +对于回退策略, 当优先使用的接口发生故障或超时时, 将进入 15 秒的快速回退状态(升级为 `hybrid`), 且恢复后立即退出。 + +与 `bind_interface`, `bind_inet4_address` 和 `bind_inet6_address` 冲突。 #### fallback_delay 在生成 RFC 6555 快速回退连接之前等待的时间长度。 -也就是说,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。 -如果为零,则使用 300 毫秒的默认延迟。 -仅当 `domain_strategy` 为 `prefer_ipv4` 或 `prefer_ipv6` 时生效。 +对于 `domain_strategy`,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。 + +对于 `network_strategy`,对于 `network_strategy`,是在回退到其他接口之前等待连接成功的时间。 + +仅当 `domain_strategy` 或 `network_strategy` 已设置时生效。 + +默认使用 `300ms`。 diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index d144fa07e4..eac0026d24 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -75,7 +75,7 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s callbacks := m.callbacks.Array() m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { - callback(tun.EventInterfaceUpdate) + callback(nil, 0) } return } @@ -94,6 +94,6 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s callbacks := m.callbacks.Array() m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { - callback(tun.EventInterfaceUpdate) + callback(newInterface, 0) } } diff --git a/option/outbound.go b/option/outbound.go index 0e2d5874da..5791802c9e 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -65,21 +65,23 @@ 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 uint32 `json:"routing_mark,omitempty"` - ReuseAddr bool `json:"reuse_addr,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:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` + 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 uint32 `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,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:"-"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` + NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"` + IsWireGuardListener bool `json:"-"` } func (o *DialerOptions) TakeDialerOptions() DialerOptions { diff --git a/option/route.go b/option/route.go index dfd72986ff..236e56f7ba 100644 --- a/option/route.go +++ b/option/route.go @@ -1,16 +1,20 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type RouteOptions struct { - GeoIP *GeoIPOptions `json:"geoip,omitempty"` - Geosite *GeositeOptions `json:"geosite,omitempty"` - Rules []Rule `json:"rules,omitempty"` - RuleSet []RuleSet `json:"rule_set,omitempty"` - Final string `json:"final,omitempty"` - FindProcess bool `json:"find_process,omitempty"` - AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` - OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` - DefaultInterface string `json:"default_interface,omitempty"` - DefaultMark uint32 `json:"default_mark,omitempty"` + GeoIP *GeoIPOptions `json:"geoip,omitempty"` + Geosite *GeositeOptions `json:"geosite,omitempty"` + Rules []Rule `json:"rules,omitempty"` + RuleSet []RuleSet `json:"rule_set,omitempty"` + Final string `json:"final,omitempty"` + FindProcess bool `json:"find_process,omitempty"` + AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` + OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` + DefaultInterface string `json:"default_interface,omitempty"` + DefaultMark uint32 `json:"default_mark,omitempty"` + DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"` + DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"` } type GeoIPOptions struct { diff --git a/option/rule_action.go b/option/rule_action.go index 9bc1303933..7c31ea7a42 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -137,14 +137,18 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e } type RouteActionOptions struct { - Outbound string `json:"outbound,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - UDPConnect bool `json:"udp_connect,omitempty"` + Outbound string `json:"outbound,omitempty"` + NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay uint32 `json:"fallback_delay,omitempty"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` } type _RouteOptionsActionOptions struct { - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - UDPConnect bool `json:"udp_connect,omitempty"` + NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay uint32 `json:"fallback_delay,omitempty"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` } type RouteOptionsActionOptions _RouteOptionsActionOptions diff --git a/option/types.go b/option/types.go index 04e3f10e23..8ed06250b5 100644 --- a/option/types.go +++ b/option/types.go @@ -3,6 +3,7 @@ package option import ( "strings" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -150,3 +151,23 @@ func DNSQueryTypeToString(queryType uint16) string { } return F.ToString(queryType) } + +type NetworkStrategy C.NetworkStrategy + +func (n NetworkStrategy) MarshalJSON() ([]byte, error) { + return json.Marshal(C.NetworkStrategy(n).String()) +} + +func (n *NetworkStrategy) UnmarshalJSON(content []byte) error { + var value string + err := json.Unmarshal(content, &value) + if err != nil { + return err + } + strategy, loaded := C.StringToNetworkStrategy[value] + if !loaded { + return E.New("unknown network strategy: ", value) + } + *n = NetworkStrategy(strategy) + return nil +} diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 27b334c9a4..4251c36633 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -24,31 +25,38 @@ func RegisterOutbound(registry *outbound.Registry) { outbound.Register[option.DirectOutboundOptions](registry, C.TypeDirect, NewOutbound) } -var _ N.ParallelDialer = (*Outbound)(nil) +var ( + _ N.ParallelDialer = (*Outbound)(nil) + _ dialer.ParallelNetworkDialer = (*Outbound)(nil) +) type Outbound struct { outbound.Adapter - logger logger.ContextLogger - dialer N.Dialer - domainStrategy dns.DomainStrategy - fallbackDelay time.Duration - overrideOption int - overrideDestination M.Socksaddr + logger logger.ContextLogger + dialer dialer.ParallelInterfaceDialer + domainStrategy dns.DomainStrategy + fallbackDelay time.Duration + networkStrategy C.NetworkStrategy + networkFallbackDelay time.Duration + overrideOption int + overrideDestination M.Socksaddr // loopBack *loopBackDetector } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.NewDirect(ctx, options.DialerOptions) if err != nil { return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.DialerOptions), - logger: logger, - domainStrategy: dns.DomainStrategy(options.DomainStrategy), - fallbackDelay: time.Duration(options.FallbackDelay), - dialer: outboundDialer, + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.DialerOptions), + logger: logger, + domainStrategy: dns.DomainStrategy(options.DomainStrategy), + fallbackDelay: time.Duration(options.FallbackDelay), + networkStrategy: C.NetworkStrategy(options.NetworkStrategy), + networkFallbackDelay: time.Duration(options.NetworkFallbackDelay), + dialer: outboundDialer, // loopBack: newLoopBackDetector(router), } if options.ProxyProtocol != 0 { @@ -96,6 +104,37 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination return h.dialer.DialContext(ctx, network, destination) } +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + 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) + } + 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 +} + 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() @@ -120,14 +159,64 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination } else { domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) } - return N.DialParallel(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.fallbackDelay) + switch domainStrategy { + case dns.DomainStrategyUseIPv4: + destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4) + if len(destinationAddresses) == 0 { + return nil, E.New("no IPv4 address available for ", destination) + } + case dns.DomainStrategyUseIPv6: + destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6) + if len(destinationAddresses) == 0 { + return nil, E.New("no IPv6 address available for ", destination) + } + } + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.fallbackDelay) } -func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { + 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: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + var domainStrategy dns.DomainStrategy + if h.domainStrategy != dns.DomainStrategyAsIS { + domainStrategy = h.domainStrategy + } else { + domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) + } + switch domainStrategy { + case dns.DomainStrategyUseIPv4: + destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4) + if len(destinationAddresses) == 0 { + return nil, E.New("no IPv4 address available for ", destination) + } + case dns.DomainStrategyUseIPv6: + destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6) + if len(destinationAddresses) == 0 { + return nil, E.New("no IPv6 address available for ", destination) + } + } + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, fallbackDelay) +} + +func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination - originDestination := destination switch h.overrideOption { case 1: destination = h.overrideDestination @@ -143,15 +232,11 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n } else { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - conn, err := h.dialer.ListenPacket(ctx, destination) + conn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, fallbackDelay) 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 nil, netip.Addr{}, err } - return conn, nil + return conn, newDestination, nil } /*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { diff --git a/protocol/socks/outbound.go b/protocol/socks/outbound.go index dbb5ab6177..70a5a5eda0 100644 --- a/protocol/socks/outbound.go +++ b/protocol/socks/outbound.go @@ -10,7 +10,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -115,23 +114,3 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx, destination) } - -// TODO -// Deprecated -func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if h.resolve { - return outbound.NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) - } else { - return outbound.NewConnection(ctx, h, conn, metadata) - } -} - -// TODO -// Deprecated -func (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - if h.resolve { - return outbound.NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) - } else { - return outbound.NewPacketConnection(ctx, h, conn, metadata) - } -} diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 12e53e591a..70d74140be 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -16,7 +16,6 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/wireguard" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -238,15 +237,3 @@ func (w *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n } return w.tunDevice.ListenPacket(ctx, destination) } - -// TODO -// Deprecated -func (w *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return outbound.NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) -} - -// TODO -// Deprecated -func (w *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return outbound.NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) -} diff --git a/route/network.go b/route/network.go index d4b8adcf41..eda1a054a9 100644 --- a/route/network.go +++ b/route/network.go @@ -8,6 +8,7 @@ import ( "runtime" "strings" "syscall" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/conntrack" @@ -38,8 +39,7 @@ type NetworkManager struct { networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface] autoDetectInterface bool - defaultInterface string - defaultMark uint32 + defaultOptions adapter.NetworkOptions autoRedirectOutputMark uint32 networkMonitor tun.NetworkUpdateMonitor @@ -58,11 +58,23 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp logger: logger, interfaceFinder: control.NewDefaultInterfaceFinder(), autoDetectInterface: routeOptions.AutoDetectInterface, - defaultInterface: routeOptions.DefaultInterface, - defaultMark: routeOptions.DefaultMark, - pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: service.FromContext[platform.Interface](ctx), - outboundManager: service.FromContext[adapter.OutboundManager](ctx), + defaultOptions: adapter.NetworkOptions{ + DefaultInterface: routeOptions.DefaultInterface, + DefaultMark: routeOptions.DefaultMark, + DefaultNetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy), + DefaultFallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay), + }, + pauseManager: service.FromContext[pause.Manager](ctx), + platformInterface: service.FromContext[platform.Interface](ctx), + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + } + if C.NetworkStrategy(routeOptions.DefaultNetworkStrategy) != C.NetworkStrategyDefault { + if routeOptions.DefaultInterface != "" { + return nil, E.New("`default_network_strategy` is conflict with `default_interface`") + } + if !routeOptions.AutoDetectInterface { + return nil, E.New("`auto_detect_interface` is required by `default_network_strategy`") + } } usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil enforceInterfaceMonitor := routeOptions.AutoDetectInterface @@ -81,12 +93,12 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp if err != nil { return nil, E.New("auto_detect_interface unsupported on current platform") } - interfaceMonitor.RegisterCallback(nm.notifyNetworkUpdate) + interfaceMonitor.RegisterCallback(nm.notifyInterfaceUpdate) nm.interfaceMonitor = interfaceMonitor } } else { interfaceMonitor := nm.platformInterface.CreateDefaultInterfaceMonitor(logger) - interfaceMonitor.RegisterCallback(nm.notifyNetworkUpdate) + interfaceMonitor.RegisterCallback(nm.notifyInterfaceUpdate) nm.interfaceMonitor = interfaceMonitor } return nm, nil @@ -262,10 +274,6 @@ func (r *NetworkManager) NetworkInterfaces() []adapter.NetworkInterface { return r.networkInterfaces.Load() } -func (r *NetworkManager) DefaultInterface() string { - return r.defaultInterface -} - func (r *NetworkManager) AutoDetectInterface() bool { return r.autoDetectInterface } @@ -298,8 +306,19 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { } } -func (r *NetworkManager) DefaultMark() uint32 { - return r.defaultMark +func (r *NetworkManager) ProtectFunc() control.Func { + if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { + return func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return r.platformInterface.AutoDetectInterfaceControl(int(fd)) + }) + } + } + return nil +} + +func (r *NetworkManager) DefaultOptions() adapter.NetworkOptions { + return r.defaultOptions } func (r *NetworkManager) RegisterAutoRedirectOutputMark(mark uint32) error { @@ -341,45 +360,47 @@ func (r *NetworkManager) ResetNetwork() { } } -func (r *NetworkManager) notifyNetworkUpdate(event int) { - if event == tun.EventNoRoute { +func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interface, flags int) { + if defaultInterface == nil { r.pauseManager.NetworkPause() r.logger.Error("missing default interface") - } else { - r.pauseManager.NetworkWake() - defaultInterface := r.DefaultNetworkInterface() - if defaultInterface == nil { - panic("invalid interface context") - } - var options []string - options = append(options, F.ToString("index ", defaultInterface.Index)) - if C.IsAndroid && r.platformInterface == nil { - var vpnStatus string - if r.interfaceMonitor.AndroidVPNEnabled() { - vpnStatus = "enabled" - } else { - vpnStatus = "disabled" - } - options = append(options, "vpn "+vpnStatus) + return + } + + r.pauseManager.NetworkWake() + var options []string + options = append(options, F.ToString("index ", defaultInterface.Index)) + if C.IsAndroid && r.platformInterface == nil { + var vpnStatus string + if r.interfaceMonitor.AndroidVPNEnabled() { + vpnStatus = "enabled" } else { - if defaultInterface.Type != "" { - options = append(options, F.ToString("type ", defaultInterface.Type)) - } - if defaultInterface.Expensive { - options = append(options, "expensive") - } - if defaultInterface.Constrained { - options = append(options, "constrained") - } + vpnStatus = "disabled" } - r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) - 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) - } + options = append(options, "vpn "+vpnStatus) + } else if r.platformInterface != nil { + networkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool { + return it.Interface.Index == defaultInterface.Index + }) + if networkInterface.Type == "" { + // race + return + } + options = append(options, F.ToString("type ", networkInterface.Type)) + if networkInterface.Expensive { + options = append(options, "expensive") + } + if networkInterface.Constrained { + options = append(options, "constrained") + } + } + r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) + 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) } } } diff --git a/route/route.go b/route/route.go index 1c4da4b7ad..051ee403ba 100644 --- a/route/route.go +++ b/route/route.go @@ -424,9 +424,13 @@ match: } switch action := currentRule.Action().(type) { case *rule.RuleActionRoute: + metadata.NetworkStrategy = action.NetworkStrategy + metadata.FallbackDelay = action.FallbackDelay metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping metadata.UDPConnect = action.UDPConnect case *rule.RuleActionRouteOptions: + metadata.NetworkStrategy = action.NetworkStrategy + metadata.FallbackDelay = action.FallbackDelay metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping metadata.UDPConnect = action.UDPConnect case *rule.RuleActionSniff: diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index d44e36eeac..f9b2e64108 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -30,12 +30,16 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti return &RuleActionRoute{ Outbound: action.RouteOptions.Outbound, RuleActionRouteOptions: RuleActionRouteOptions{ + NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy), + FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, UDPConnect: action.RouteOptions.UDPConnect, }, }, nil case C.RuleActionTypeRouteOptions: return &RuleActionRouteOptions{ + NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy), + FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, UDPConnect: action.RouteOptionsOptions.UDPConnect, }, nil @@ -135,6 +139,8 @@ func (r *RuleActionRoute) String() string { } type RuleActionRouteOptions struct { + NetworkStrategy C.NetworkStrategy + FallbackDelay time.Duration UDPDisableDomainUnmapping bool UDPConnect bool } diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index 9a06ac1716..29c6bbe022 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -166,7 +166,7 @@ func (t *Transport) updateServers() error { } } -func (t *Transport) interfaceUpdated(int) { +func (t *Transport) interfaceUpdated(defaultInterface *control.Interface, flags int) { err := t.updateServers() if err != nil { t.options.Logger.Error("update servers: ", err) From ca813f461be1af7aeff0ddb69a4d7dd5885c12b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 12 Nov 2024 22:49:36 +0800 Subject: [PATCH 0170/1272] documentation: Remove unused titles --- docs/configuration/dns/fakeip.md | 2 -- docs/configuration/dns/fakeip.zh.md | 2 -- docs/configuration/dns/rule_action.md | 2 -- docs/configuration/dns/rule_action.zh.md | 2 -- docs/configuration/inbound/shadowtls.md | 4 ++-- docs/configuration/rule-set/adguard.md | 2 -- docs/configuration/rule-set/adguard.zh.md | 2 -- docs/configuration/rule-set/source-format.md | 2 -- docs/configuration/rule-set/source-format.zh.md | 2 -- docs/configuration/shared/tls.md | 2 +- docs/configuration/shared/udp-over-tcp.md | 2 -- 11 files changed, 3 insertions(+), 21 deletions(-) diff --git a/docs/configuration/dns/fakeip.md b/docs/configuration/dns/fakeip.md index 51db1f42d6..63490ac197 100644 --- a/docs/configuration/dns/fakeip.md +++ b/docs/configuration/dns/fakeip.md @@ -1,5 +1,3 @@ -# FakeIP - ### Structure ```json diff --git a/docs/configuration/dns/fakeip.zh.md b/docs/configuration/dns/fakeip.zh.md index 3d9a814a66..10c6dc68e7 100644 --- a/docs/configuration/dns/fakeip.zh.md +++ b/docs/configuration/dns/fakeip.zh.md @@ -1,5 +1,3 @@ -# FakeIP - ### 结构 ```json diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index cccc139679..af19131f62 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# DNS Rule Action - !!! question "Since sing-box 1.11.0" ### route diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 8fad238169..219a5fd757 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# DNS 规则动作 - !!! question "自 sing-box 1.11.0 起" ### route diff --git a/docs/configuration/inbound/shadowtls.md b/docs/configuration/inbound/shadowtls.md index db010b7908..0f6684d20e 100644 --- a/docs/configuration/inbound/shadowtls.md +++ b/docs/configuration/inbound/shadowtls.md @@ -66,11 +66,11 @@ Only available in the ShadowTLS protocol 3. ==Required== -Handshake server address and [Dial options](/configuration/shared/dial/). +Handshake server address and [Dial Fields](/configuration/shared/dial/). #### handshake_for_server_name -Handshake server address and [Dial options](/configuration/shared/dial/) for specific server name. +Handshake server address and [Dial Fields](/configuration/shared/dial/) for specific server name. Only available in the ShadowTLS protocol 2/3. diff --git a/docs/configuration/rule-set/adguard.md b/docs/configuration/rule-set/adguard.md index 870972bf2f..bda7379451 100644 --- a/docs/configuration/rule-set/adguard.md +++ b/docs/configuration/rule-set/adguard.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# AdGuard DNS Filter - !!! question "Since sing-box 1.10.0" sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box, diff --git a/docs/configuration/rule-set/adguard.zh.md b/docs/configuration/rule-set/adguard.zh.md index b08ab34b4d..026f2e0bea 100644 --- a/docs/configuration/rule-set/adguard.zh.md +++ b/docs/configuration/rule-set/adguard.zh.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# AdGuard DNS Filter - !!! question "自 sing-box 1.10.0 起" sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box, diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index 43e8ea4535..1dcc1d4470 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# Source Format - !!! quote "Changes in sing-box 1.11.0" :material-plus: version `3` diff --git a/docs/configuration/rule-set/source-format.zh.md b/docs/configuration/rule-set/source-format.zh.md index 0e8fc0cb96..3dacaea7c8 100644 --- a/docs/configuration/rule-set/source-format.zh.md +++ b/docs/configuration/rule-set/source-format.zh.md @@ -2,8 +2,6 @@ icon: material/new-box --- -# 源文件格式 - !!! quote "sing-box 1.11.0 中的更改" :material-plus: version `3` diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index 40343e18ef..7a6c9d5e08 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -380,7 +380,7 @@ See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details ==Required== -Handshake server address and [Dial options](/configuration/shared/dial/). +Handshake server address and [Dial Fields](/configuration/shared/dial/). #### private_key diff --git a/docs/configuration/shared/udp-over-tcp.md b/docs/configuration/shared/udp-over-tcp.md index 6bd8a68a91..9c2688b94b 100644 --- a/docs/configuration/shared/udp-over-tcp.md +++ b/docs/configuration/shared/udp-over-tcp.md @@ -1,5 +1,3 @@ -# UDP over TCP - !!! warning "" It's a proprietary protocol created by SagerNet, not part of shadowsocks. From 9db9484863ff7e2dae60713241846d4fd4b4d7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 Nov 2024 19:05:28 +0800 Subject: [PATCH 0171/1272] Refactor multi networks strategy --- adapter/inbound.go | 2 + adapter/network.go | 12 +-- adapter/outbound/default.go | 6 +- common/dialer/default.go | 63 +++++++++------ common/dialer/default_parallel_interface.go | 90 +++++++++------------ common/dialer/default_parallel_network.go | 18 ++--- common/dialer/dialer.go | 8 +- common/dialer/resolve.go | 10 +-- common/srs/binary.go | 16 +++- constant/network.go | 54 +++++++------ docs/configuration/route/index.md | 37 +++++---- docs/configuration/route/index.zh.md | 39 ++++++--- docs/configuration/route/rule_action.md | 22 ++--- docs/configuration/route/rule_action.zh.md | 22 ++--- docs/configuration/shared/dial.md | 63 +++++++++++---- docs/configuration/shared/dial.zh.md | 54 ++++++++++--- experimental/libbox/platform.go | 10 +-- experimental/libbox/service.go | 2 +- option/outbound.go | 36 +++++---- option/route.go | 26 +++--- option/rule.go | 72 ++++++++--------- option/rule_dns.go | 76 ++++++++--------- option/rule_set.go | 44 +++++----- option/types.go | 24 ++++++ protocol/direct/outbound.go | 16 ++-- route/network.go | 12 +-- route/rule/rule_action.go | 2 + route/rule/rule_default.go | 3 +- route/rule/rule_dns.go | 2 +- route/rule/rule_headless.go | 3 +- route/rule/rule_item_network_type.go | 5 +- 31 files changed, 507 insertions(+), 342 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index 33b1b4d164..1093bac4e6 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -69,6 +69,8 @@ type InboundContext struct { UDPDisableDomainUnmapping bool UDPConnect bool NetworkStrategy C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType FallbackDelay time.Duration DNSServer string diff --git a/adapter/network.go b/adapter/network.go index dd924c339f..08fc00fac0 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -28,10 +28,12 @@ type NetworkManager interface { } type NetworkOptions struct { - DefaultNetworkStrategy C.NetworkStrategy - DefaultFallbackDelay time.Duration - DefaultInterface string - DefaultMark uint32 + NetworkStrategy C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType + FallbackDelay time.Duration + BindInterface string + RoutingMark uint32 } type InterfaceUpdateListener interface { @@ -45,7 +47,7 @@ type WIFIState struct { type NetworkInterface struct { control.Interface - Type string + Type C.InterfaceType DNSServers []string Expensive bool Constrained bool diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go index 84be8abaf1..27cafb9d2f 100644 --- a/adapter/outbound/default.go +++ b/adapter/outbound/default.go @@ -26,7 +26,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a var err error if len(metadata.DestinationAddresses) > 0 { if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) } @@ -56,7 +56,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, if metadata.UDPConnect { if len(metadata.DestinationAddresses) > 0 { if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) } @@ -74,7 +74,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, } else { if len(metadata.DestinationAddresses) > 0 { if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) } diff --git a/common/dialer/default.go b/common/dialer/default.go index c7329f2e1a..5871e64007 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/common/conntrack" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" @@ -33,6 +34,8 @@ type DefaultDialer struct { isWireGuardListener bool networkManager adapter.NetworkManager networkStrategy C.NetworkStrategy + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType networkFallbackDelay time.Duration networkLastFallback atomic.TypedValue[time.Time] } @@ -43,6 +46,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti listener net.ListenConfig interfaceFinder control.InterfaceFinder networkStrategy C.NetworkStrategy + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType networkFallbackDelay time.Duration ) if networkManager != nil { @@ -56,8 +61,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti listener.Control = control.Append(listener.Control, bindFunc) } if options.RoutingMark > 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) - listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) + dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark))) + listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark))) } if networkManager != nil { autoRedirectOutputMark := networkManager.AutoRedirectOutputMark() @@ -74,6 +79,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`") } networkStrategy = C.NetworkStrategy(options.NetworkStrategy) + networkType = common.Map(options.NetworkType, option.InterfaceType.Build) + fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build) networkFallbackDelay = time.Duration(options.NetworkFallbackDelay) if networkManager == nil || !networkManager.AutoDetectInterface() { return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") @@ -81,23 +88,31 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti } if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil { defaultOptions := networkManager.DefaultOptions() - if defaultOptions.DefaultInterface != "" { - bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.DefaultInterface, -1) - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) - } else if networkManager.AutoDetectInterface() { - if defaultOptions.DefaultNetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault { - networkStrategy = defaultOptions.DefaultNetworkStrategy - networkFallbackDelay = defaultOptions.DefaultFallbackDelay - bindFunc := networkManager.ProtectFunc() - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) - } else { - bindFunc := networkManager.AutoDetectInterfaceFunc() + if options.BindInterface == "" { + if defaultOptions.BindInterface != "" { + bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) + } else if networkManager.AutoDetectInterface() { + if defaultOptions.NetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault { + networkStrategy = defaultOptions.NetworkStrategy + networkType = defaultOptions.NetworkType + fallbackNetworkType = defaultOptions.FallbackNetworkType + networkFallbackDelay = defaultOptions.FallbackDelay + bindFunc := networkManager.ProtectFunc() + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } else { + bindFunc := networkManager.AutoDetectInterfaceFunc() + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } } } + if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { + dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark)) + listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark)) + } } if options.ReuseAddr { listener.Control = control.Append(listener.Control, control.ReuseAddr()) @@ -179,6 +194,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti isWireGuardListener: options.IsWireGuardListener, networkManager: networkManager, networkStrategy: networkStrategy, + networkType: networkType, + fallbackNetworkType: fallbackNetworkType, networkFallbackDelay: networkFallbackDelay, }, nil } @@ -202,11 +219,11 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) } } else { - return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkFallbackDelay) + return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) } } -func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if strategy == C.NetworkStrategyDefault { return d.DialContext(ctx, network, address) } @@ -226,9 +243,9 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin err error ) if !fastFallback { - conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, fallbackDelay) + conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } else { - conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, fallbackDelay, d.networkLastFallback.Store) + conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store) } if err != nil { return nil, err @@ -249,11 +266,11 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)) } } else { - return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkFallbackDelay) + return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) } } -func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { +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 == C.NetworkStrategyDefault { return d.ListenPacket(ctx, destination) } @@ -264,11 +281,11 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina if destination.IsIPv4() && !destination.Addr.IsUnspecified() { network += "4" } - return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, fallbackDelay)) + return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, interfaceType, fallbackInterfaceType, fallbackDelay)) } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - return d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkFallbackDelay) + return d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) } func trackConn(conn net.Conn, err error) (net.Conn, error) { diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go index baf0349ec8..71d9814bd2 100644 --- a/common/dialer/default_parallel_interface.go +++ b/common/dialer/default_parallel_interface.go @@ -7,14 +7,14 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" N "github.com/sagernet/sing/common/network" ) -func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, bool, error) { - primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) +func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, bool, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType) if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, false, E.New("no available network interface") } @@ -84,8 +84,8 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di } } -func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) { - primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) +func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType) if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, false, E.New("no available network interface") } @@ -144,8 +144,8 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d } } -func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { - primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) +func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType) if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, E.New("no available network interface") } @@ -174,12 +174,12 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene return nil, E.Errors(errors...) } -func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) { +func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) { interfaces := networkManager.NetworkInterfaces() switch strategy { - case C.NetworkStrategyFallback: - defaultIf := networkManager.InterfaceMonitor().DefaultInterface() - if defaultIf != nil { + case C.NetworkStrategyDefault: + if len(interfaceType) == 0 { + defaultIf := networkManager.InterfaceMonitor().DefaultInterface() for _, iif := range interfaces { if iif.Index == defaultIf.Index { primaryInterfaces = append(primaryInterfaces, iif) @@ -188,54 +188,36 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS } } } else { - primaryInterfaces = interfaces + primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(interfaceType, iif.Type) + }) } case C.NetworkStrategyHybrid: - primaryInterfaces = interfaces - case C.NetworkStrategyWIFI: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeWIFI { - primaryInterfaces = append(primaryInterfaces, iif) - } else { - fallbackInterfaces = append(fallbackInterfaces, iif) - } - } - case C.NetworkStrategyCellular: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeCellular { - primaryInterfaces = append(primaryInterfaces, iif) - } else { - fallbackInterfaces = append(fallbackInterfaces, iif) - } - } - case C.NetworkStrategyEthernet: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeEthernet { - primaryInterfaces = append(primaryInterfaces, iif) - } else { - fallbackInterfaces = append(fallbackInterfaces, iif) - } - } - case C.NetworkStrategyWIFIOnly: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeWIFI { - primaryInterfaces = append(primaryInterfaces, iif) - } - } - case C.NetworkStrategyCellularOnly: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeCellular { - primaryInterfaces = append(primaryInterfaces, iif) - } + if len(interfaceType) == 0 { + primaryInterfaces = interfaces + } else { + primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(interfaceType, iif.Type) + }) } - case C.NetworkStrategyEthernetOnly: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeEthernet { - primaryInterfaces = append(primaryInterfaces, iif) + case C.NetworkStrategyFallback: + if len(interfaceType) == 0 { + defaultIf := networkManager.InterfaceMonitor().DefaultInterface() + for _, iif := range interfaces { + if iif.Index == defaultIf.Index { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } } + } else { + primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(interfaceType, iif.Type) + }) } - default: - panic(F.ToString("unknown network strategy: ", strategy)) + fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(fallbackInterfaceType, iif.Type) + }) } return primaryInterfaces, fallbackInterfaces } diff --git a/common/dialer/default_parallel_network.go b/common/dialer/default_parallel_network.go index f42d9330c7..ea043dfdb2 100644 --- a/common/dialer/default_parallel_network.go +++ b/common/dialer/default_parallel_network.go @@ -13,13 +13,13 @@ import ( N "github.com/sagernet/sing/common/network" ) -func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { - return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, fallbackDelay) + return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var errors []error for _, address := range destinationAddresses { - conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay) + conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) if err == nil { return conn, nil } @@ -28,7 +28,7 @@ func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, netw return nil, E.Errors(errors...) } -func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if fallbackDelay == 0 { fallbackDelay = N.DefaultFallbackDelay } @@ -43,7 +43,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne return address.Is6() && !address.Is4In6() }) if len(addresses4) == 0 || len(addresses6) == 0 { - return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, fallbackDelay) + return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var primaries, fallbacks []netip.Addr if preferIPv6 { @@ -65,7 +65,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne if !primary { ras = fallbacks } - c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, fallbackDelay) + c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) select { case results <- dialResult{Conn: c, error: err, primary: primary, done: true}: case <-returned: @@ -106,13 +106,13 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne } } -func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { +func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { - return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, fallbackDelay) + return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var errors []error for _, address := range destinationAddresses { - conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay) + conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) if err == nil { return conn, address, nil } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index b3305d7397..b307a3303b 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -77,11 +77,11 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter type ParallelInterfaceDialer interface { N.Dialer - DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) - ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) + DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) } type ParallelNetworkDialer interface { - DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) - ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) + 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) } diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index ce17923c40..b5d922b375 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -106,7 +106,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } -func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } @@ -128,13 +128,13 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context fallbackDelay = d.fallbackDelay } if d.parallel { - return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, fallbackDelay) + return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } else { - return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, fallbackDelay) + return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } } -func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { +func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } @@ -152,7 +152,7 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C if err != nil { return nil, err } - conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, fallbackDelay) + conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) if err != nil { return nil, err } diff --git a/common/srs/binary.go b/common/srs/binary.go index fbed78adc5..42b4460d8d 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -226,7 +226,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea } rule.AdGuardDomainMatcher = matcher case ruleItemNetworkType: - rule.NetworkType, err = readRuleItemString(reader) + rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader) case ruleItemNetworkIsExpensive: rule.NetworkIsExpensive = true case ruleItemNetworkIsConstrained: @@ -349,7 +349,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen if generateVersion < C.RuleSetVersion3 { return E.New("network_type rule item is only supported in version 3 or later") } - err = writeRuleItemString(writer, ruleItemNetworkType, rule.NetworkType) + err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType) if err != nil { return err } @@ -414,6 +414,18 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e return varbin.Write(writer, binary.BigEndian, value) } +func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) { + return varbin.ReadValue[[]E](reader, binary.BigEndian) +} + +func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error { + err := writer.WriteByte(itemType) + if err != nil { + return err + } + return varbin.Write(writer, binary.BigEndian, value) +} + func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) { return varbin.ReadValue[[]uint16](reader, binary.BigEndian) } diff --git a/constant/network.go b/constant/network.go index c026b7b15b..88a1dd815f 100644 --- a/constant/network.go +++ b/constant/network.go @@ -5,44 +5,52 @@ import ( F "github.com/sagernet/sing/common/format" ) +type InterfaceType uint8 + const ( - InterfaceTypeWIFI = "wifi" - InterfaceTypeCellular = "cellular" - InterfaceTypeEthernet = "ethernet" - InterfaceTypeOther = "other" + InterfaceTypeWIFI InterfaceType = iota + InterfaceTypeCellular + InterfaceTypeEthernet + InterfaceTypeOther +) + +var ( + interfaceTypeToString = map[InterfaceType]string{ + InterfaceTypeWIFI: "wifi", + InterfaceTypeCellular: "cellular", + InterfaceTypeEthernet: "ethernet", + InterfaceTypeOther: "other", + } + StringToInterfaceType = common.ReverseMap(interfaceTypeToString) ) -type NetworkStrategy int +func (t InterfaceType) String() string { + name, loaded := interfaceTypeToString[t] + if !loaded { + return F.ToString(int(t)) + } + return name +} + +type NetworkStrategy uint8 const ( NetworkStrategyDefault NetworkStrategy = iota NetworkStrategyFallback NetworkStrategyHybrid - NetworkStrategyWIFI - NetworkStrategyCellular - NetworkStrategyEthernet - NetworkStrategyWIFIOnly - NetworkStrategyCellularOnly - NetworkStrategyEthernetOnly ) var ( - NetworkStrategyToString = map[NetworkStrategy]string{ - NetworkStrategyDefault: "default", - NetworkStrategyFallback: "fallback", - NetworkStrategyHybrid: "hybrid", - NetworkStrategyWIFI: "wifi", - NetworkStrategyCellular: "cellular", - NetworkStrategyEthernet: "ethernet", - NetworkStrategyWIFIOnly: "wifi_only", - NetworkStrategyCellularOnly: "cellular_only", - NetworkStrategyEthernetOnly: "ethernet_only", + networkStrategyToString = map[NetworkStrategy]string{ + NetworkStrategyDefault: "default", + NetworkStrategyFallback: "fallback", + NetworkStrategyHybrid: "hybrid", } - StringToNetworkStrategy = common.ReverseMap(NetworkStrategyToString) + StringToNetworkStrategy = common.ReverseMap(networkStrategyToString) ) func (s NetworkStrategy) String() string { - name, loaded := NetworkStrategyToString[s] + name, loaded := networkStrategyToString[s] if !loaded { return F.ToString(int(s)) } diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 2b035eb426..58f733528e 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -7,6 +7,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.11.0" :material-plus: [default_network_strategy](#default_network_strategy) + :material-plus: [default_network_type](#default_network_type) + :material-plus: [default_fallback_network_type](#default_fallback_network_type) :material-alert: [default_fallback_delay](#default_fallback_delay) !!! quote "Changes in sing-box 1.8.0" @@ -30,17 +32,18 @@ icon: material/new-box "default_interface": "", "default_mark": 0, "default_network_strategy": "", + "default_network_type": [], + "default_fallback_network_type": [], "default_fallback_delay": "" } } ``` -### Fields +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item -| Key | Format | -|-----------|-----------------------| -| `geoip` | [GeoIP](./geoip/) | -| `geosite` | [Geosite](./geosite/) | +### Fields #### rules @@ -96,11 +99,9 @@ Takes no effect if `outbound.routing_mark` is set. #### default_network_strategy -!!! quote "" - - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. +!!! question "Since sing-box 1.11.0" -Strategy for selecting network interfaces. +See [Dial Fields](/configuration/shared/dial/#network_strategy) for details. Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set. @@ -108,12 +109,20 @@ Can be overrides by `outbound.network_strategy`. Conflicts with `default_interface`. -See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values. +#### default_network_type -#### default_fallback_delay +!!! question "Since sing-box 1.11.0" -!!! quote "" +See [Dial Fields](/configuration/shared/dial/#network_type) for details. + +#### default_fallback_network_type + +!!! question "Since sing-box 1.11.0" + +See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details. + +#### default_fallback_delay - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set. +!!! question "Since sing-box 1.11.0" -See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. \ No newline at end of file +See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md index b00237c450..9550b4ac3b 100644 --- a/docs/configuration/route/index.zh.md +++ b/docs/configuration/route/index.zh.md @@ -1,5 +1,16 @@ +--- +icon: material/new-box +--- + # 路由 +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [network_strategy](#network_strategy) + :material-plus: [default_network_type](#default_network_type) + :material-plus: [default_fallback_network_type](#default_fallback_network_type) + :material-alert: [default_fallback_delay](#default_fallback_delay) + !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) @@ -26,6 +37,10 @@ } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + ### 字段 | 键 | 格式 | @@ -87,11 +102,9 @@ #### network_strategy -!!! quote "" - - 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`。 +!!! question "自 sing-box 1.11.0 起" -选择网络接口的策略。 +详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 当 `outbound.bind_interface`, `outbound.inet4_bind_address` 或 `outbound.inet6_bind_address` 已设置时不生效。 @@ -99,12 +112,20 @@ 与 `default_interface` 冲突。 -可用值请参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 +#### default_network_type -#### fallback_delay +!!! question "自 sing-box 1.11.0 起" -!!! quote "" +详情参阅 [拨号字段](/configuration/shared/dial/#default_network_type)。 + +#### default_fallback_network_type + +!!! question "自 sing-box 1.11.0 起" + +详情参阅 [拨号字段](/configuration/shared/dial/#default_fallback_network_type)。 + +#### default_fallback_delay - 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface` 且 `network_strategy` 已设置。 +!!! question "自 sing-box 1.11.0 起" -详情请参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 +详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 5e9cfbc220..6462c84836 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -11,12 +11,18 @@ icon: material/new-box "action": "route", // default "outbound": "", "network_strategy": "", + "network_type": [], + "fallback_network_type": [], "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } ``` +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + `route` inherits the classic rule behavior of routing connection to the specified outbound. #### outbound @@ -27,22 +33,20 @@ Tag of target outbound. #### network_strategy -!!! quote "" - - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. - -Strategy for selecting network interfaces. +See [Dial Fields](/configuration/shared/dial/#network_strategy) for details. Only take effect if outbound is direct without `outbound.bind_interface`, `outbound.inet4_bind_address` and `outbound.inet6_bind_address` set. -See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values. +#### network_type -#### fallback_delay +See [Dial Fields](/configuration/shared/dial/#network_type) for details. + +#### fallback_network_type -!!! quote "" +See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details. - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set. +#### fallback_delay See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 52965fd240..acadb66d3c 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -12,6 +12,8 @@ icon: material/new-box "outbound": "", "network_strategy": "", "fallback_delay": "", + "network_type": [], + "fallback_network_type": [], "udp_disable_domain_unmapping": false, "udp_connect": false } @@ -27,22 +29,20 @@ icon: material/new-box #### network_strategy -!!! quote "" - - 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`。 - -选择网络接口的策略。 +详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 仅当出站为 `direct` 且 `outbound.bind_interface`, `outbound.inet4_bind_address` 且 `outbound.inet6_bind_address` 未设置时生效。 -可用值参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 +#### network_type -#### fallback_delay +详情参阅 [拨号字段](/configuration/shared/dial/#network_type)。 -!!! quote "" +#### fallback_network_type - 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface` 且 `network_strategy` 已设置。 +详情参阅 [拨号字段](/configuration/shared/dial/#fallback_network_type)。 + +#### fallback_delay 详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 @@ -68,6 +68,10 @@ icon: material/new-box } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + `route-options` 为路由设置选项。 ### reject diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index e67bf61623..5f654ae247 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -5,7 +5,9 @@ icon: material/new-box !!! quote "Changes in sing-box 1.11.0" :material-plus: [network_strategy](#network_strategy) - :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [network_type](#network_type) + :material-alert: [fallback_network_type](#fallback_network_type) ### Structure @@ -23,10 +25,16 @@ icon: material/new-box "udp_fragment": false, "domain_strategy": "prefer_ipv6", "network_strategy": "default", + "network_type": [], + "fallback_network_type": [], "fallback_delay": "300ms" } ``` +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + ### Fields #### detour @@ -101,30 +109,57 @@ If set, the requested domain name will be resolved to IP before connect. !!! quote "" - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. Strategy for selecting network interfaces. Available values: -- `default` (default): Connect to the default interface. -- `fallback`: Try all other interfaces when timeout. -- `hybrid`: Connect to all interfaces concurrently and choose the fastest one. -- `wifi`: Prioritize WIFI, but try all other interfaces when unavailable or timeout. -- `cellular`: Prioritize Cellular, but try all other interfaces when unavailable or timeout. -- `ethernet`: Prioritize Ethernet, but try all other interfaces when unavailable or timeout. -- `wifi_only`: Connect to WIFI only. -- `cellular_only`: Connect to Cellular only. -- `ethernet_only`: Connect to Ethernet only. +- `default` (default): Connect to default network or networks specified in `network_type` sequentially. +- `hybrid`: Connect to all networks or networks specified in `network_type` concurrently. +- `fallback`: Connect to default network or preferred networks specified in `network_type` concurrently, and try fallback networks when unavailable or timeout. -For fallback strategies, when preferred interfaces fails or times out, -it will enter a 15s fast fallback state (upgraded to `hybrid`), -and exit immediately if recovers. +For fallback, when preferred interfaces fails or times out, +it will enter a 15s fast fallback state (Connect to all preferred and fallback networks concurrently), +and exit immediately if preferred networks recover. Conflicts with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + +Network types to use when using `default` or `hybrid` network strategy or +preferred network types to use when using `fallback` network strategy. + +Available values: `wifi`, `cellular`, `ethernet`, `other`. + +Device's default network is used by default. + +#### fallback_network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + +Fallback network types when preferred networks are unavailable or timeout when using `fallback` network strategy. + +All other networks expect preferred are used by default. + #### fallback_delay +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + The length of time to wait before spawning a RFC 6555 Fast Fallback connection. For `domain_strategy`, is the amount of time to wait for connection to succeed before assuming diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 4e20f106e8..ab83c44c69 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -5,7 +5,9 @@ icon: material/new-box !!! quote "sing-box 1.11.0 中的更改" :material-plus: [network_strategy](#network_strategy) - :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [network_type](#network_type) + :material-alert: [fallback_network_type](#fallback_network_type) ### 结构 @@ -23,10 +25,16 @@ icon: material/new-box "udp_fragment": false, "domain_strategy": "prefer_ipv6", "network_strategy": "", + "network_type": [], + "fallback_network_type": [], "fallback_delay": "300ms" } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + ### 字段 #### detour @@ -99,26 +107,48 @@ icon: material/new-box !!! quote "" - 仅在 Android 与 iOS 平台图形客户端中支持。 + 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。 用于选择网络接口的策略。 可用值: -- `default` (默认): 连接到默认接口, -- `fallback`: 如果超时,尝试所有剩余接口。 -- `hybrid`: 同时尝试所有接口,选择最快的一个。 -- `wifi`: 优先使用 WIFI,但在不可用或超时时尝试所有其他接口。 -- `cellular`: 优先使用蜂窝数据,但在不可用或超时时尝试所有其他接口。 -- `ethernet`: 优先使用以太网,但在不可用或超时时尝试所有其他接口。 -- `wifi_only`: 仅连接到 WIFI。 -- `cellular_only`: 仅连接到蜂窝数据。 -- `ethernet_only`: 仅连接到以太网。 +- `default`(默认值):按顺序连接默认网络或 `network_type` 中指定的网络。 +- `hybrid`:同时连接所有网络或 `network_type` 中指定的网络。 +- `fallback`:同时连接默认网络或 `network_type` 中指定的首选网络,当不可用或超时时尝试回退网络。 -对于回退策略, 当优先使用的接口发生故障或超时时, 将进入 15 秒的快速回退状态(升级为 `hybrid`), 且恢复后立即退出。 +对于回退模式,当首选接口失败或超时时, +将进入15秒的快速回退状态(同时连接所有首选和回退网络), +如果首选网络恢复,则立即退出。 与 `bind_interface`, `bind_inet4_address` 和 `bind_inet6_address` 冲突。 +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。 + +当使用 `default` 或 `hybrid` 网络策略时要使用的网络类型,或当使用 `fallback` 网络策略时要使用的首选网络类型。 + +可用值:`wifi`, `cellular`, `ethernet`, `other`。 + +默认使用设备默认网络。 + +#### fallback_network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。 + +当使用 `fallback` 网络策略时,在首选网络不可用或超时的情况下要使用的回退网络类型。 + +默认使用除首选网络外的所有其他网络。 + #### fallback_delay 在生成 RFC 6555 快速回退连接之前等待的时间长度。 diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index a61243458c..d5951cd38f 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -34,10 +34,10 @@ type InterfaceUpdateListener interface { } const ( - InterfaceTypeWIFI = C.InterfaceTypeWIFI - InterfaceTypeCellular = C.InterfaceTypeCellular - InterfaceTypeEthernet = C.InterfaceTypeEthernet - InterfaceTypeOther = C.InterfaceTypeOther + InterfaceTypeWIFI = int32(C.InterfaceTypeWIFI) + InterfaceTypeCellular = int32(C.InterfaceTypeCellular) + InterfaceTypeEthernet = int32(C.InterfaceTypeEthernet) + InterfaceTypeOther = int32(C.InterfaceTypeOther) ) type NetworkInterface struct { @@ -47,7 +47,7 @@ type NetworkInterface struct { Addresses StringIterator Flags int32 - Type string + Type int32 DNSServer StringIterator Metered bool } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 28ae03d3c0..e40cbe0367 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -202,7 +202,7 @@ func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, err Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), Flags: linkFlags(uint32(netInterface.Flags)), }, - Type: netInterface.Type, + Type: C.InterfaceType(netInterface.Type), DNSServers: iteratorToArray[string](netInterface.DNSServer), Expensive: netInterface.Metered || isDefault && w.isExpensive, Constrained: isDefault && w.isConstrained, diff --git a/option/outbound.go b/option/outbound.go index 5791802c9e..34ef904a80 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -65,23 +65,25 @@ 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 uint32 `json:"routing_mark,omitempty"` - ReuseAddr bool `json:"reuse_addr,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:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` - FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` - NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` + 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"` + 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:"-"` + DomainStrategy DomainStrategy `json:"domain_strategy,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"` + NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"` + IsWireGuardListener bool `json:"-"` } func (o *DialerOptions) TakeDialerOptions() DialerOptions { diff --git a/option/route.go b/option/route.go index 236e56f7ba..0eb1cbf1b2 100644 --- a/option/route.go +++ b/option/route.go @@ -3,18 +3,20 @@ package option import "github.com/sagernet/sing/common/json/badoption" type RouteOptions struct { - GeoIP *GeoIPOptions `json:"geoip,omitempty"` - Geosite *GeositeOptions `json:"geosite,omitempty"` - Rules []Rule `json:"rules,omitempty"` - RuleSet []RuleSet `json:"rule_set,omitempty"` - Final string `json:"final,omitempty"` - FindProcess bool `json:"find_process,omitempty"` - AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` - OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` - DefaultInterface string `json:"default_interface,omitempty"` - DefaultMark uint32 `json:"default_mark,omitempty"` - DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"` - DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"` + GeoIP *GeoIPOptions `json:"geoip,omitempty"` + Geosite *GeositeOptions `json:"geosite,omitempty"` + Rules []Rule `json:"rules,omitempty"` + RuleSet []RuleSet `json:"rule_set,omitempty"` + Final string `json:"final,omitempty"` + FindProcess bool `json:"find_process,omitempty"` + AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` + OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` + DefaultInterface string `json:"default_interface,omitempty"` + DefaultMark FwMark `json:"default_mark,omitempty"` + DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"` + DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"` + DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"` + DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"` } type GeoIPOptions struct { diff --git a/option/rule.go b/option/rule.go index 82a53f75a4..b769dab831 100644 --- a/option/rule.go +++ b/option/rule.go @@ -67,42 +67,42 @@ 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[string] `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"` + 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 33dc816c53..b437eb54d3 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -68,44 +68,44 @@ 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"` - 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[string] `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"` + 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"` // 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 9ccca4759b..f7a5f3344f 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -146,28 +146,28 @@ 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[string] `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"` + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` diff --git a/option/types.go b/option/types.go index 8ed06250b5..66f58ef84c 100644 --- a/option/types.go +++ b/option/types.go @@ -171,3 +171,27 @@ func (n *NetworkStrategy) UnmarshalJSON(content []byte) error { *n = NetworkStrategy(strategy) return nil } + +type InterfaceType C.InterfaceType + +func (t InterfaceType) Build() C.InterfaceType { + return C.InterfaceType(t) +} + +func (t InterfaceType) MarshalJSON() ([]byte, error) { + return json.Marshal(C.InterfaceType(t).String()) +} + +func (t *InterfaceType) UnmarshalJSON(content []byte) error { + var value string + err := json.Unmarshal(content, &value) + if err != nil { + return err + } + interfaceType, loaded := C.StringToInterfaceType[value] + if !loaded { + return E.New("unknown interface type: ", value) + } + *t = InterfaceType(interfaceType) + return nil +} diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 4251c36633..5ae0dac647 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -12,7 +12,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" @@ -37,6 +37,8 @@ type Outbound struct { domainStrategy dns.DomainStrategy fallbackDelay time.Duration networkStrategy C.NetworkStrategy + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType networkFallbackDelay time.Duration overrideOption int overrideDestination M.Socksaddr @@ -55,6 +57,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), networkStrategy: C.NetworkStrategy(options.NetworkStrategy), + networkType: common.Map(options.NetworkType, option.InterfaceType.Build), + fallbackNetworkType: common.Map(options.FallbackNetworkType, option.InterfaceType.Build), networkFallbackDelay: time.Duration(options.NetworkFallbackDelay), dialer: outboundDialer, // loopBack: newLoopBackDetector(router), @@ -171,10 +175,10 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination return nil, E.New("no IPv6 address available for ", destination) } } - return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.fallbackDelay) + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.networkType, h.fallbackNetworkType, h.fallbackDelay) } -func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination @@ -210,10 +214,10 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest return nil, E.New("no IPv6 address available for ", destination) } } - return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, fallbackDelay) + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay) } -func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { +func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination @@ -232,7 +236,7 @@ func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M. } else { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - conn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, fallbackDelay) + 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/route/network.go b/route/network.go index eda1a054a9..e7c4df9ceb 100644 --- a/route/network.go +++ b/route/network.go @@ -59,10 +59,12 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp interfaceFinder: control.NewDefaultInterfaceFinder(), autoDetectInterface: routeOptions.AutoDetectInterface, defaultOptions: adapter.NetworkOptions{ - DefaultInterface: routeOptions.DefaultInterface, - DefaultMark: routeOptions.DefaultMark, - DefaultNetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy), - DefaultFallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay), + BindInterface: routeOptions.DefaultInterface, + RoutingMark: uint32(routeOptions.DefaultMark), + 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), }, pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[platform.Interface](ctx), @@ -382,7 +384,7 @@ func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interfa networkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool { return it.Interface.Index == defaultInterface.Index }) - if networkInterface.Type == "" { + if networkInterface.Name == "" { // race return } diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index f9b2e64108..52e9520726 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -140,6 +140,8 @@ func (r *RuleActionRoute) String() string { type RuleActionRouteOptions struct { NetworkStrategy C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType FallbackDelay time.Duration UDPDisableDomainUnmapping bool UDPConnect bool diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index a5d47b4445..2794c28751 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -8,6 +8,7 @@ import ( "github.com/sagernet/sing-box/experimental/deprecated" "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/service" ) @@ -224,7 +225,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.allItems = append(rule.allItems, item) } if len(options.NetworkType) > 0 { - item := NewNetworkTypeItem(networkManager, options.NetworkType) + item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build)) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 4ac08a5a44..fb8c6b786f 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -221,7 +221,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if len(options.NetworkType) > 0 { - item := NewNetworkTypeItem(networkManager, options.NetworkType) + item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build)) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index 7f2dc5fca7..619856a5a5 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -6,6 +6,7 @@ import ( "github.com/sagernet/sing-box/adapter" 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/service" ) @@ -142,7 +143,7 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR } if networkManager != nil { if len(options.NetworkType) > 0 { - item := NewNetworkTypeItem(networkManager, options.NetworkType) + item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build)) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_item_network_type.go b/route/rule/rule_item_network_type.go index 8ebdb25e9d..31856e70df 100644 --- a/route/rule/rule_item_network_type.go +++ b/route/rule/rule_item_network_type.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" ) @@ -12,10 +13,10 @@ var _ RuleItem = (*NetworkTypeItem)(nil) type NetworkTypeItem struct { networkManager adapter.NetworkManager - networkType []string + networkType []C.InterfaceType } -func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []string) *NetworkTypeItem { +func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []C.InterfaceType) *NetworkTypeItem { return &NetworkTypeItem{ networkManager: networkManager, networkType: networkType, From effcf394692df5fe9e99ba73ccc7e45752c614b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 14 Nov 2024 12:42:42 +0800 Subject: [PATCH 0172/1272] Add `dns.cache_capacity` --- docs/configuration/dns/index.md | 18 ++++++++++++++++++ docs/configuration/dns/index.zh.md | 19 ++++++++++++++++++- option/dns.go | 1 + route/router.go | 1 + 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index f37c93a4bb..fc81333443 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -1,3 +1,12 @@ +--- +icon: material/new +--- + + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [cache_capacity](#cache_capacity) + !!! quote "Changes in sing-box 1.9.0" :material-plus: [client_subnet](#client_subnet) @@ -16,6 +25,7 @@ "disable_cache": false, "disable_expire": false, "independent_cache": false, + "cache_capacity": 0, "reverse_mapping": false, "client_subnet": "", "fakeip": {} @@ -58,6 +68,14 @@ Disable dns cache expire. Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance. +#### cache_capacity + +!!! quote "Since sing-box 1.11.0" + +LRU cache capacity. + +Value less than 1024 will be ignored. + #### reverse_mapping Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing. diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index c845ae0aa7..20fbc00db5 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -1,4 +1,12 @@ -!!! quote "sing-box 1.9.0 中的更改" +--- +icon: material/new +--- + +!!! quote "自 sing-box 1.11.0 起" + + :material-plus: [cache_capacity](#cache_capacity) + +!!! quote "自 sing-box 1.9.0 起" :material-plus: [client_subnet](#client_subnet) @@ -16,6 +24,7 @@ "disable_cache": false, "disable_expire": false, "independent_cache": false, + "cache_capacity": 0, "reverse_mapping": false, "client_subnet": "", "fakeip": {} @@ -57,6 +66,14 @@ 使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。 +#### cache_capacity + +!!! quote "自 sing-box 1.11.0 起" + +LRU 缓存容量。 + +小于 1024 的值将被忽略。 + #### reverse_mapping 在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。 diff --git a/option/dns.go b/option/dns.go index 32c1ac2e1b..272c518054 100644 --- a/option/dns.go +++ b/option/dns.go @@ -31,6 +31,7 @@ type DNSClientOptions struct { DisableCache bool `json:"disable_cache,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"` IndependentCache bool `json:"independent_cache,omitempty"` + CacheCapacity uint32 `json:"cache_capacity,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } diff --git a/route/router.go b/route/router.go index 1f760a86a0..f23604b306 100644 --- a/route/router.go +++ b/route/router.go @@ -96,6 +96,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route DisableCache: dnsOptions.DNSClientOptions.DisableCache, DisableExpire: dnsOptions.DNSClientOptions.DisableExpire, IndependentCache: dnsOptions.DNSClientOptions.IndependentCache, + CacheCapacity: dnsOptions.DNSClientOptions.CacheCapacity, RDRC: func() dns.RDRCStore { cacheFile := service.FromContext[adapter.CacheFile](ctx) if cacheFile == nil { From 2dbb8c55c9e6aa6ab9388621c1d03a769e343d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 14 Nov 2024 18:31:37 +0800 Subject: [PATCH 0173/1272] Add override destination to route options --- adapter/inbound.go | 7 +-- adapter/outbound/default.go | 24 ++++----- common/dialer/default_parallel_network.go | 44 +++++++++++----- docs/configuration/dns/index.md | 9 +--- docs/configuration/dns/index.zh.md | 10 ++-- docs/configuration/outbound/direct.md | 20 ++++++-- docs/configuration/outbound/direct.zh.md | 22 +++++--- docs/configuration/route/rule_action.md | 50 +++++++++++-------- docs/configuration/route/rule_action.zh.md | 58 +++++++++++++--------- docs/deprecated.md | 6 +++ docs/deprecated.zh.md | 7 +++ docs/migration.md | 38 ++++++++++++++ docs/migration.zh.md | 42 +++++++++++++++- experimental/deprecated/constants.go | 10 ++++ option/direct.go | 29 +++++++++-- option/rule_action.go | 25 +++++----- route/route.go | 37 +++++++++++--- route/rule/rule_action.go | 7 +++ 18 files changed, 324 insertions(+), 121 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index 1093bac4e6..d3cc95b7d9 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -61,9 +61,10 @@ type InboundContext struct { // cache // Deprecated: implement in rule action - InboundDetour string - LastInbound string - OriginDestination M.Socksaddr + InboundDetour string + LastInbound string + OriginDestination M.Socksaddr + RouteOriginalDestination M.Socksaddr // Deprecated InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go index 27cafb9d2f..573673f287 100644 --- a/adapter/outbound/default.go +++ b/adapter/outbound/default.go @@ -25,11 +25,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a var outConn net.Conn var err error if len(metadata.DestinationAddresses) > 0 { - if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) - } else { - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) - } + outConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) } @@ -73,11 +69,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, } } else { if len(metadata.DestinationAddresses) > 0 { - if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) - } else { - outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) - } + outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) } @@ -91,11 +83,17 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, return err } if destinationAddress.IsValid() { - if metadata.Destination.IsFqdn() { + var originDestination M.Socksaddr + if metadata.RouteOriginalDestination.IsValid() { + originDestination = metadata.RouteOriginalDestination + } else { + originDestination = metadata.Destination + } + if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) { if metadata.UDPDisableDomainUnmapping { - outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination) } else { - outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination) } } if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { diff --git a/common/dialer/default_parallel_network.go b/common/dialer/default_parallel_network.go index ea043dfdb2..5145656ba8 100644 --- a/common/dialer/default_parallel_network.go +++ b/common/dialer/default_parallel_network.go @@ -13,17 +13,27 @@ import ( N "github.com/sagernet/sing/common/network" ) -func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { +func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var errors []error - for _, address := range destinationAddresses { - conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) - if err == nil { - return conn, nil + if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel { + for _, address := range destinationAddresses { + conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + if err == nil { + return conn, nil + } + errors = append(errors, err) + } + } else { + for _, address := range destinationAddresses { + conn, err := dialer.DialContext(ctx, network, M.SocksaddrFrom(address, destination.Port)) + if err == nil { + return conn, nil + } + errors = append(errors, err) } - errors = append(errors, err) } return nil, E.Errors(errors...) } @@ -106,17 +116,27 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne } } -func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { +func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var errors []error - for _, address := range destinationAddresses { - conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) - if err == nil { - return conn, address, nil + if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel { + for _, address := range destinationAddresses { + conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + if err == nil { + return conn, address, nil + } + errors = append(errors, err) + } + } else { + for _, address := range destinationAddresses { + conn, err := dialer.ListenPacket(ctx, M.SocksaddrFrom(address, destination.Port)) + if err == nil { + return conn, address, nil + } + errors = append(errors, err) } - errors = append(errors, err) } return nil, netip.Addr{}, E.Errors(errors...) } diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index fc81333443..0756281dbf 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -1,16 +1,11 @@ --- -icon: material/new +icon: material/new-box --- - !!! quote "Changes in sing-box 1.11.0" :material-plus: [cache_capacity](#cache_capacity) -!!! quote "Changes in sing-box 1.9.0" - - :material-plus: [client_subnet](#client_subnet) - # DNS ### Structure @@ -70,7 +65,7 @@ Make each DNS server's cache independent for special purposes. If enabled, will #### cache_capacity -!!! quote "Since sing-box 1.11.0" +!!! question "Since sing-box 1.11.0" LRU cache capacity. diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index 20fbc00db5..76c07b6a2c 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -1,15 +1,11 @@ --- -icon: material/new +icon: material/new-box --- -!!! quote "自 sing-box 1.11.0 起" +!!! quote "sing-box 1.11.0 中的更改" :material-plus: [cache_capacity](#cache_capacity) -!!! quote "自 sing-box 1.9.0 起" - - :material-plus: [client_subnet](#client_subnet) - # DNS ### 结构 @@ -68,7 +64,7 @@ icon: material/new #### cache_capacity -!!! quote "自 sing-box 1.11.0 起" +!!! question "自 sing-box 1.11.0 起" LRU 缓存容量。 diff --git a/docs/configuration/outbound/direct.md b/docs/configuration/outbound/direct.md index c2f5671a6b..71649243a5 100644 --- a/docs/configuration/outbound/direct.md +++ b/docs/configuration/outbound/direct.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-alert-decagram: [override_address](#override_address) + :material-alert-decagram: [override_port](#override_port) + `direct` outbound send requests directly. ### Structure @@ -9,7 +18,6 @@ "override_address": "1.0.0.1", "override_port": 53, - "proxy_protocol": 0, ... // Dial Fields } @@ -19,15 +27,19 @@ #### override_address +!!! failure "Deprecated in sing-box 1.11.0" + + Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options). + Override the connection destination address. #### override_port -Override the connection destination port. +!!! failure "Deprecated in sing-box 1.11.0" -#### proxy_protocol + Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options). -Write [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header. +Override the connection destination port. Protocol value can be `1` or `2`. diff --git a/docs/configuration/outbound/direct.zh.md b/docs/configuration/outbound/direct.zh.md index aee131758a..55d3bf8c2d 100644 --- a/docs/configuration/outbound/direct.zh.md +++ b/docs/configuration/outbound/direct.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-alert-decagram: [override_address](#override_address) + :material-alert-decagram: [override_port](#override_port) + `direct` 出站直接发送请求。 ### 结构 @@ -9,7 +18,6 @@ "override_address": "1.0.0.1", "override_port": 53, - "proxy_protocol": 0, ... // 拨号字段 } @@ -19,17 +27,19 @@ #### override_address +!!! failure "已在 sing-box 1.11.0 废弃" + + 目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。 + 覆盖连接目标地址。 #### override_port -覆盖连接目标端口。 - -#### proxy_protocol +!!! failure "已在 sing-box 1.11.0 废弃" -写出 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) 到连接头。 + 目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。 -可用协议版本值:`1` 或 `2`。 +覆盖连接目标端口。 ### 拨号字段 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 6462c84836..63e2b00b75 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -10,12 +10,8 @@ icon: material/new-box { "action": "route", // default "outbound": "", - "network_strategy": "", - "network_type": [], - "fallback_network_type": [], - "fallback_delay": "", - "udp_disable_domain_unmapping": false, - "udp_connect": false + + ... // route-options Fields } ``` @@ -31,6 +27,34 @@ icon: material/new-box Tag of target outbound. +#### route-options Fields + +See `route-options` fields below. + +### route-options + +```json +{ + "action": "route-options", + "override_address": "", + "override_port": 0, + "network_strategy": "", + "fallback_delay": "", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +`route-options` set options for routing. + +#### override_address + +Override the connection destination address. + +#### override_port + +Override the connection destination port. + #### network_strategy See [Dial Fields](/configuration/shared/dial/#network_strategy) for details. @@ -62,20 +86,6 @@ do not support receiving UDP packets with domain addresses, such as Surge. If enabled, attempts to connect UDP connection to the destination instead of listen. -### route-options - -```json -{ - "action": "route-options", - "network_strategy": "", - "fallback_delay": "", - "udp_disable_domain_unmapping": false, - "udp_connect": false -} -``` - -`route-options` set options for routing. - ### reject ```json diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index acadb66d3c..7959fced45 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -10,12 +10,8 @@ icon: material/new-box { "action": "route", // 默认 "outbound": "", - "network_strategy": "", - "fallback_delay": "", - "network_type": [], - "fallback_network_type": [], - "udp_disable_domain_unmapping": false, - "udp_connect": false + + ... // route-options 字段 } ``` @@ -27,6 +23,38 @@ icon: material/new-box 目标出站的标签。 +#### route-options 字段 + +参阅下方的 `route-options` 字段。 + +### route-options + +```json +{ + "action": "route-options", + "override_address": "", + "override_port": 0, + "network_strategy": "", + "fallback_delay": "", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + +`route-options` 为路由设置选项。 + +#### override_address + +覆盖目标地址。 + +#### override_port + +覆盖目标端口。 + #### network_strategy 详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 @@ -56,24 +84,6 @@ icon: material/new-box 如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 -### route-options - -```json -{ - "action": "route-options", - "network_strategy": "", - "fallback_delay": "", - "udp_disable_domain_unmapping": false, - "udp_connect": false -} -``` - -!!! note "" - - 当内容只有一项时,可以忽略 JSON 数组 [] 标签 - -`route-options` 为路由设置选项。 - ### reject ```json diff --git a/docs/deprecated.md b/docs/deprecated.md index d57c93ade4..5dcec56241 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -22,6 +22,12 @@ check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions). Old fields will be removed in sing-box 1.13.0. +#### Destination override fields in direct outbound + +Destination override fields (`override_address` / `override_port`) in direct outbound are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-destination-override-fields-to-route-options). + ## 1.10.0 #### TUN address fields are merged diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 76a426d279..6f6c839fce 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -20,6 +20,13 @@ icon: material/delete-alert 旧字段将在 sing-box 1.13.0 中被移除。 +#### direct 出站中的目标地址覆盖字段 + +direct 出站中的目标地址覆盖字段(`override_address` / `override_port`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。 + +旧字段将在 sing-box 1.13.0 中被移除。 + ## 1.10.0 #### Match source 规则项已重命名 diff --git a/docs/migration.md b/docs/migration.md index 8f01c4f031..ea1afa2dd2 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -156,6 +156,44 @@ Inbound fields are deprecated and can be replaced by rule actions. } ``` +### Migrate destination override fields to route options + +Destination override fields in direct outbound are deprecated and can be replaced by route options. + +!!! info "References" + + [Rule Action](/configuration/route/rule_action/) / + [Direct](/configuration/outbound/direct/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "direct", + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "route": { + "rules": [ + { + "action": "route-options", // or route + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + ``` + ## 1.10.0 ### TUN address fields are merged diff --git a/docs/migration.zh.md b/docs/migration.zh.md index dc62f37003..73afbb0519 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -104,6 +104,7 @@ icon: material/arrange-bring-forward ### 迁移旧的入站字段到规则动作 + 入站选项已被弃用,且可以被规则动作替代。 !!! info "参考" @@ -156,6 +157,45 @@ icon: material/arrange-bring-forward } ``` +### 迁移 direct 出站中的目标地址覆盖字段到路由字段 + +direct 出站中的目标地址覆盖字段已废弃,且可以被路由字段替代。 + +!!! info "参考" + + [Rule Action](/zh/configuration/route/rule_action/) / + [Direct](/zh/configuration/outbound/direct/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "direct", + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "route": { + "rules": [ + { + "action": "route-options", // 或 route + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + } + ``` + ## 1.10.0 ### TUN 地址字段已合并 @@ -164,8 +204,6 @@ icon: material/arrange-bring-forward `inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`, `inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。 -旧字段已废弃,且将在 sing-box 1.11.0 中移除。 - !!! info "参考" [TUN](/zh/configuration/inbound/tun/) diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 918b698df9..bf278f844e 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -104,6 +104,15 @@ var OptionInboundOptions = Note{ 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 Options = []Note{ OptionBadMatchSource, OptionGEOIP, @@ -111,4 +120,5 @@ var Options = []Note{ OptionTUNAddressX, OptionSpecialOutbounds, OptionInboundOptions, + OptionDestinationOverrideFields, } diff --git a/option/direct.go b/option/direct.go index 8624846dbc..180ff0aa23 100644 --- a/option/direct.go +++ b/option/direct.go @@ -1,5 +1,12 @@ package option +import ( + "context" + + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing/common/json" +) + type DirectInboundOptions struct { ListenOptions Network NetworkList `json:"network,omitempty"` @@ -7,9 +14,25 @@ type DirectInboundOptions struct { OverridePort uint16 `json:"override_port,omitempty"` } -type DirectOutboundOptions struct { +type _DirectOutboundOptions struct { DialerOptions + // Deprecated: Use Route Action instead OverrideAddress string `json:"override_address,omitempty"` - OverridePort uint16 `json:"override_port,omitempty"` - ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` + // Deprecated: Use Route Action instead + OverridePort uint16 `json:"override_port,omitempty"` + // Deprecated: removed + ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` +} + +type DirectOutboundOptions _DirectOutboundOptions + +func (d *DirectOutboundOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalDisallowUnknownFields(content, (*_DirectOutboundOptions)(d)) + if err != nil { + return err + } + if d.OverrideAddress != "" || d.OverridePort != 0 { + deprecated.Report(ctx, deprecated.OptionDestinationOverrideFields) + } + return nil } diff --git a/option/rule_action.go b/option/rule_action.go index 7c31ea7a42..ce3b92d9e9 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -137,24 +137,25 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e } type RouteActionOptions struct { - Outbound string `json:"outbound,omitempty"` - NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` - FallbackDelay uint32 `json:"fallback_delay,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - UDPConnect bool `json:"udp_connect,omitempty"` + Outbound string `json:"outbound,omitempty"` + RawRouteOptionsActionOptions } -type _RouteOptionsActionOptions struct { - NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` - FallbackDelay uint32 `json:"fallback_delay,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - UDPConnect bool `json:"udp_connect,omitempty"` +type RawRouteOptionsActionOptions struct { + OverrideAddress string `json:"override_address,omitempty"` + OverridePort uint16 `json:"override_port,omitempty"` + + NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay uint32 `json:"fallback_delay,omitempty"` + + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` } -type RouteOptionsActionOptions _RouteOptionsActionOptions +type RouteOptionsActionOptions RawRouteOptionsActionOptions func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, (*_RouteOptionsActionOptions)(r)) + err := json.Unmarshal(data, (*RawRouteOptionsActionOptions)(r)) if err != nil { return err } diff --git a/route/route.go b/route/route.go index 051ee403ba..7d21d3154c 100644 --- a/route/route.go +++ b/route/route.go @@ -422,17 +422,38 @@ match: } } } + var routeOptions *rule.RuleActionRouteOptions switch action := currentRule.Action().(type) { case *rule.RuleActionRoute: - metadata.NetworkStrategy = action.NetworkStrategy - metadata.FallbackDelay = action.FallbackDelay - metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping - metadata.UDPConnect = action.UDPConnect + routeOptions = &action.RuleActionRouteOptions case *rule.RuleActionRouteOptions: - metadata.NetworkStrategy = action.NetworkStrategy - metadata.FallbackDelay = action.FallbackDelay - metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping - metadata.UDPConnect = action.UDPConnect + routeOptions = action + } + if routeOptions != nil { + // TODO: add nat + if (routeOptions.OverrideAddress.IsValid() || routeOptions.OverridePort > 0) && !metadata.RouteOriginalDestination.IsValid() { + metadata.RouteOriginalDestination = metadata.Destination + } + if routeOptions.OverrideAddress.IsValid() { + metadata.Destination = M.Socksaddr{ + Addr: routeOptions.OverrideAddress.Addr, + Port: metadata.Destination.Port, + Fqdn: routeOptions.OverrideAddress.Fqdn, + } + } + if routeOptions.OverridePort > 0 { + metadata.Destination = M.Socksaddr{ + Addr: metadata.Destination.Addr, + Port: routeOptions.OverridePort, + Fqdn: metadata.Destination.Fqdn, + } + } + metadata.NetworkStrategy = routeOptions.NetworkStrategy + metadata.FallbackDelay = routeOptions.FallbackDelay + metadata.UDPDisableDomainUnmapping = routeOptions.UDPDisableDomainUnmapping + metadata.UDPConnect = routeOptions.UDPConnect + } + switch action := currentRule.Action().(type) { case *rule.RuleActionSniff: if !preMatch { newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 52e9520726..1b4099c903 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -19,6 +19,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -30,6 +31,8 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti return &RuleActionRoute{ Outbound: action.RouteOptions.Outbound, RuleActionRouteOptions: RuleActionRouteOptions{ + OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0), + OverridePort: action.RouteOptions.OverridePort, NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy), FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, @@ -38,6 +41,8 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti }, nil case C.RuleActionTypeRouteOptions: return &RuleActionRouteOptions{ + OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptionsOptions.OverrideAddress, 0), + OverridePort: action.RouteOptionsOptions.OverridePort, NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy), FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, @@ -139,6 +144,8 @@ func (r *RuleActionRoute) String() string { } type RuleActionRouteOptions struct { + OverrideAddress M.Socksaddr + OverridePort uint16 NetworkStrategy C.NetworkStrategy NetworkType []C.InterfaceType FallbackNetworkType []C.InterfaceType From 285a82050c7e650d79e2aeca34ef485d5cec17a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 16 Nov 2024 12:15:14 +0800 Subject: [PATCH 0174/1272] documentation: Fix typo --- docs/configuration/route/index.md | 2 +- docs/configuration/route/index.zh.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 58f733528e..1a1919e94b 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -9,7 +9,7 @@ icon: material/new-box :material-plus: [default_network_strategy](#default_network_strategy) :material-plus: [default_network_type](#default_network_type) :material-plus: [default_fallback_network_type](#default_fallback_network_type) - :material-alert: [default_fallback_delay](#default_fallback_delay) + :material-plus: [default_fallback_delay](#default_fallback_delay) !!! quote "Changes in sing-box 1.8.0" diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md index 9550b4ac3b..a224ddc4c5 100644 --- a/docs/configuration/route/index.zh.md +++ b/docs/configuration/route/index.zh.md @@ -9,7 +9,7 @@ icon: material/new-box :material-plus: [network_strategy](#network_strategy) :material-plus: [default_network_type](#default_network_type) :material-plus: [default_fallback_network_type](#default_fallback_network_type) - :material-alert: [default_fallback_delay](#default_fallback_delay) + :material-plus: [default_fallback_delay](#default_fallback_delay) !!! quote "sing-box 1.8.0 中的更改" From fd299a0961a721c43c14193ccf964e43d0abab7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 20 Nov 2024 11:32:02 +0800 Subject: [PATCH 0175/1272] refactor: connection manager --- adapter/connections.go | 14 ++ box.go | 18 +- protocol/direct/inbound.go | 2 +- route/conn.go | 332 +++++++++++++++++++++++++++++++++++++ route/conn_monitor.go | 128 ++++++++++++++ route/conn_monitor_test.go | 43 +++++ route/dns.go | 6 +- route/geo_resources.go | 8 +- route/network.go | 8 + route/route.go | 42 ++--- route/router.go | 20 ++- 11 files changed, 569 insertions(+), 52 deletions(-) create mode 100644 adapter/connections.go create mode 100644 route/conn.go create mode 100644 route/conn_monitor.go create mode 100644 route/conn_monitor_test.go diff --git a/adapter/connections.go b/adapter/connections.go new file mode 100644 index 0000000000..0682d05a0f --- /dev/null +++ b/adapter/connections.go @@ -0,0 +1,14 @@ +package adapter + +import ( + "context" + "net" + + N "github.com/sagernet/sing/common/network" +) + +type ConnectionManager interface { + Lifecycle + 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 3b69617f64..44a29992e4 100644 --- a/box.go +++ b/box.go @@ -36,9 +36,10 @@ type Box struct { logFactory log.Factory logger log.ContextLogger network *route.NetworkManager - router *route.Router inbound *inbound.Manager outbound *outbound.Manager + connection *route.ConnectionManager + router *route.Router services []adapter.LifecycleService done chan struct{} } @@ -128,6 +129,8 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "initialize network manager") } service.MustRegister[adapter.NetworkManager](ctx, networkManager) + connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection")) + service.MustRegister[adapter.ConnectionManager](ctx, connectionManager) router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS)) if err != nil { return nil, E.Cause(err, "initialize router") @@ -238,9 +241,10 @@ func New(options Options) (*Box, error) { } return &Box{ network: networkManager, - router: router, inbound: inboundManager, outbound: outboundManager, + connection: connectionManager, + router: router, createdAt: createdAt, logFactory: logFactory, logger: logFactory.Logger(), @@ -299,11 +303,11 @@ func (s *Box) preStart() error { if err != nil { return err } - err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound) + err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound) if err != nil { return err } - err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router) + err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router) if err != nil { return err } @@ -323,7 +327,7 @@ func (s *Box) start() error { if err != nil { return err } - err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound) + err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound) if err != nil { return err } @@ -331,7 +335,7 @@ func (s *Box) start() error { if err != nil { return err } - err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound) + err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound) if err != nil { return err } @@ -350,7 +354,7 @@ func (s *Box) Close() error { close(s.done) } err := common.Close( - s.inbound, s.outbound, s.router, s.network, + s.inbound, s.outbound, s.router, s.connection, s.network, ) for _, lifecycleService := range s.services { err = E.Append(err, lifecycleService.Close(), func(err error) error { diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go index 8415e21fd8..6db60d7890 100644 --- a/protocol/direct/inbound.go +++ b/protocol/direct/inbound.go @@ -83,7 +83,7 @@ func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { destination = i.overrideDestination case 2: destination = i.overrideDestination - destination.Port = source.Port + destination.Port = i.listener.UDPAddr().Port case 3: destination = source destination.Port = i.overrideDestination.Port diff --git a/route/conn.go b/route/conn.go new file mode 100644 index 0000000000..594379cc93 --- /dev/null +++ b/route/conn.go @@ -0,0 +1,332 @@ +package route + +import ( + "context" + "io" + "net" + "net/netip" + "sync/atomic" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "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" +) + +var _ adapter.ConnectionManager = (*ConnectionManager)(nil) + +type ConnectionManager struct { + logger logger.ContextLogger + monitor *ConnectionMonitor +} + +func NewConnectionManager(logger logger.ContextLogger) *ConnectionManager { + return &ConnectionManager{ + logger: logger, + monitor: NewConnectionMonitor(), + } +} + +func (m *ConnectionManager) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateInitialize { + return nil + } + return m.monitor.Start() +} + +func (m *ConnectionManager) Close() error { + return m.monitor.Close() +} + +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 ( + remoteConn net.Conn + err error + ) + if len(metadata.DestinationAddresses) > 0 { + remoteConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) + } else { + remoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) + } + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + m.logger.ErrorContext(ctx, "open outbound connection: ", err) + return + } + err = N.ReportConnHandshakeSuccess(conn, remoteConn) + if err != nil { + remoteConn.Close() + N.CloseOnHandshakeFailure(conn, onClose, err) + m.logger.ErrorContext(ctx, "report handshake success: ", err) + return + } + var done atomic.Bool + if ctx.Done() != nil { + onClose = N.AppendClose(onClose, m.monitor.Add(ctx, conn)) + } + go m.connectionCopy(ctx, conn, remoteConn, false, &done, onClose) + go m.connectionCopy(ctx, remoteConn, conn, true, &done, onClose) +} + +func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader, destination io.Writer, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { + originSource := source + originDestination := destination + var readCounters, writeCounters []N.CountFunc + for { + source, readCounters = N.UnwrapCountReader(source, readCounters) + destination, writeCounters = N.UnwrapCountWriter(destination, writeCounters) + if cachedSrc, isCached := source.(N.CachedReader); isCached { + cachedBuffer := cachedSrc.ReadCached() + if cachedBuffer != nil { + dataLen := cachedBuffer.Len() + _, err := destination.Write(cachedBuffer.Bytes()) + cachedBuffer.Release() + if err != nil { + m.logger.ErrorContext(ctx, "connection upload payload: ", err) + if done.Swap(true) { + if onClose != nil { + onClose(err) + } + } + common.Close(originSource, originDestination) + return + } + for _, counter := range readCounters { + counter(int64(dataLen)) + } + for _, counter := range writeCounters { + counter(int64(dataLen)) + } + } + continue + } + break + } + _, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters) + if err != nil { + common.Close(originSource, originDestination) + } else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex { + err = duplexDst.CloseWrite() + if err != nil { + common.Close(originSource, originDestination) + } + } else { + common.Close(originDestination) + } + if done.Swap(true) { + if onClose != nil { + onClose(err) + } + common.Close(originSource, originDestination) + } + if !direction { + if err == nil { + m.logger.DebugContext(ctx, "connection upload finished") + } else if !E.IsClosedOrCanceled(err) { + m.logger.ErrorContext(ctx, "connection upload closed: ", err) + } else { + m.logger.TraceContext(ctx, "connection upload closed") + } + } else { + if err == nil { + m.logger.DebugContext(ctx, "connection download finished") + } else if !E.IsClosedOrCanceled(err) { + m.logger.ErrorContext(ctx, "connection download closed: ", err) + } else { + m.logger.TraceContext(ctx, "connection download closed") + } + } +} + +func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + ctx = adapter.WithContext(ctx, &metadata) + var ( + remotePacketConn net.PacketConn + remoteConn net.Conn + destinationAddress netip.Addr + err error + ) + if metadata.UDPConnect { + if len(metadata.DestinationAddresses) > 0 { + if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { + remoteConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) + } else { + remoteConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + } + } else { + remoteConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) + } + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + m.logger.ErrorContext(ctx, "open outbound packet connection: ", err) + return + } + remotePacketConn = bufio.NewUnbindPacketConn(remoteConn) + connRemoteAddr := M.AddrFromNet(remoteConn.RemoteAddr()) + if connRemoteAddr != metadata.Destination.Addr { + destinationAddress = connRemoteAddr + } + } 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 { + remotePacketConn, err = this.ListenPacket(ctx, metadata.Destination) + } + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + m.logger.ErrorContext(ctx, "listen outbound packet connection: ", err) + return + } + } + err = N.ReportPacketConnHandshakeSuccess(conn, remotePacketConn) + if err != nil { + conn.Close() + remotePacketConn.Close() + m.logger.ErrorContext(ctx, "report handshake success: ", err) + return + } + if destinationAddress.IsValid() { + var originDestination M.Socksaddr + if metadata.RouteOriginalDestination.IsValid() { + originDestination = metadata.RouteOriginalDestination + } else { + originDestination = metadata.Destination + } + 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) + } + } + if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { + natConn.UpdateDestination(destinationAddress) + } + } + destination := bufio.NewPacketConn(remotePacketConn) + var done atomic.Bool + if ctx.Done() != nil { + onClose = N.AppendClose(onClose, m.monitor.Add(ctx, conn)) + } + go m.packetConnectionCopy(ctx, conn, destination, false, &done, onClose) + go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose) +} + +func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { + _, err := bufio.CopyPacket(destination, source) + /*var readCounters, writeCounters []N.CountFunc + var cachedPackets []*N.PacketBuffer + originSource := source + for { + source, readCounters = N.UnwrapCountPacketReader(source, readCounters) + destination, writeCounters = N.UnwrapCountPacketWriter(destination, writeCounters) + if cachedReader, isCached := source.(N.CachedPacketReader); isCached { + packet := cachedReader.ReadCachedPacket() + if packet != nil { + cachedPackets = append(cachedPackets, packet) + continue + } + } + break + } + var handled bool + if natConn, isNatConn := source.(udpnat.Conn); isNatConn { + natConn.SetHandler(&udpHijacker{ + ctx: ctx, + logger: m.logger, + source: natConn, + destination: destination, + direction: direction, + readCounters: readCounters, + writeCounters: writeCounters, + done: done, + onClose: onClose, + }) + handled = true + } + if cachedPackets != nil { + _, err := bufio.WritePacketWithPool(originSource, destination, cachedPackets, readCounters, writeCounters) + if err != nil { + common.Close(source, destination) + m.logger.ErrorContext(ctx, "packet upload payload: ", err) + return + } + } + if handled { + return + } + _, err := bufio.CopyPacketWithCounters(destination, source, originSource, readCounters, writeCounters)*/ + if !direction { + if E.IsClosedOrCanceled(err) { + m.logger.TraceContext(ctx, "packet upload closed") + } else { + m.logger.DebugContext(ctx, "packet upload closed: ", err) + } + } else { + if E.IsClosedOrCanceled(err) { + m.logger.TraceContext(ctx, "packet download closed") + } else { + m.logger.DebugContext(ctx, "packet download closed: ", err) + } + } + if !done.Swap(true) { + if onClose != nil { + onClose(err) + } + } + common.Close(source, destination) +} + +/*type udpHijacker struct { + ctx context.Context + logger logger.ContextLogger + source io.Closer + destination N.PacketWriter + direction bool + readCounters []N.CountFunc + writeCounters []N.CountFunc + done *atomic.Bool + onClose N.CloseHandlerFunc +} + +func (u *udpHijacker) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + dataLen := buffer.Len() + for _, counter := range u.readCounters { + counter(int64(dataLen)) + } + err := u.destination.WritePacket(buffer, source) + if err != nil { + common.Close(u.source, u.destination) + u.logger.DebugContext(u.ctx, "packet upload closed: ", err) + return + } + for _, counter := range u.writeCounters { + counter(int64(dataLen)) + } +} + +func (u *udpHijacker) Close() error { + var err error + if !u.done.Swap(true) { + err = common.Close(u.source, u.destination) + if u.onClose != nil { + u.onClose(net.ErrClosed) + } + } + if u.direction { + u.logger.TraceContext(u.ctx, "packet download closed") + } else { + u.logger.TraceContext(u.ctx, "packet upload closed") + } + return err +} + +func (u *udpHijacker) Upstream() any { + return u.destination +} +*/ diff --git a/route/conn_monitor.go b/route/conn_monitor.go new file mode 100644 index 0000000000..9e271b82a0 --- /dev/null +++ b/route/conn_monitor.go @@ -0,0 +1,128 @@ +package route + +import ( + "context" + "io" + "reflect" + "sync" + "time" + + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" +) + +type ConnectionMonitor struct { + access sync.RWMutex + reloadChan chan struct{} + connections list.List[*monitorEntry] +} + +type monitorEntry struct { + ctx context.Context + closer io.Closer +} + +func NewConnectionMonitor() *ConnectionMonitor { + return &ConnectionMonitor{ + reloadChan: make(chan struct{}, 1), + } +} + +func (m *ConnectionMonitor) Add(ctx context.Context, closer io.Closer) N.CloseHandlerFunc { + m.access.Lock() + defer m.access.Unlock() + element := m.connections.PushBack(&monitorEntry{ + ctx: ctx, + closer: closer, + }) + select { + case <-m.reloadChan: + return nil + default: + select { + case m.reloadChan <- struct{}{}: + default: + } + } + return func(it error) { + m.access.Lock() + defer m.access.Unlock() + m.connections.Remove(element) + select { + case <-m.reloadChan: + default: + select { + case m.reloadChan <- struct{}{}: + default: + } + } + } +} + +func (m *ConnectionMonitor) Start() error { + go m.monitor() + return nil +} + +func (m *ConnectionMonitor) Close() error { + m.access.Lock() + defer m.access.Unlock() + close(m.reloadChan) + for element := m.connections.Front(); element != nil; element = element.Next() { + element.Value.closer.Close() + } + return nil +} + +func (m *ConnectionMonitor) monitor() { + var ( + selectCases []reflect.SelectCase + elements []*list.Element[*monitorEntry] + ) + rootCase := reflect.SelectCase{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(m.reloadChan), + } + for { + m.access.RLock() + if m.connections.Len() == 0 { + m.access.RUnlock() + if _, loaded := <-m.reloadChan; !loaded { + return + } else { + continue + } + } + if len(elements) < m.connections.Len() { + elements = make([]*list.Element[*monitorEntry], 0, m.connections.Len()) + } + if len(selectCases) < m.connections.Len()+1 { + selectCases = make([]reflect.SelectCase, 0, m.connections.Len()+1) + } + elements = elements[:0] + selectCases = selectCases[:1] + selectCases[0] = rootCase + for element := m.connections.Front(); element != nil; element = element.Next() { + elements = append(elements, element) + selectCases = append(selectCases, reflect.SelectCase{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(element.Value.ctx.Done()), + }) + } + m.access.RUnlock() + selected, _, loaded := reflect.Select(selectCases) + if selected == 0 { + if !loaded { + return + } else { + time.Sleep(time.Second) + continue + } + } + element := elements[selected-1] + m.access.Lock() + m.connections.Remove(element) + m.access.Unlock() + element.Value.closer.Close() // maybe go close + } +} diff --git a/route/conn_monitor_test.go b/route/conn_monitor_test.go new file mode 100644 index 0000000000..a712bddcdb --- /dev/null +++ b/route/conn_monitor_test.go @@ -0,0 +1,43 @@ +package route_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/sagernet/sing-box/route" + + "github.com/stretchr/testify/require" +) + +func TestMonitor(t *testing.T) { + t.Parallel() + var closer myCloser + closer.Add(1) + monitor := route.NewConnectionMonitor() + require.NoError(t, monitor.Start()) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + monitor.Add(ctx, &closer) + done := make(chan struct{}) + go func() { + closer.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(time.Second + 100*time.Millisecond): + t.Fatal("timeout") + } + cancel() + require.NoError(t, monitor.Close()) +} + +type myCloser struct { + sync.WaitGroup +} + +func (c *myCloser) Close() error { + c.Done() + return nil +} diff --git a/route/dns.go b/route/dns.go index b69be5a279..2c6efefe40 100644 --- a/route/dns.go +++ b/route/dns.go @@ -32,15 +32,15 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad } func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext) { - if uConn, isUDPNAT2 := conn.(*udpnat.Conn); isUDPNAT2 { + if natConn, isNatConn := conn.(udpnat.Conn); isNatConn { metadata.Destination = M.Socksaddr{} for _, packet := range packetBuffers { buffer := packet.Buffer destination := packet.Destination N.PutPacketBuffer(packet) - go ExchangeDNSPacket(ctx, r, uConn, buffer, metadata, destination) + go ExchangeDNSPacket(ctx, r, natConn, buffer, metadata, destination) } - uConn.SetHandler(&dnsHijacker{ + natConn.SetHandler(&dnsHijacker{ router: r, conn: conn, ctx: ctx, diff --git a/route/geo_resources.go b/route/geo_resources.go index c5a45ffdee..8a8a3ef549 100644 --- a/route/geo_resources.go +++ b/route/geo_resources.go @@ -145,13 +145,13 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error { r.logger.Info("downloading geoip database") var detour adapter.Outbound if r.geoIPOptions.DownloadDetour != "" { - outbound, loaded := r.outboundManager.Outbound(r.geoIPOptions.DownloadDetour) + outbound, loaded := r.outbound.Outbound(r.geoIPOptions.DownloadDetour) if !loaded { return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour) } detour = outbound } else { - detour = r.outboundManager.Default() + detour = r.outbound.Default() } if parentDir := filepath.Dir(savePath); parentDir != "" { @@ -200,13 +200,13 @@ func (r *Router) downloadGeositeDatabase(savePath string) error { r.logger.Info("downloading geosite database") var detour adapter.Outbound if r.geositeOptions.DownloadDetour != "" { - outbound, loaded := r.outboundManager.Outbound(r.geositeOptions.DownloadDetour) + outbound, loaded := r.outbound.Outbound(r.geositeOptions.DownloadDetour) if !loaded { return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour) } detour = outbound } else { - detour = r.outboundManager.Default() + detour = r.outbound.Default() } if parentDir := filepath.Dir(savePath); parentDir != "" { diff --git a/route/network.go b/route/network.go index e7c4df9ceb..510686e521 100644 --- a/route/network.go +++ b/route/network.go @@ -48,6 +48,7 @@ type NetworkManager struct { powerListener winpowrprof.EventListener pauseManager pause.Manager platformInterface platform.Interface + inboundManager adapter.InboundManager outboundManager adapter.OutboundManager wifiState adapter.WIFIState started bool @@ -354,6 +355,13 @@ func (r *NetworkManager) WIFIState() adapter.WIFIState { func (r *NetworkManager) ResetNetwork() { conntrack.Close() + for _, inbound := range r.inboundManager.Inbounds() { + listener, isListener := inbound.(adapter.InterfaceUpdateListener) + if isListener { + listener.InterfaceUpdated() + } + } + for _, outbound := range r.outboundManager.Outbounds() { listener, isListener := outbound.(adapter.InterfaceUpdateListener) if isListener { diff --git a/route/route.go b/route/route.go index 7d21d3154c..a56016f93a 100644 --- a/route/route.go +++ b/route/route.go @@ -11,7 +11,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/sniff" @@ -58,7 +57,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) } - detour, loaded := r.inboundManager.Get(metadata.InboundDetour) + detour, loaded := r.inbound.Get(metadata.InboundDetour) if !loaded { return E.New("inbound detour not found: ", metadata.InboundDetour) } @@ -96,7 +95,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: var loaded bool - selectedOutbound, loaded = r.outboundManager.Outbound(action.Outbound) + selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) if !loaded { buf.ReleaseMulti(buffers) return E.New("outbound not found: ", action.Outbound) @@ -118,7 +117,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad } } if selectedRule == nil { - defaultOutbound := r.outboundManager.Default() + defaultOutbound := r.outbound.Default() if !common.Contains(defaultOutbound.Network(), N.NetworkTCP) { buf.ReleaseMulti(buffers) return E.New("TCP is not supported by default outbound: ", defaultOutbound.Tag()) @@ -148,19 +147,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad } return nil } - // TODO - err = outbound.NewConnection(ctx, selectedOutbound, conn, metadata) - if err != nil { - conn.Close() - if onClose != nil { - onClose(err) - } - return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) - } else { - if onClose != nil { - onClose(nil) - } - } + r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose) return nil } @@ -199,7 +186,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) } - detour, loaded := r.inboundManager.Get(metadata.InboundDetour) + detour, loaded := r.inbound.Get(metadata.InboundDetour) if !loaded { return E.New("inbound detour not found: ", metadata.InboundDetour) } @@ -233,7 +220,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m switch action := selectedRule.Action().(type) { case *rule.RuleActionRoute: var loaded bool - selectedOutbound, loaded = r.outboundManager.Outbound(action.Outbound) + selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) if !loaded { N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("outbound not found: ", action.Outbound) @@ -252,7 +239,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } } if selectedRule == nil || selectReturn { - defaultOutbound := r.outboundManager.Default() + defaultOutbound := r.outbound.Default() if !common.Contains(defaultOutbound.Network(), N.NetworkUDP) { N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("UDP is not supported by outbound: ", defaultOutbound.Tag()) @@ -278,12 +265,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } return nil } - // TODO - err = outbound.NewPacketConnection(ctx, selectedOutbound, conn, metadata) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) - } + r.connection.NewPacketConnection(ctx, selectedOutbound, conn, metadata, onClose) return nil } @@ -450,8 +432,12 @@ match: } metadata.NetworkStrategy = routeOptions.NetworkStrategy metadata.FallbackDelay = routeOptions.FallbackDelay - metadata.UDPDisableDomainUnmapping = routeOptions.UDPDisableDomainUnmapping - metadata.UDPConnect = routeOptions.UDPConnect + if routeOptions.UDPDisableDomainUnmapping { + metadata.UDPDisableDomainUnmapping = true + } + if routeOptions.UDPConnect { + metadata.UDPConnect = true + } } switch action := currentRule.Action().(type) { case *rule.RuleActionSniff: diff --git a/route/router.go b/route/router.go index f23604b306..792391e265 100644 --- a/route/router.go +++ b/route/router.go @@ -38,9 +38,10 @@ type Router struct { ctx context.Context logger log.ContextLogger dnsLogger log.ContextLogger - inboundManager adapter.InboundManager - outboundManager adapter.OutboundManager - networkManager adapter.NetworkManager + inbound adapter.InboundManager + outbound adapter.OutboundManager + connection adapter.ConnectionManager + network adapter.NetworkManager rules []adapter.Rule needGeoIPDatabase bool needGeositeDatabase bool @@ -74,9 +75,10 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route ctx: ctx, logger: logFactory.NewLogger("router"), dnsLogger: logFactory.NewLogger("dns"), - inboundManager: service.FromContext[adapter.InboundManager](ctx), - outboundManager: service.FromContext[adapter.OutboundManager](ctx), - networkManager: service.FromContext[adapter.NetworkManager](ctx), + inbound: service.FromContext[adapter.InboundManager](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + connection: service.FromContext[adapter.ConnectionManager](ctx), + network: service.FromContext[adapter.NetworkManager](ctx), rules: make([]adapter.Rule, 0, len(options.Rules)), dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)), ruleSetMap: make(map[string]adapter.RuleSet), @@ -260,7 +262,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route Context: ctx, Name: "local", Address: "local", - Dialer: common.Must1(dialer.NewDefault(router.networkManager, option.DialerOptions{})), + Dialer: common.Must1(dialer.NewDefault(router.network, option.DialerOptions{})), }))) } defaultTransport = transports[0] @@ -405,7 +407,7 @@ func (r *Router) Start(stage adapter.StartStage) error { monitor.Start("initialize process searcher") searcher, err := process.NewSearcher(process.Config{ Logger: r.logger, - PackageManager: r.networkManager.PackageManager(), + PackageManager: r.network.PackageManager(), }) monitor.Finish() if err != nil { @@ -507,7 +509,7 @@ func (r *Router) SetTracker(tracker adapter.ConnectionTracker) { } func (r *Router) ResetNetwork() { - r.networkManager.ResetNetwork() + r.network.ResetNetwork() for _, transport := range r.transports { transport.Reset() } From 68781387fe7f12e36361e841262e2ed68df7c9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 21 Nov 2024 18:10:41 +0800 Subject: [PATCH 0176/1272] refactor: WireGuard endpoint --- adapter/endpoint.go | 28 ++ adapter/endpoint/adapter.go | 43 +++ adapter/endpoint/manager.go | 147 ++++++++++ adapter/endpoint/registry.go | 72 +++++ adapter/inbound.go | 2 +- adapter/inbound/manager.go | 11 +- adapter/lifecycle_legacy.go | 3 + adapter/outbound/adapter.go | 18 +- adapter/outbound/manager.go | 29 +- box.go | 51 +++- cmd/sing-box/cmd.go | 2 +- common/dialer/default.go | 2 +- docs/configuration/endpoint/index.md | 32 +++ docs/configuration/endpoint/index.zh.md | 32 +++ docs/configuration/endpoint/wireguard.md | 138 ++++++++++ docs/configuration/endpoint/wireguard.zh.md | 140 ++++++++++ docs/configuration/inbound/tun.md | 4 +- docs/configuration/inbound/tun.zh.md | 4 +- docs/configuration/index.md | 2 + docs/configuration/index.zh.md | 2 + docs/configuration/outbound/wireguard.md | 10 +- docs/configuration/outbound/wireguard.zh.md | 10 +- docs/configuration/shared/listen.md | 4 +- docs/configuration/shared/listen.zh.md | 2 +- docs/deprecated.md | 7 + docs/deprecated.zh.md | 7 + docs/migration.md | 72 +++++ docs/migration.zh.md | 73 ++++- experimental/deprecated/constants.go | 10 + experimental/libbox/config.go | 4 +- experimental/libbox/service.go | 2 +- include/registry.go | 9 + include/wireguard.go | 5 + include/wireguard_stub.go | 9 +- mkdocs.yml | 4 + option/endpoint.go | 47 ++++ option/inbound.go | 2 +- option/options.go | 1 + option/outbound.go | 2 +- option/wireguard.go | 38 ++- protocol/block/outbound.go | 2 +- protocol/direct/inbound.go | 5 +- protocol/direct/outbound.go | 2 +- protocol/dns/outbound.go | 2 +- protocol/group/selector.go | 2 +- protocol/group/urltest.go | 2 +- protocol/http/inbound.go | 5 +- protocol/http/outbound.go | 2 +- protocol/hysteria/inbound.go | 5 +- protocol/hysteria/outbound.go | 2 +- protocol/hysteria2/inbound.go | 5 +- protocol/hysteria2/outbound.go | 2 +- protocol/mixed/inbound.go | 5 +- protocol/naive/inbound.go | 5 +- protocol/redirect/redirect.go | 5 +- protocol/redirect/tproxy.go | 5 +- protocol/shadowsocks/inbound.go | 5 +- protocol/shadowsocks/inbound_multi.go | 5 +- protocol/shadowsocks/inbound_relay.go | 5 +- protocol/shadowsocks/outbound.go | 2 +- protocol/shadowtls/inbound.go | 5 +- protocol/shadowtls/outbound.go | 2 +- protocol/socks/inbound.go | 5 +- protocol/socks/outbound.go | 2 +- protocol/ssh/outbound.go | 2 +- protocol/tor/outbound.go | 2 +- protocol/trojan/inbound.go | 5 +- protocol/trojan/outbound.go | 2 +- protocol/tuic/inbound.go | 5 +- protocol/tuic/outbound.go | 2 +- protocol/tun/inbound.go | 182 ++++++------- protocol/vless/inbound.go | 5 +- protocol/vless/outbound.go | 2 +- protocol/vmess/inbound.go | 5 +- protocol/vmess/outbound.go | 2 +- protocol/wireguard/endpoint.go | 211 ++++++++++++++ protocol/wireguard/outbound.go | 257 +++++++----------- route/network.go | 37 ++- route/route_dns.go | 2 +- transport/wireguard/client_bind.go | 36 ++- transport/wireguard/device.go | 37 ++- transport/wireguard/device_stack.go | 123 ++++----- .../{gonet.go => device_stack_gonet.go} | 0 transport/wireguard/device_stack_stub.go | 10 +- transport/wireguard/device_system.go | 151 +++++----- transport/wireguard/device_system_stack.go | 182 +++++++++++++ transport/wireguard/endpoint.go | 253 ++++++++++++++++- transport/wireguard/endpoint_options.go | 40 +++ transport/wireguard/resolve.go | 148 ---------- 89 files changed, 2188 insertions(+), 680 deletions(-) create mode 100644 adapter/endpoint.go create mode 100644 adapter/endpoint/adapter.go create mode 100644 adapter/endpoint/manager.go create mode 100644 adapter/endpoint/registry.go create mode 100644 docs/configuration/endpoint/index.md create mode 100644 docs/configuration/endpoint/index.zh.md create mode 100644 docs/configuration/endpoint/wireguard.md create mode 100644 docs/configuration/endpoint/wireguard.zh.md create mode 100644 option/endpoint.go create mode 100644 protocol/wireguard/endpoint.go rename transport/wireguard/{gonet.go => device_stack_gonet.go} (100%) create mode 100644 transport/wireguard/device_system_stack.go create mode 100644 transport/wireguard/endpoint_options.go delete mode 100644 transport/wireguard/resolve.go diff --git a/adapter/endpoint.go b/adapter/endpoint.go new file mode 100644 index 0000000000..f09f08ce75 --- /dev/null +++ b/adapter/endpoint.go @@ -0,0 +1,28 @@ +package adapter + +import ( + "context" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" +) + +type Endpoint interface { + Lifecycle + Type() string + Tag() string + Outbound +} + +type EndpointRegistry interface { + option.EndpointOptionsRegistry + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error) +} + +type EndpointManager interface { + Lifecycle + Endpoints() []Endpoint + Get(tag string) (Endpoint, bool) + Remove(tag string) error + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error +} diff --git a/adapter/endpoint/adapter.go b/adapter/endpoint/adapter.go new file mode 100644 index 0000000000..c75e4d839c --- /dev/null +++ b/adapter/endpoint/adapter.go @@ -0,0 +1,43 @@ +package endpoint + +import "github.com/sagernet/sing-box/option" + +type Adapter struct { + endpointType string + endpointTag string + network []string + dependencies []string +} + +func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter { + return Adapter{ + endpointType: endpointType, + endpointTag: endpointTag, + network: network, + dependencies: dependencies, + } +} + +func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter { + var dependencies []string + if dialOptions.Detour != "" { + dependencies = []string{dialOptions.Detour} + } + return NewAdapter(endpointType, endpointTag, network, dependencies) +} + +func (a *Adapter) Type() string { + return a.endpointType +} + +func (a *Adapter) Tag() string { + return a.endpointTag +} + +func (a *Adapter) Network() []string { + return a.network +} + +func (a *Adapter) Dependencies() []string { + return a.dependencies +} diff --git a/adapter/endpoint/manager.go b/adapter/endpoint/manager.go new file mode 100644 index 0000000000..5a633beed3 --- /dev/null +++ b/adapter/endpoint/manager.go @@ -0,0 +1,147 @@ +package endpoint + +import ( + "context" + "os" + "sync" + + "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/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +var _ adapter.EndpointManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.EndpointRegistry + access sync.Mutex + started bool + stage adapter.StartStage + endpoints []adapter.Endpoint + endpointByTag map[string]adapter.Endpoint +} + +func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager { + return &Manager{ + logger: logger, + registry: registry, + endpointByTag: make(map[string]adapter.Endpoint), + } +} + +func (m *Manager) Start(stage adapter.StartStage) error { + m.access.Lock() + defer m.access.Unlock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + if stage == adapter.StartStateStart { + // started with outbound manager + return nil + } + for _, endpoint := range m.endpoints { + err := adapter.LegacyStart(endpoint, stage) + if err != nil { + return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]") + } + } + return nil +} + +func (m *Manager) Close() error { + m.access.Lock() + defer m.access.Unlock() + if !m.started { + return nil + } + m.started = false + endpoints := m.endpoints + m.endpoints = nil + monitor := taskmonitor.New(m.logger, C.StopTimeout) + var err error + for _, endpoint := range endpoints { + monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]") + err = E.Append(err, endpoint.Close(), func(err error) error { + return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]") + }) + monitor.Finish() + } + return nil +} + +func (m *Manager) Endpoints() []adapter.Endpoint { + m.access.Lock() + defer m.access.Unlock() + return m.endpoints +} + +func (m *Manager) Get(tag string) (adapter.Endpoint, bool) { + m.access.Lock() + defer m.access.Unlock() + endpoint, found := m.endpointByTag[tag] + return endpoint, found +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + endpoint, found := m.endpointByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.endpointByTag, tag) + index := common.Index(m.endpoints, func(it adapter.Endpoint) bool { + return it == endpoint + }) + if index == -1 { + panic("invalid endpoint index") + } + m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...) + started := m.started + m.access.Unlock() + if started { + return endpoint.Close() + } + return nil +} + +func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error { + endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + for _, stage := range adapter.ListStartStages { + err = adapter.LegacyStart(endpoint, stage) + if err != nil { + return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]") + } + } + } + if existsEndpoint, loaded := m.endpointByTag[tag]; loaded { + if m.started { + err = existsEndpoint.Close() + if err != nil { + return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]") + } + } + existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool { + return it == existsEndpoint + }) + if existsIndex == -1 { + panic("invalid endpoint index") + } + m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...) + } + m.endpoints = append(m.endpoints, endpoint) + m.endpointByTag[tag] = endpoint + return nil +} diff --git a/adapter/endpoint/registry.go b/adapter/endpoint/registry.go new file mode 100644 index 0000000000..92cb9025de --- /dev/null +++ b/adapter/endpoint/registry.go @@ -0,0 +1,72 @@ +package endpoint + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options)) + }) +} + +var _ adapter.EndpointRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructor map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructor: make(map[string]constructorFunc), + } +} + +func (m *Registry) CreateOptions(outboundType string) (any, bool) { + m.access.Lock() + defer m.access.Unlock() + optionsConstructor, loaded := m.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) { + m.access.Lock() + defer m.access.Unlock() + constructor, loaded := m.constructor[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + m.access.Lock() + defer m.access.Unlock() + m.optionsType[outboundType] = optionsConstructor + m.constructor[outboundType] = constructor +} diff --git a/adapter/inbound.go b/adapter/inbound.go index d3cc95b7d9..b4eaca3d4d 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -13,7 +13,7 @@ import ( ) type Inbound interface { - Service + Lifecycle Type() string Tag() string } diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go index d2be0f365e..c690b2c913 100644 --- a/adapter/inbound/manager.go +++ b/adapter/inbound/manager.go @@ -18,6 +18,7 @@ var _ adapter.InboundManager = (*Manager)(nil) type Manager struct { logger log.ContextLogger registry adapter.InboundRegistry + endpoint adapter.EndpointManager access sync.Mutex started bool stage adapter.StartStage @@ -25,10 +26,11 @@ type Manager struct { inboundByTag map[string]adapter.Inbound } -func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry) *Manager { +func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager { return &Manager{ logger: logger, registry: registry, + endpoint: endpoint, inboundByTag: make(map[string]adapter.Inbound), } } @@ -79,9 +81,12 @@ func (m *Manager) Inbounds() []adapter.Inbound { func (m *Manager) Get(tag string) (adapter.Inbound, bool) { m.access.Lock() - defer m.access.Unlock() inbound, found := m.inboundByTag[tag] - return inbound, found + m.access.Unlock() + if found { + return inbound, true + } + return m.endpoint.Get(tag) } func (m *Manager) Remove(tag string) error { diff --git a/adapter/lifecycle_legacy.go b/adapter/lifecycle_legacy.go index 0c8c75daed..94a5cf8c8d 100644 --- a/adapter/lifecycle_legacy.go +++ b/adapter/lifecycle_legacy.go @@ -1,6 +1,9 @@ package adapter func LegacyStart(starter any, stage StartStage) error { + if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle { + return lifecycle.Start(stage) + } switch stage { case StartStateInitialize: if preStarter, isPreStarter := starter.(interface { diff --git a/adapter/outbound/adapter.go b/adapter/outbound/adapter.go index 481bb6197d..cd71527af5 100644 --- a/adapter/outbound/adapter.go +++ b/adapter/outbound/adapter.go @@ -5,35 +5,35 @@ import ( ) type Adapter struct { - protocol string + outboundType string + outboundTag string network []string - tag string dependencies []string } -func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter { +func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter { return Adapter{ - protocol: protocol, + outboundType: outboundType, + outboundTag: outboundTag, network: network, - tag: tag, dependencies: dependencies, } } -func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter { +func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter { var dependencies []string if dialOptions.Detour != "" { dependencies = []string{dialOptions.Detour} } - return NewAdapter(protocol, network, tag, dependencies) + return NewAdapter(outboundType, outboundTag, network, dependencies) } func (a *Adapter) Type() string { - return a.protocol + return a.outboundType } func (a *Adapter) Tag() string { - return a.tag + return a.outboundTag } func (a *Adapter) Network() []string { diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index 84a105c59e..c3941d02b5 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -21,6 +21,7 @@ var _ adapter.OutboundManager = (*Manager)(nil) type Manager struct { logger log.ContextLogger registry adapter.OutboundRegistry + endpoint adapter.EndpointManager defaultTag string access sync.Mutex started bool @@ -32,10 +33,11 @@ type Manager struct { defaultOutboundFallback adapter.Outbound } -func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, defaultTag string) *Manager { +func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager { return &Manager{ logger: logger, registry: registry, + endpoint: endpoint, defaultTag: defaultTag, outboundByTag: make(map[string]adapter.Outbound), dependByTag: make(map[string][]string), @@ -56,7 +58,14 @@ func (m *Manager) Start(stage adapter.StartStage) error { outbounds := m.outbounds m.access.Unlock() if stage == adapter.StartStateStart { - return m.startOutbounds(outbounds) + if m.defaultTag != "" && m.defaultOutbound == nil { + defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag) + if !loaded { + return E.New("default outbound not found: ", m.defaultTag) + } + m.defaultOutbound = defaultEndpoint + } + return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...)) } else { for _, outbound := range outbounds { err := adapter.LegacyStart(outbound, stage) @@ -87,7 +96,14 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error { } started[outboundTag] = true canContinue = true - if starter, isStarter := outboundToStart.(interface { + if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter { + monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + err := starter.Start(adapter.StartStateStart) + monitor.Finish() + if err != nil { + return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + } + } else if starter, isStarter := outboundToStart.(interface { Start() error }); isStarter { monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]") @@ -160,9 +176,12 @@ func (m *Manager) Outbounds() []adapter.Outbound { func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) { m.access.Lock() - defer m.access.Unlock() outbound, found := m.outboundByTag[tag] - return outbound, found + m.access.Unlock() + if found { + return outbound, true + } + return m.endpoint.Get(tag) } func (m *Manager) Default() adapter.Outbound { diff --git a/box.go b/box.go index 44a29992e4..5dc76ebc7b 100644 --- a/box.go +++ b/box.go @@ -9,6 +9,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" @@ -36,6 +37,7 @@ type Box struct { logFactory log.Factory logger log.ContextLogger network *route.NetworkManager + endpoint *endpoint.Manager inbound *inbound.Manager outbound *outbound.Manager connection *route.ConnectionManager @@ -54,6 +56,7 @@ func Context( ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry, + endpointRegistry adapter.EndpointRegistry, ) context.Context { if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || service.FromContext[adapter.InboundRegistry](ctx) == nil { @@ -65,6 +68,11 @@ func Context( ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry) ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry) } + if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil || + service.FromContext[adapter.EndpointRegistry](ctx) == nil { + ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry) + ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry) + } return ctx } @@ -76,12 +84,16 @@ func New(options Options) (*Box, error) { } ctx = service.ContextWithDefaultRegistry(ctx) + endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) + outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) + + if endpointRegistry == nil { + return nil, E.New("missing endpoint registry in context") + } if inboundRegistry == nil { return nil, E.New("missing inbound registry in context") } - - outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) if outboundRegistry == nil { return nil, E.New("missing outbound registry in context") } @@ -119,8 +131,10 @@ func New(options Options) (*Box, error) { } routeOptions := common.PtrValueOrDefault(options.Route) - inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry) - outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final) + endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry) + inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager) + outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final) + service.MustRegister[adapter.EndpointManager](ctx, endpointManager) service.MustRegister[adapter.InboundManager](ctx, inboundManager) service.MustRegister[adapter.OutboundManager](ctx, outboundManager) @@ -135,6 +149,24 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "initialize router") } + for i, endpointOptions := range options.Endpoints { + var tag string + if endpointOptions.Tag != "" { + tag = endpointOptions.Tag + } else { + tag = F.ToString(i) + } + err = endpointManager.Create(ctx, + router, + logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")), + tag, + endpointOptions.Type, + endpointOptions.Options, + ) + if err != nil { + return nil, E.Cause(err, "initialize inbound[", i, "]") + } + } for i, inboundOptions := range options.Inbounds { var tag string if inboundOptions.Tag != "" { @@ -241,6 +273,7 @@ func New(options Options) (*Box, error) { } return &Box{ network: networkManager, + endpoint: endpointManager, inbound: inboundManager, outbound: outboundManager, connection: connectionManager, @@ -303,7 +336,7 @@ func (s *Box) preStart() error { if err != nil { return err } - err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound) + err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint) if err != nil { return err } @@ -327,7 +360,11 @@ func (s *Box) start() error { if err != nil { return err } - err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound) + err = adapter.Start(adapter.StartStateStart, s.endpoint) + if err != nil { + return err + } + err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint) if err != nil { return err } @@ -335,7 +372,7 @@ func (s *Box) start() error { if err != nil { return err } - err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound) + err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint) if err != nil { return err } diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go index dc7a830965..d55235b855 100644 --- a/cmd/sing-box/cmd.go +++ b/cmd/sing-box/cmd.go @@ -69,5 +69,5 @@ func preRun(cmd *cobra.Command, args []string) { configPaths = append(configPaths, "config.json") } globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())) - globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry()) + globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()) } diff --git a/common/dialer/default.go b/common/dialer/default.go index 5871e64007..bf553618f7 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -285,7 +285,7 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - return d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) + return d.udpListener.ListenPacket(context.Background(), network, address) } func trackConn(conn net.Conn, err error) (net.Conn, error) { diff --git a/docs/configuration/endpoint/index.md b/docs/configuration/endpoint/index.md new file mode 100644 index 0000000000..e40333db35 --- /dev/null +++ b/docs/configuration/endpoint/index.md @@ -0,0 +1,32 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.11.0" + +# Endpoint + +Endpoint is protocols that has both inbound and outbound behavior. + +### Structure + +```json +{ + "endpoints": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### Fields + +| Type | Format | +|-------------|---------------------------| +| `wireguard` | [WireGuard](./wireguard/) | + +#### tag + +The tag of the endpoint. diff --git a/docs/configuration/endpoint/index.zh.md b/docs/configuration/endpoint/index.zh.md new file mode 100644 index 0000000000..69ba2d09ce --- /dev/null +++ b/docs/configuration/endpoint/index.zh.md @@ -0,0 +1,32 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.11.0 起" + +# 端点 + +端点是具有入站和出站行为的协议。 + +### 结构 + +```json +{ + "endpoints": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### 字段 + +| 类型 | 格式 | +|-------------|---------------------------| +| `wireguard` | [WireGuard](./wiregaurd/) | + +#### tag + +端点的标签。 diff --git a/docs/configuration/endpoint/wireguard.md b/docs/configuration/endpoint/wireguard.md new file mode 100644 index 0000000000..2d792e0279 --- /dev/null +++ b/docs/configuration/endpoint/wireguard.md @@ -0,0 +1,138 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.11.0" + +### Structure + +```json +{ + "type": "wireguard", + "tag": "wg-ep", + + "system": false, + "name": "", + "mtu": 1408, + "gso": false, + "address": [], + "private_key": "", + "listen_port": 10000, + "peers": [ + { + "address": "127.0.0.1", + "port": 10001, + "public_key": "", + "pre_shared_key": "", + "allowed_ips": [], + "persistent_keepalive_interval": 0, + "reserved": [0, 0, 0] + } + ], + "udp_timeout": "", + "workers": 0, + + ... // Dial Fields +} +``` + +### Fields + +#### system + +Use system interface. + +Requires privilege and cannot conflict with exists system interfaces. + +#### name + +Custom interface name for system interface. + +#### mtu + +WireGuard MTU. + +`1408` will be used by default. + +#### gso + +!!! quote "" + + Only supported on Linux. + +Try to enable generic segmentation offload. + +#### address + +==Required== + +List of IP (v4 or v6) address prefixes to be assigned to the interface. + +#### private_key + +==Required== + +WireGuard requires base64-encoded public and private keys. These can be generated using the wg(8) utility: + +```shell +wg genkey +echo "private key" || wg pubkey +``` + +or `sing-box generate wg-keypair`. + +#### peers + +==Required== + +List of WireGuard peers. + +#### peers.address + +WireGuard peer address. + +#### peers.port + +WireGuard peer port. + +#### peers.public_key + +==Required== + +WireGuard peer public key. + +#### peers.pre_shared_key + +WireGuard peer pre-shared key. + +#### peers.allowed_ips + +==Required== + +WireGuard allowed IPs. + +#### peers.persistent_keepalive_interval + +WireGuard persistent keepalive interval, in seconds. + +Disabled by default. + +#### peers.reserved + +WireGuard reserved field bytes. + +#### udp_timeout + +UDP NAT expiration time. + +`5m` will be used by default. + +#### workers + +WireGuard worker count. + +CPU count is used by default. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/endpoint/wireguard.zh.md b/docs/configuration/endpoint/wireguard.zh.md new file mode 100644 index 0000000000..8941b63095 --- /dev/null +++ b/docs/configuration/endpoint/wireguard.zh.md @@ -0,0 +1,140 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.11.0 起" + +### 结构 + +```json +{ + "type": "wireguard", + "tag": "wg-ep", + + "system": false, + "name": "", + "mtu": 1408, + "gso": false, + "address": [], + "private_key": "", + "listen_port": 10000, + "peers": [ + { + "address": "127.0.0.1", + "port": 10001, + "public_key": "", + "pre_shared_key": "", + "allowed_ips": [], + "persistent_keepalive_interval": 0, + "reserved": [0, 0, 0] + } + ], + "udp_timeout": "", + "workers": 0, + + ... // 拨号字段 +} +``` + +### 字段 + +#### system_interface + +使用系统设备。 + +需要特权且不能与已有系统接口冲突。 + +#### name + +为系统接口自定义设备名称。 + +#### mtu + +WireGuard MTU。 + +默认使用 1408。 + +#### gso + +!!! quote "" + + 仅支持 Linux。 + +尝试启用通用分段卸载。 + +#### address + +==必填== + +接口的 IPv4/IPv6 地址或地址段的列表您。 + +要分配给接口的 IP(v4 或 v6)地址段列表。 + +#### private_key + +==必填== + +WireGuard 需要 base64 编码的公钥和私钥。 这些可以使用 wg(8) 实用程序生成: + +```shell +wg genkey +echo "private key" || wg pubkey +``` + +或 `sing-box generate wg-keypair`. + +#### peers + +==必填== + +WireGuard 对等方的列表。 + +#### peers.address + +对等方的 IP 地址。 + +#### peers.port + +对等方的 WireGuard 端口。 + +#### peers.public_key + +==必填== + +对等方的 WireGuard 公钥。 + +#### peers.pre_shared_key + +对等方的预共享密钥。 + +#### peers.allowed_ips + +==必填== + +对等方的允许 IP 地址。 + +#### peers.persistent_keepalive_interval + +对等方的持久性保持活动间隔,以秒为单位。 + +默认禁用。 + +#### peers.reserved + +对等方的保留字段字节。 + +#### udp_timeout + +UDP NAT 过期时间。 + +默认使用 `5m`。 + +#### workers + +WireGuard worker 数量。 + +默认使用 CPU 数量。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index cc2f5603c8..51b8a10a3c 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -360,7 +360,9 @@ Performance may degrade slightly, so it is not recommended to enable on when it #### udp_timeout -UDP NAT expiration time in seconds, default is 300 (5 minutes). +UDP NAT expiration time. + +`5m` will be used by default. #### stack diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 6bc1eb8793..0b8a6aa127 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -356,7 +356,9 @@ tun 接口的 IPv6 前缀。 #### udp_timeout -UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。 +UDP NAT 过期时间。 + +默认使用 `5m`。 #### stack diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 0c22fc25ab..05e6a87da8 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -9,6 +9,7 @@ sing-box uses JSON for configuration files. "log": {}, "dns": {}, "ntp": {}, + "endpoints": [], "inbounds": [], "outbounds": [], "route": {}, @@ -23,6 +24,7 @@ sing-box uses JSON for configuration files. | `log` | [Log](./log/) | | `dns` | [DNS](./dns/) | | `ntp` | [NTP](./ntp/) | +| `endpoints` | [Endpoint](./endpoint/) | | `inbounds` | [Inbound](./inbound/) | | `outbounds` | [Outbound](./outbound/) | | `route` | [Route](./route/) | diff --git a/docs/configuration/index.zh.md b/docs/configuration/index.zh.md index 0d24a7ca7a..6aeb4857f4 100644 --- a/docs/configuration/index.zh.md +++ b/docs/configuration/index.zh.md @@ -8,6 +8,7 @@ sing-box 使用 JSON 作为配置文件格式。 { "log": {}, "dns": {}, + "endpoints": [], "inbounds": [], "outbounds": [], "route": {}, @@ -21,6 +22,7 @@ sing-box 使用 JSON 作为配置文件格式。 |----------------|------------------------| | `log` | [日志](./log/) | | `dns` | [DNS](./dns/) | +| `endpoints` | [端点](./endpoint/) | | `inbounds` | [入站](./inbound/) | | `outbounds` | [出站](./outbound/) | | `route` | [路由](./route/) | diff --git a/docs/configuration/outbound/wireguard.md b/docs/configuration/outbound/wireguard.md index c3f51f1fb6..e3d2671a43 100644 --- a/docs/configuration/outbound/wireguard.md +++ b/docs/configuration/outbound/wireguard.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.11.0" + + WireGuard outbound is deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-wireguard-outbound-to-endpoint). + !!! quote "Changes in sing-box 1.8.0" :material-plus: [gso](#gso) @@ -15,7 +23,7 @@ "gso": false, "interface_name": "wg0", "local_address": [ - "10.0.0.2/32" + "10.0.0.1/32" ], "private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=", "peers": [ diff --git a/docs/configuration/outbound/wireguard.zh.md b/docs/configuration/outbound/wireguard.zh.md index 5de2813225..63f2ddfd7d 100644 --- a/docs/configuration/outbound/wireguard.zh.md +++ b/docs/configuration/outbound/wireguard.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "已在 sing-box 1.11.0 废弃" + + WireGuard 出站已被启用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。 + !!! quote "sing-box 1.8.0 中的更改" :material-plus: [gso](#gso) @@ -15,7 +23,7 @@ "gso": false, "interface_name": "wg0", "local_address": [ - "10.0.0.2/32" + "10.0.0.1/32" ], "private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=", "peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=", diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index fa6a05b97c..3e1b000f90 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -68,9 +68,9 @@ Enable UDP fragmentation. #### udp_timeout -UDP NAT expiration time in seconds. +UDP NAT expiration time. -`5m` is used by default. +`5m` will be used by default. #### detour diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index 3b472c4dff..4f8ca9d6a2 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -69,7 +69,7 @@ icon: material/delete-clock #### udp_timeout -UDP NAT 过期时间,以秒为单位。 +UDP NAT 过期时间。 默认使用 `5m`。 diff --git a/docs/deprecated.md b/docs/deprecated.md index 5dcec56241..b72ee11e7d 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -28,6 +28,13 @@ Destination override fields (`override_address` / `override_port`) in direct out and can be replaced by rule actions, check [Migration](../migration/#migrate-destination-override-fields-to-route-options). +#### WireGuard outbound + +WireGuard outbound is deprecated and can be replaced by endpoint, +check [Migration](../migration/#migrate-wireguard-outbound-to-endpoint). + +Old outbound will be removed in sing-box 1.13.0. + ## 1.10.0 #### TUN address fields are merged diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 6f6c839fce..220725a9af 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -27,6 +27,13 @@ direct 出站中的目标地址覆盖字段(`override_address` / `override_por 旧字段将在 sing-box 1.13.0 中被移除。 +#### WireGuard 出站 + +WireGuard 出站已废弃且可以通过端点替代, +参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。 + +旧出站将在 sing-box 1.13.0 中被移除。 + ## 1.10.0 #### Match source 规则项已重命名 diff --git a/docs/migration.md b/docs/migration.md index ea1afa2dd2..480c0d50ce 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -194,6 +194,78 @@ Destination override fields in direct outbound are deprecated and can be replace } ``` +### Migrate WireGuard outbound to endpoint + +WireGuard outbound is deprecated and can be replaced by endpoint. + +!!! info "References" + + [Endpoint](/configuration/endpoint/) / + [WireGuard Endpoint](/configuration/endpoint/wireguard/) / + [WireGuard Outbound](/configuration/outbound/wireguard/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "wireguard", + "tag": "wg-out", + + "server": "127.0.0.1", + "server_port": 10001, + "system_interface": true, + "gso": true, + "interface_name": "wg0", + "local_address": [ + "10.0.0.1/32" + ], + "private_key": "", + "peer_public_key": "", + "pre_shared_key": "", + "reserved": [0, 0, 0], + "mtu": 1408 + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "endpoints": [ + { + "type": "wireguard", + "tag": "wg-ep", + "system": true, + "name": "wg0", + "mtu": 1408, + "gso": true, + "address": [ + "10.0.0.2/32" + ], + "private_key": "", + "listen_port": 10000, + "peers": [ + { + "address": "127.0.0.1", + "port": 10001, + "public_key": "", + "pre_shared_key": "", + "allowed_ips": [ + "0.0.0.0/0" + ], + "persistent_keepalive_interval": 30, + "reserved": [0, 0, 0] + } + ] + } + ] + } + ``` + ## 1.10.0 ### TUN address fields are merged diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 73afbb0519..f03f63b095 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -104,7 +104,6 @@ icon: material/arrange-bring-forward ### 迁移旧的入站字段到规则动作 - 入站选项已被弃用,且可以被规则动作替代。 !!! info "参考" @@ -196,6 +195,78 @@ direct 出站中的目标地址覆盖字段已废弃,且可以被路由字段 } ``` +### 迁移 WireGuard 出站到端点 + +WireGuard 出站已被弃用,且可以被端点替代。 + +!!! info "参考" + + [端点](/zh/configuration/endpoint/) / + [WireGuard 端点](/zh/configuration/endpoint/wireguard/) / + [WireGuard 出站](/zh/configuration/outbound/wireguard/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "wireguard", + "tag": "wg-out", + + "server": "127.0.0.1", + "server_port": 10001, + "system_interface": true, + "gso": true, + "interface_name": "wg0", + "local_address": [ + "10.0.0.1/32" + ], + "private_key": "", + "peer_public_key": "", + "pre_shared_key": "", + "reserved": [0, 0, 0], + "mtu": 1408 + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "endpoints": [ + { + "type": "wireguard", + "tag": "wg-ep", + "system": true, + "name": "wg0", + "mtu": 1408, + "gso": true, + "address": [ + "10.0.0.2/32" + ], + "private_key": "", + "listen_port": 10000, + "peers": [ + { + "address": "127.0.0.1", + "port": 10001, + "public_key": "", + "pre_shared_key": "", + "allowed_ips": [ + "0.0.0.0/0" + ], + "persistent_keepalive_interval": 30, + "reserved": [0, 0, 0] + } + ] + } + ] + } + ``` + ## 1.10.0 ### TUN 地址字段已合并 diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index bf278f844e..6eadea6a44 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -113,6 +113,15 @@ var OptionDestinationOverrideFields = Note{ 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 Options = []Note{ OptionBadMatchSource, OptionGEOIP, @@ -121,4 +130,5 @@ var Options = []Note{ OptionSpecialOutbounds, OptionInboundOptions, OptionDestinationOverrideFields, + OptionWireGuardOutbound, } diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 02d5b884b5..159fd8f6a0 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -30,7 +30,7 @@ func parseConfig(ctx context.Context, configContent string) (option.Options, err } func CheckConfig(configContent string) error { - ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()) options, err := parseConfig(ctx, configContent) if err != nil { return err @@ -131,7 +131,7 @@ func (s *platformInterfaceStub) SendNotification(notification *platform.Notifica } func FormatConfig(configContent string) (*StringBox, error) { - options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent) + options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()), configContent) if err != nil { return nil, err } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index e40cbe0367..8dd4cb22fc 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -44,7 +44,7 @@ type BoxService struct { } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { - ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) options, err := parseConfig(ctx, configContent) diff --git a/include/registry.go b/include/registry.go index 03fb33f245..e71ffb0c82 100644 --- a/include/registry.go +++ b/include/registry.go @@ -4,6 +4,7 @@ import ( "context" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" C "github.com/sagernet/sing-box/constant" @@ -82,6 +83,14 @@ func OutboundRegistry() *outbound.Registry { return registry } +func EndpointRegistry() *endpoint.Registry { + registry := endpoint.NewRegistry() + + registerWireGuardEndpoint(registry) + + return registry +} + func registerStubForRemovedInbounds(registry *inbound.Registry) { inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") diff --git a/include/wireguard.go b/include/wireguard.go index dfc3a242a5..f2ce9e2341 100644 --- a/include/wireguard.go +++ b/include/wireguard.go @@ -3,6 +3,7 @@ package include import ( + "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/protocol/wireguard" ) @@ -10,3 +11,7 @@ import ( 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 a9e84522bb..247546e26e 100644 --- a/include/wireguard_stub.go +++ b/include/wireguard_stub.go @@ -6,6 +6,7 @@ import ( "context" "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" @@ -14,7 +15,13 @@ import ( ) func registerWireGuardOutbound(registry *outbound.Registry) { - outbound.Register[option.WireGuardOutboundOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { + 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/mkdocs.yml b/mkdocs.yml index 66e8a2e99b..4854fa4a60 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -112,6 +112,9 @@ nav: - V2Ray Transport: configuration/shared/v2ray-transport.md - UDP over TCP: configuration/shared/udp-over-tcp.md - TCP Brutal: configuration/shared/tcp-brutal.md + - Endpoint: + - configuration/endpoint/index.md + - WireGuard: configuration/endpoint/wireguard.md - Inbound: - configuration/inbound/index.md - Direct: configuration/inbound/direct.md @@ -241,6 +244,7 @@ plugins: Multiplex: 多路复用 V2Ray Transport: V2Ray 传输层 + Endpoint: 端点 Inbound: 入站 Outbound: 出站 diff --git a/option/endpoint.go b/option/endpoint.go new file mode 100644 index 0000000000..909fb89618 --- /dev/null +++ b/option/endpoint.go @@ -0,0 +1,47 @@ +package option + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/service" +) + +type EndpointOptionsRegistry interface { + CreateOptions(endpointType string) (any, bool) +} + +type _Endpoint struct { + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` +} + +type Endpoint _Endpoint + +func (h *Endpoint) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Endpoint)(h), h.Options) +} + +func (h *Endpoint) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_Endpoint)(h)) + if err != nil { + return err + } + registry := service.FromContext[EndpointOptionsRegistry](ctx) + if registry == nil { + return E.New("missing Endpoint fields registry in context") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown inbound type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Endpoint)(h), options) + if err != nil { + return err + } + h.Options = options + return nil +} diff --git a/option/inbound.go b/option/inbound.go index 2cc1598946..1cf16ff6ec 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -28,7 +28,7 @@ func (h *Inbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { } func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { - err := json.Unmarshal(content, (*_Inbound)(h)) + err := json.UnmarshalContext(ctx, content, (*_Inbound)(h)) if err != nil { return err } diff --git a/option/options.go b/option/options.go index 13a16c08b5..94c9771928 100644 --- a/option/options.go +++ b/option/options.go @@ -13,6 +13,7 @@ type _Options struct { Log *LogOptions `json:"log,omitempty"` DNS *DNSOptions `json:"dns,omitempty"` NTP *NTPOptions `json:"ntp,omitempty"` + Endpoints []Endpoint `json:"endpoints,omitempty"` Inbounds []Inbound `json:"inbounds,omitempty"` Outbounds []Outbound `json:"outbounds,omitempty"` Route *RouteOptions `json:"route,omitempty"` diff --git a/option/outbound.go b/option/outbound.go index 34ef904a80..833a2d2030 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -30,7 +30,7 @@ func (h *Outbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { } func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { - err := json.Unmarshal(content, (*_Outbound)(h)) + err := json.UnmarshalContext(ctx, content, (*_Outbound)(h)) if err != nil { return err } diff --git a/option/wireguard.go b/option/wireguard.go index ebdf159fde..62ef332ae7 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -6,14 +6,38 @@ import ( "github.com/sagernet/sing/common/json/badoption" ) -type WireGuardOutboundOptions struct { +type WireGuardEndpointOptions struct { + System bool `json:"system,omitempty"` + Name string `json:"name,omitempty"` + MTU uint32 `json:"mtu,omitempty"` + GSO bool `json:"gso,omitempty"` + Address badoption.Listable[netip.Prefix] `json:"address"` + PrivateKey string `json:"private_key"` + ListenPort uint16 `json:"listen_port,omitempty"` + Peers []WireGuardPeer `json:"peers,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Workers int `json:"workers,omitempty"` + DialerOptions +} + +type WireGuardPeer struct { + Address string `json:"address,omitempty"` + Port uint16 `json:"port,omitempty"` + PublicKey string `json:"public_key,omitempty"` + PreSharedKey string `json:"pre_shared_key,omitempty"` + AllowedIPs badoption.Listable[netip.Prefix] `json:"allowed_ips,omitempty"` + 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 []WireGuardPeer `json:"peers,omitempty"` + Peers []LegacyWireGuardPeer `json:"peers,omitempty"` ServerOptions PeerPublicKey string `json:"peer_public_key"` PreSharedKey string `json:"pre_shared_key,omitempty"` @@ -23,10 +47,10 @@ type WireGuardOutboundOptions struct { Network NetworkList `json:"network,omitempty"` } -type WireGuardPeer struct { +type LegacyWireGuardPeer struct { ServerOptions - PublicKey string `json:"public_key,omitempty"` - PreSharedKey string `json:"pre_shared_key,omitempty"` - AllowedIPs badoption.Listable[string] `json:"allowed_ips,omitempty"` - Reserved []uint8 `json:"reserved,omitempty"` + 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/block/outbound.go b/protocol/block/outbound.go index 75bc7797e0..fe1ccda760 100644 --- a/protocol/block/outbound.go +++ b/protocol/block/outbound.go @@ -26,7 +26,7 @@ type Outbound struct { func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, _ option.StubOptions) (adapter.Outbound, error) { return &Outbound{ - Adapter: outbound.NewAdapter(C.TypeBlock, []string{N.NetworkTCP, N.NetworkUDP}, tag, nil), + Adapter: outbound.NewAdapter(C.TypeBlock, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), logger: logger, }, nil } diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go index 6db60d7890..c9594f9a7c 100644 --- a/protocol/direct/inbound.go +++ b/protocol/direct/inbound.go @@ -68,7 +68,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (i *Inbound) Start() error { +func (i *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } return i.listener.Start() } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 5ae0dac647..4962ea7025 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -52,7 +52,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), logger: logger, domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), diff --git a/protocol/dns/outbound.go b/protocol/dns/outbound.go index 7ce9fde2f2..3c493f80e3 100644 --- a/protocol/dns/outbound.go +++ b/protocol/dns/outbound.go @@ -28,7 +28,7 @@ type Outbound struct { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) { return &Outbound{ - Adapter: outbound.NewAdapter(C.TypeDNS, []string{N.NetworkTCP, N.NetworkUDP}, tag, nil), + Adapter: outbound.NewAdapter(C.TypeDNS, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), router: router, logger: logger, }, nil diff --git a/protocol/group/selector.go b/protocol/group/selector.go index 08db74c09e..0bb3cd6644 100644 --- a/protocol/group/selector.go +++ b/protocol/group/selector.go @@ -38,7 +38,7 @@ type Selector struct { func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (adapter.Outbound, error) { outbound := &Selector{ - Adapter: outbound.NewAdapter(C.TypeSelector, nil, tag, options.Outbounds), + Adapter: outbound.NewAdapter(C.TypeSelector, tag, nil, options.Outbounds), ctx: ctx, outboundManager: service.FromContext[adapter.OutboundManager](ctx), logger: logger, diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index f1a84b5044..fcada7dc3b 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -49,7 +49,7 @@ type URLTest struct { func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (adapter.Outbound, error) { outbound := &URLTest{ - Adapter: outbound.NewAdapter(C.TypeURLTest, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.Outbounds), + Adapter: outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds), ctx: ctx, router: router, outboundManager: service.FromContext[adapter.OutboundManager](ctx), diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go index bb8fe8c7dd..a7a463f76b 100644 --- a/protocol/http/inbound.go +++ b/protocol/http/inbound.go @@ -61,7 +61,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { diff --git a/protocol/http/outbound.go b/protocol/http/outbound.go index 81fd024669..c58f307138 100644 --- a/protocol/http/outbound.go +++ b/protocol/http/outbound.go @@ -39,7 +39,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } return &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHTTP, []string{N.NetworkTCP}, tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHTTP, tag, []string{N.NetworkTCP}, options.DialerOptions), logger: logger, client: sHTTP.NewClient(sHTTP.Options{ Dialer: detour, diff --git a/protocol/hysteria/inbound.go b/protocol/hysteria/inbound.go index 00defa92ab..6fa03479a2 100644 --- a/protocol/hysteria/inbound.go +++ b/protocol/hysteria/inbound.go @@ -160,7 +160,10 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { diff --git a/protocol/hysteria/outbound.go b/protocol/hysteria/outbound.go index 89e6cc1089..e1d8716c45 100644 --- a/protocol/hysteria/outbound.go +++ b/protocol/hysteria/outbound.go @@ -95,7 +95,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } return &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria, networkList, tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria, tag, networkList, options.DialerOptions), logger: logger, client: client, }, nil diff --git a/protocol/hysteria2/inbound.go b/protocol/hysteria2/inbound.go index 03cd8d2d52..a3260e55ee 100644 --- a/protocol/hysteria2/inbound.go +++ b/protocol/hysteria2/inbound.go @@ -171,7 +171,10 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go index 4cabb4751c..068cc7f7fe 100644 --- a/protocol/hysteria2/outbound.go +++ b/protocol/hysteria2/outbound.go @@ -81,7 +81,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } return &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria2, networkList, tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria2, tag, networkList, options.DialerOptions), logger: logger, client: client, }, nil diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go index 4f48144086..ad7bed7de2 100644 --- a/protocol/mixed/inbound.go +++ b/protocol/mixed/inbound.go @@ -54,7 +54,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } return h.listener.Start() } diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go index 1a561aeaf2..18acd2accd 100644 --- a/protocol/naive/inbound.go +++ b/protocol/naive/inbound.go @@ -78,7 +78,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (n *Inbound) Start() error { +func (n *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } var tlsConfig *tls.STDConfig if n.tlsConfig != nil { err := n.tlsConfig.Start() diff --git a/protocol/redirect/redirect.go b/protocol/redirect/redirect.go index 23bfad3eb4..0950d102b9 100644 --- a/protocol/redirect/redirect.go +++ b/protocol/redirect/redirect.go @@ -42,7 +42,10 @@ func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextL return redirect, nil } -func (h *Redirect) Start() error { +func (h *Redirect) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } return h.listener.Start() } diff --git a/protocol/redirect/tproxy.go b/protocol/redirect/tproxy.go index 825b9f0085..78f0079fde 100644 --- a/protocol/redirect/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -61,7 +61,10 @@ func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLog return tproxy, nil } -func (t *TProxy) Start() error { +func (t *TProxy) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } err := t.listener.Start() if err != nil { return err diff --git a/protocol/shadowsocks/inbound.go b/protocol/shadowsocks/inbound.go index 84ad43fcb0..8332a93cc0 100644 --- a/protocol/shadowsocks/inbound.go +++ b/protocol/shadowsocks/inbound.go @@ -93,7 +93,10 @@ func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, err } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } return h.listener.Start() } diff --git a/protocol/shadowsocks/inbound_multi.go b/protocol/shadowsocks/inbound_multi.go index a76075efe0..ec55713430 100644 --- a/protocol/shadowsocks/inbound_multi.go +++ b/protocol/shadowsocks/inbound_multi.go @@ -101,7 +101,10 @@ func newMultiInbound(ctx context.Context, router adapter.Router, logger log.Cont return inbound, err } -func (h *MultiInbound) Start() error { +func (h *MultiInbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } return h.listener.Start() } diff --git a/protocol/shadowsocks/inbound_relay.go b/protocol/shadowsocks/inbound_relay.go index f7ec2b7703..bb20de3f58 100644 --- a/protocol/shadowsocks/inbound_relay.go +++ b/protocol/shadowsocks/inbound_relay.go @@ -86,7 +86,10 @@ func newRelayInbound(ctx context.Context, router adapter.Router, logger log.Cont return inbound, err } -func (h *RelayInbound) Start() error { +func (h *RelayInbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } return h.listener.Start() } diff --git a/protocol/shadowsocks/outbound.go b/protocol/shadowsocks/outbound.go index 8771fa8e99..7e7277ef95 100644 --- a/protocol/shadowsocks/outbound.go +++ b/protocol/shadowsocks/outbound.go @@ -49,7 +49,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, options.Network.Build(), tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, tag, options.Network.Build(), options.DialerOptions), logger: logger, dialer: outboundDialer, method: method, diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index 1be422decc..ce4238fd49 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -90,7 +90,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } return h.listener.Start() } diff --git a/protocol/shadowtls/outbound.go b/protocol/shadowtls/outbound.go index e979dba270..2b480729e5 100644 --- a/protocol/shadowtls/outbound.go +++ b/protocol/shadowtls/outbound.go @@ -29,7 +29,7 @@ type Outbound struct { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (adapter.Outbound, error) { outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowTLS, []string{N.NetworkTCP}, tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowTLS, tag, []string{N.NetworkTCP}, options.DialerOptions), } if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go index fddacb21fb..115db30308 100644 --- a/protocol/socks/inbound.go +++ b/protocol/socks/inbound.go @@ -50,7 +50,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } return h.listener.Start() } diff --git a/protocol/socks/outbound.go b/protocol/socks/outbound.go index 70a5a5eda0..0632f0825f 100644 --- a/protocol/socks/outbound.go +++ b/protocol/socks/outbound.go @@ -50,7 +50,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, options.Network.Build(), tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, tag, options.Network.Build(), options.DialerOptions), router: router, logger: logger, client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), diff --git a/protocol/ssh/outbound.go b/protocol/ssh/outbound.go index 1dfc1f6d68..eb9970b5fb 100644 --- a/protocol/ssh/outbound.go +++ b/protocol/ssh/outbound.go @@ -54,7 +54,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSSH, []string{N.NetworkTCP}, tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSSH, tag, []string{N.NetworkTCP}, options.DialerOptions), ctx: ctx, logger: logger, dialer: outboundDialer, diff --git a/protocol/tor/outbound.go b/protocol/tor/outbound.go index 3d2170115d..58824b53fb 100644 --- a/protocol/tor/outbound.go +++ b/protocol/tor/outbound.go @@ -80,7 +80,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } return &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, []string{N.NetworkTCP}, tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, tag, []string{N.NetworkTCP}, options.DialerOptions), ctx: ctx, logger: logger, proxy: NewProxyListener(ctx, logger, outboundDialer), diff --git a/protocol/trojan/inbound.go b/protocol/trojan/inbound.go index 2dc1bb9421..107667c580 100644 --- a/protocol/trojan/inbound.go +++ b/protocol/trojan/inbound.go @@ -110,7 +110,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index 68b0069043..82889bc188 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -43,7 +43,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTrojan, options.Network.Build(), tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTrojan, tag, options.Network.Build(), options.DialerOptions), logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), diff --git a/protocol/tuic/inbound.go b/protocol/tuic/inbound.go index 496079c194..a21b72ea5b 100644 --- a/protocol/tuic/inbound.go +++ b/protocol/tuic/inbound.go @@ -142,7 +142,10 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { diff --git a/protocol/tuic/outbound.go b/protocol/tuic/outbound.go index 177f21fc95..49b01f96e9 100644 --- a/protocol/tuic/outbound.go +++ b/protocol/tuic/outbound.go @@ -80,7 +80,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } return &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTUIC, options.Network.Build(), tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTUIC, tag, options.Network.Build(), options.DialerOptions), logger: logger, client: client, udpStream: options.UDPOverStream, diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 302afb578d..b3ada90550 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -300,104 +300,104 @@ func (t *Inbound) Tag() string { return t.tag } -func (t *Inbound) Start() error { - if C.IsAndroid && t.platformInterface == nil { - t.tunOptions.BuildAndroidRules(t.networkManager.PackageManager()) - } - if t.tunOptions.Name == "" { - t.tunOptions.Name = tun.CalculateInterfaceName("") - } - var ( - tunInterface tun.Tun - err error - ) - monitor := taskmonitor.New(t.logger, C.StartTimeout) - monitor.Start("open tun interface") - if t.platformInterface != nil { - tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions) - } else { - tunInterface, err = tun.New(t.tunOptions) - } - monitor.Finish() - if err != nil { - return E.Cause(err, "configure tun interface") - } - t.logger.Trace("creating stack") - t.tunIf = tunInterface - var ( - forwarderBindInterface bool - includeAllNetworks bool - ) - if t.platformInterface != nil { - forwarderBindInterface = true - includeAllNetworks = t.platformInterface.IncludeAllNetworks() - } - tunStack, err := tun.NewStack(t.stack, tun.StackOptions{ - Context: t.ctx, - Tun: tunInterface, - TunOptions: t.tunOptions, - UDPTimeout: t.udpTimeout, - Handler: t, - Logger: t.logger, - ForwarderBindInterface: forwarderBindInterface, - InterfaceFinder: t.networkManager.InterfaceFinder(), - IncludeAllNetworks: includeAllNetworks, - }) - if err != nil { - return err - } - t.tunStack = tunStack - t.logger.Info("started at ", t.tunOptions.Name) - return nil -} - -func (t *Inbound) PostStart() error { - monitor := taskmonitor.New(t.logger, C.StartTimeout) - monitor.Start("starting tun stack") - err := t.tunStack.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "starting tun stack") - } - monitor.Start("starting tun interface") - err = t.tunIf.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "starting TUN interface") - } - if t.autoRedirect != nil { - t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) - for _, routeRuleSet := range t.routeRuleSet { - ipSets := routeRuleSet.ExtractIPSet() - if len(ipSets) == 0 { - t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name()) - } - t.routeAddressSet = append(t.routeAddressSet, ipSets...) +func (t *Inbound) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + if C.IsAndroid && t.platformInterface == nil { + t.tunOptions.BuildAndroidRules(t.networkManager.PackageManager()) } - t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) - for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { - ipSets := routeExcludeRuleSet.ExtractIPSet() - if len(ipSets) == 0 { - t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name()) - } - t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...) + if t.tunOptions.Name == "" { + t.tunOptions.Name = tun.CalculateInterfaceName("") + } + var ( + tunInterface tun.Tun + err error + ) + monitor := taskmonitor.New(t.logger, C.StartTimeout) + monitor.Start("open tun interface") + if t.platformInterface != nil { + tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions) + } else { + tunInterface, err = tun.New(t.tunOptions) } - monitor.Start("initialize auto-redirect") - err := t.autoRedirect.Start() monitor.Finish() if err != nil { - return E.Cause(err, "auto-redirect") + return E.Cause(err, "configure tun interface") } - for _, routeRuleSet := range t.routeRuleSet { - t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) - routeRuleSet.DecRef() + t.logger.Trace("creating stack") + t.tunIf = tunInterface + var ( + forwarderBindInterface bool + includeAllNetworks bool + ) + if t.platformInterface != nil { + forwarderBindInterface = true + includeAllNetworks = t.platformInterface.IncludeAllNetworks() } - for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { - t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) - routeExcludeRuleSet.DecRef() + tunStack, err := tun.NewStack(t.stack, tun.StackOptions{ + Context: t.ctx, + Tun: tunInterface, + TunOptions: t.tunOptions, + UDPTimeout: t.udpTimeout, + Handler: t, + Logger: t.logger, + ForwarderBindInterface: forwarderBindInterface, + InterfaceFinder: t.networkManager.InterfaceFinder(), + IncludeAllNetworks: includeAllNetworks, + }) + if err != nil { + return err + } + t.tunStack = tunStack + t.logger.Info("started at ", t.tunOptions.Name) + case adapter.StartStatePostStart: + monitor := taskmonitor.New(t.logger, C.StartTimeout) + monitor.Start("starting tun stack") + err := t.tunStack.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "starting tun stack") + } + monitor.Start("starting tun interface") + err = t.tunIf.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "starting TUN interface") + } + if t.autoRedirect != nil { + t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) + for _, routeRuleSet := range t.routeRuleSet { + ipSets := routeRuleSet.ExtractIPSet() + if len(ipSets) == 0 { + t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name()) + } + t.routeAddressSet = append(t.routeAddressSet, ipSets...) + } + t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) + for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { + ipSets := routeExcludeRuleSet.ExtractIPSet() + if len(ipSets) == 0 { + t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name()) + } + t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...) + } + monitor.Start("initialize auto-redirect") + err := t.autoRedirect.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "auto-redirect") + } + for _, routeRuleSet := range t.routeRuleSet { + t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + routeRuleSet.DecRef() + } + for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { + t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + routeExcludeRuleSet.DecRef() + } + t.routeAddressSet = nil + t.routeExcludeAddressSet = nil } - t.routeAddressSet = nil - t.routeExcludeAddressSet = nil } return nil } diff --git a/protocol/vless/inbound.go b/protocol/vless/inbound.go index f5dfeabb6b..b0aa959e06 100644 --- a/protocol/vless/inbound.go +++ b/protocol/vless/inbound.go @@ -89,7 +89,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index de655230e0..1d832a654d 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -46,7 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVLESS, options.Network.Build(), tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVLESS, tag, options.Network.Build(), options.DialerOptions), logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), diff --git a/protocol/vmess/inbound.go b/protocol/vmess/inbound.go index 88ad222764..9f1009a301 100644 --- a/protocol/vmess/inbound.go +++ b/protocol/vmess/inbound.go @@ -99,7 +99,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } err := h.service.Start() if err != nil { return err diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index 1e84639f47..d41b30d964 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -46,7 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVMess, options.Network.Build(), tag, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVMess, tag, options.Network.Build(), options.DialerOptions), logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go new file mode 100644 index 0000000000..5465099c02 --- /dev/null +++ b/protocol/wireguard/endpoint.go @@ -0,0 +1,211 @@ +package wireguard + +import ( + "context" + "net" + "net/netip" + "time" + + "github.com/sagernet/sing-box/adapter" + "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/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/wireguard" + "github.com/sagernet/sing-dns" + "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" + "github.com/sagernet/sing/service" +) + +func RegisterEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint) +} + +var ( + _ adapter.Endpoint = (*Endpoint)(nil) + _ adapter.InterfaceUpdateListener = (*Endpoint)(nil) +) + +type Endpoint struct { + endpoint.Adapter + ctx context.Context + router adapter.Router + logger logger.ContextLogger + localAddresses []netip.Prefix + endpoint *wireguard.Endpoint +} + +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), + ctx: ctx, + router: router, + logger: logger, + localAddresses: options.Address, + } + if options.Detour == "" { + options.IsWireGuardListener = true + } else if options.GSO { + return nil, E.New("gso is conflict with detour") + } + outboundDialer, err := dialer.New(ctx, options.DialerOptions) + if err != nil { + return nil, err + } + wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ + Context: ctx, + Logger: logger, + System: options.System, + Handler: ep, + UDPTimeout: time.Duration(options.UDPTimeout), + Dialer: outboundDialer, + CreateDialer: func(interfaceName string) N.Dialer { + return common.Must1(dialer.NewDefault(service.FromContext[adapter.NetworkManager](ctx), option.DialerOptions{ + BindInterface: interfaceName, + })) + }, + Name: options.Name, + MTU: options.MTU, + GSO: options.GSO, + Address: options.Address, + PrivateKey: options.PrivateKey, + ListenPort: options.ListenPort, + ResolvePeer: func(domain string) (netip.Addr, error) { + endpointAddresses, lookupErr := router.Lookup(ctx, domain, dns.DomainStrategy(options.DomainStrategy)) + if lookupErr != nil { + return netip.Addr{}, lookupErr + } + return endpointAddresses[0], nil + }, + Peers: common.Map(options.Peers, func(it option.WireGuardPeer) wireguard.PeerOptions { + return wireguard.PeerOptions{ + Endpoint: M.ParseSocksaddrHostPort(it.Address, it.Port), + PublicKey: it.PublicKey, + PreSharedKey: it.PreSharedKey, + AllowedIPs: it.AllowedIPs, + PersistentKeepaliveInterval: it.PersistentKeepaliveInterval, + Reserved: it.Reserved, + } + }), + Workers: options.Workers, + }) + if err != nil { + return nil, err + } + ep.endpoint = wgEndpoint + return ep, nil +} + +func (w *Endpoint) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + return w.endpoint.Start(false) + case adapter.StartStatePostStart: + return w.endpoint.Start(true) + } + return nil +} + +func (w *Endpoint) Close() error { + return w.endpoint.Close() +} + +func (w *Endpoint) InterfaceUpdated() { + w.endpoint.BindUpdate() + return +} + +func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { + return w.router.PreMatch(adapter.InboundContext{ + Inbound: w.Tag(), + InboundType: w.Type(), + Network: network, + Source: source, + Destination: destination, + }) +} + +func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = w.Tag() + metadata.InboundType = w.Type() + metadata.Source = source + for _, localPrefix := range w.localAddresses { + if localPrefix.Contains(destination.Addr) { + metadata.OriginDestination = destination + if destination.Addr.Is4() { + destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + } else { + destination.Addr = netip.IPv6Loopback() + } + break + } + } + metadata.Destination = destination + w.logger.InfoContext(ctx, "inbound connection from ", source) + w.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + w.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (w *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = w.Tag() + metadata.InboundType = w.Type() + metadata.Source = source + metadata.Destination = destination + for _, localPrefix := range w.localAddresses { + if localPrefix.Contains(destination.Addr) { + metadata.OriginDestination = destination + if destination.Addr.Is4() { + metadata.Destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + } else { + metadata.Destination.Addr = netip.IPv6Loopback() + } + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) + } + } + w.logger.InfoContext(ctx, "inbound packet connection from ", source) + w.logger.InfoContext(ctx, "inbound packet connection to ", destination) + w.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (w *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch network { + case N.NetworkTCP: + w.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + w.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + if destination.IsFqdn() { + destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) + if err != nil { + return nil, err + } + return N.DialSerial(ctx, w.endpoint, network, destination, destinationAddresses) + } else if !destination.Addr.IsValid() { + return nil, E.New("invalid destination: ", destination) + } + return w.endpoint.DialContext(ctx, network, destination) +} + +func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + w.logger.InfoContext(ctx, "outbound packet connection to ", destination) + if destination.IsFqdn() { + destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) + if err != nil { + return nil, err + } + packetConn, _, err := N.ListenSerial(ctx, w.endpoint, destination, destinationAddresses) + if err != nil { + return nil, err + } + return packetConn, err + } + return w.endpoint.ListenPacket(ctx, destination) +} diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 70d74140be..0831c2d7cf 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -2,238 +2,163 @@ package wireguard import ( "context" - "encoding/base64" - "encoding/hex" - "fmt" "net" "net/netip" - "strings" "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" - "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-dns" "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/x/list" "github.com/sagernet/sing/service" - "github.com/sagernet/sing/service/pause" - "github.com/sagernet/wireguard-go/conn" - "github.com/sagernet/wireguard-go/device" ) func RegisterOutbound(registry *outbound.Registry) { - outbound.Register[option.WireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) + outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) } -var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) +var ( + _ adapter.Endpoint = (*Endpoint)(nil) + _ adapter.InterfaceUpdateListener = (*Endpoint)(nil) +) type Outbound struct { outbound.Adapter - ctx context.Context - router adapter.Router - logger logger.ContextLogger - workers int - peers []wireguard.PeerConfig - useStdNetBind bool - listener N.Dialer - ipcConf string - - pauseManager pause.Manager - pauseCallback *list.Element[pause.Callback] - bind conn.Bind - device *device.Device - tunDevice wireguard.Device + ctx context.Context + router adapter.Router + logger logger.ContextLogger + localAddresses []netip.Prefix + endpoint *wireguard.Endpoint } -func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.LegacyWireGuardOutboundOptions) (adapter.Outbound, error) { + deprecated.Report(ctx, deprecated.OptionWireGuardOutbound) outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, options.Network.Build(), tag, options.DialerOptions), - ctx: ctx, - router: router, - logger: logger, - workers: options.Workers, - pauseManager: service.FromContext[pause.Manager](ctx), - } - peers, err := wireguard.ParsePeers(options) - if err != nil { - return nil, err + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + ctx: ctx, + router: router, + logger: logger, + localAddresses: options.LocalAddress, } - outbound.peers = peers - if len(options.LocalAddress) == 0 { - return nil, E.New("missing local address") - } - if options.GSO { - if options.GSO && options.Detour != "" { - return nil, E.New("gso is conflict with detour") - } + if options.Detour == "" { options.IsWireGuardListener = true - outbound.useStdNetBind = true + } else if options.GSO { + return nil, E.New("gso is conflict with detour") } - listener, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } - outbound.listener = listener - var privateKey string - { - bytes, err := base64.StdEncoding.DecodeString(options.PrivateKey) - if err != nil { - return nil, E.Cause(err, "decode private key") - } - privateKey = hex.EncodeToString(bytes) - } - outbound.ipcConf = "private_key=" + privateKey - mtu := options.MTU - if mtu == 0 { - mtu = 1408 - } - var wireTunDevice wireguard.Device - if !options.SystemInterface && tun.WithGVisor { - wireTunDevice, err = wireguard.NewStackDevice(options.LocalAddress, mtu) - } else { - wireTunDevice, err = wireguard.NewSystemDevice(service.FromContext[adapter.NetworkManager](ctx), options.InterfaceName, options.LocalAddress, mtu, options.GSO) - } - if err != nil { - return nil, E.Cause(err, "create WireGuard device") - } - outbound.tunDevice = wireTunDevice - return outbound, nil -} - -func (w *Outbound) Start() error { - if common.Any(w.peers, func(peer wireguard.PeerConfig) bool { - return !peer.Endpoint.IsValid() - }) { - // wait for all outbounds to be started and continue in PortStart - return nil - } - return w.start() -} - -func (w *Outbound) PostStart() error { - if common.All(w.peers, func(peer wireguard.PeerConfig) bool { - return peer.Endpoint.IsValid() - }) { - return nil - } - return w.start() -} - -func (w *Outbound) start() error { - err := wireguard.ResolvePeers(w.ctx, w.router, w.peers) - if err != nil { - return err - } - var bind conn.Bind - if w.useStdNetBind { - bind = conn.NewStdNetBind(w.listener.(dialer.WireGuardListener)) - } else { - var ( - isConnect bool - connectAddr netip.AddrPort - reserved [3]uint8 - ) - peerLen := len(w.peers) - if peerLen == 1 { - isConnect = true - connectAddr = w.peers[0].Endpoint - reserved = w.peers[0].Reserved + 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, } - bind = wireguard.NewClientBind(w.ctx, w.logger, w.listener, isConnect, connectAddr, reserved) - } - if w.useStdNetBind || len(w.peers) > 1 { - for _, peer := range w.peers { - if peer.Reserved != [3]uint8{} { - bind.SetReservedForEndpoint(peer.Endpoint, peer.Reserved) - } - } - } - err = w.tunDevice.Start() - if err != nil { - return err - } - wgDevice := device.NewDevice(w.ctx, w.tunDevice, bind, &device.Logger{ - Verbosef: func(format string, args ...interface{}) { - w.logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) + }) + 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(service.FromContext[adapter.NetworkManager](ctx), option.DialerOptions{ + BindInterface: interfaceName, + })) }, - Errorf: func(format string, args ...interface{}) { - w.logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) + Name: options.InterfaceName, + MTU: options.MTU, + GSO: options.GSO, + Address: options.LocalAddress, + PrivateKey: options.PrivateKey, + ResolvePeer: func(domain string) (netip.Addr, error) { + endpointAddresses, lookupErr := router.Lookup(ctx, domain, dns.DomainStrategy(options.DomainStrategy)) + if lookupErr != nil { + return netip.Addr{}, lookupErr + } + return endpointAddresses[0], nil }, - }, w.workers) - ipcConf := w.ipcConf - for _, peer := range w.peers { - ipcConf += peer.GenerateIpcLines() - } - err = wgDevice.IpcSet(ipcConf) + Peers: peers, + Workers: options.Workers, + }) if err != nil { - return E.Cause(err, "setup wireguard: \n", ipcConf) + return nil, err } - w.device = wgDevice - w.pauseCallback = w.pauseManager.RegisterCallback(w.onPauseUpdated) - return nil + outbound.endpoint = wgEndpoint + return outbound, nil } -func (w *Outbound) Close() error { - if w.device != nil { - w.device.Close() - } - if w.pauseCallback != nil { - w.pauseManager.UnregisterCallback(w.pauseCallback) +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 (w *Outbound) InterfaceUpdated() { - w.device.BindUpdate() - return +func (o *Outbound) Close() error { + return o.endpoint.Close() } -func (w *Outbound) onPauseUpdated(event int) { - switch event { - case pause.EventDevicePaused: - w.device.Down() - case pause.EventDeviceWake: - w.device.Up() - } +func (o *Outbound) InterfaceUpdated() { + o.endpoint.BindUpdate() + return } -func (w *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch network { case N.NetworkTCP: - w.logger.InfoContext(ctx, "outbound connection to ", destination) + o.logger.InfoContext(ctx, "outbound connection to ", destination) case N.NetworkUDP: - w.logger.InfoContext(ctx, "outbound packet connection to ", destination) + o.logger.InfoContext(ctx, "outbound packet connection to ", destination) } if destination.IsFqdn() { - destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) + destinationAddresses, err := o.router.LookupDefault(ctx, destination.Fqdn) if err != nil { return nil, err } - return N.DialSerial(ctx, w.tunDevice, network, destination, destinationAddresses) + return N.DialSerial(ctx, o.endpoint, network, destination, destinationAddresses) + } else if !destination.Addr.IsValid() { + return nil, E.New("invalid destination: ", destination) } - return w.tunDevice.DialContext(ctx, network, destination) + return o.endpoint.DialContext(ctx, network, destination) } -func (w *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - w.logger.InfoContext(ctx, "outbound packet connection to ", 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 := w.router.LookupDefault(ctx, destination.Fqdn) + destinationAddresses, err := o.router.LookupDefault(ctx, destination.Fqdn) if err != nil { return nil, err } - packetConn, _, err := N.ListenSerial(ctx, w.tunDevice, destination, destinationAddresses) + packetConn, _, err := N.ListenSerial(ctx, o.endpoint, destination, destinationAddresses) if err != nil { return nil, err } return packetConn, err } - return w.tunDevice.ListenPacket(ctx, destination) + return o.endpoint.ListenPacket(ctx, destination) } diff --git a/route/network.go b/route/network.go index 510686e521..7cb3df8d1c 100644 --- a/route/network.go +++ b/route/network.go @@ -41,17 +41,17 @@ type NetworkManager struct { autoDetectInterface bool defaultOptions adapter.NetworkOptions autoRedirectOutputMark uint32 - - networkMonitor tun.NetworkUpdateMonitor - interfaceMonitor tun.DefaultInterfaceMonitor - packageManager tun.PackageManager - powerListener winpowrprof.EventListener - pauseManager pause.Manager - platformInterface platform.Interface - inboundManager adapter.InboundManager - outboundManager adapter.OutboundManager - wifiState adapter.WIFIState - started bool + networkMonitor tun.NetworkUpdateMonitor + interfaceMonitor tun.DefaultInterfaceMonitor + packageManager tun.PackageManager + powerListener winpowrprof.EventListener + pauseManager pause.Manager + platformInterface platform.Interface + endpoint adapter.EndpointManager + inbound adapter.InboundManager + outbound adapter.OutboundManager + wifiState adapter.WIFIState + started bool } func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { @@ -69,7 +69,9 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp }, pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[platform.Interface](ctx), - outboundManager: service.FromContext[adapter.OutboundManager](ctx), + endpoint: service.FromContext[adapter.EndpointManager](ctx), + inbound: service.FromContext[adapter.InboundManager](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), } if C.NetworkStrategy(routeOptions.DefaultNetworkStrategy) != C.NetworkStrategyDefault { if routeOptions.DefaultInterface != "" { @@ -355,14 +357,21 @@ func (r *NetworkManager) WIFIState() adapter.WIFIState { func (r *NetworkManager) ResetNetwork() { conntrack.Close() - for _, inbound := range r.inboundManager.Inbounds() { + for _, endpoint := range r.endpoint.Endpoints() { + listener, isListener := endpoint.(adapter.InterfaceUpdateListener) + if isListener { + listener.InterfaceUpdated() + } + } + + for _, inbound := range r.inbound.Inbounds() { listener, isListener := inbound.(adapter.InterfaceUpdateListener) if isListener { listener.InterfaceUpdated() } } - for _, outbound := range r.outboundManager.Outbounds() { + for _, outbound := range r.outbound.Outbounds() { listener, isListener := outbound.(adapter.InterfaceUpdateListener) if isListener { listener.InterfaceUpdated() diff --git a/route/route_dns.go b/route/route_dns.go index c11c07fe1b..696e167668 100644 --- a/route/route_dns.go +++ b/route/route_dns.go @@ -11,7 +11,7 @@ import ( C "github.com/sagernet/sing-box/constant" R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" - tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/cache" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go index 20e7c0790c..e74e909d24 100644 --- a/transport/wireguard/client_bind.go +++ b/transport/wireguard/client_bind.go @@ -128,7 +128,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) select { case <-c.done: default: - c.logger.Error(context.Background(), E.Cause(err, "read packet")) + c.logger.Error(E.Cause(err, "read packet")) err = nil } return @@ -138,7 +138,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) b := packets[0] common.ClearArray(b[1:4]) } - eps[0] = Endpoint(M.AddrPortFromNet(addr)) + eps[0] = remoteEndpoint(M.AddrPortFromNet(addr)) count = 1 return } @@ -169,7 +169,7 @@ func (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint) error { time.Sleep(time.Second) return err } - destination := netip.AddrPort(ep.(Endpoint)) + destination := netip.AddrPort(ep.(remoteEndpoint)) for _, b := range bufs { if len(b) > 3 { reserved, loaded := c.reservedForEndpoint[destination] @@ -192,7 +192,7 @@ func (c *ClientBind) ParseEndpoint(s string) (conn.Endpoint, error) { if err != nil { return nil, err } - return Endpoint(ap), nil + return remoteEndpoint(ap), nil } func (c *ClientBind) BatchSize() int { @@ -229,3 +229,31 @@ func (w *wireConn) Close() error { close(w.done) return nil } + +var _ conn.Endpoint = (*remoteEndpoint)(nil) + +type remoteEndpoint netip.AddrPort + +func (e remoteEndpoint) ClearSrc() { +} + +func (e remoteEndpoint) SrcToString() string { + return "" +} + +func (e remoteEndpoint) DstToString() string { + return (netip.AddrPort)(e).String() +} + +func (e remoteEndpoint) DstToBytes() []byte { + b, _ := (netip.AddrPort)(e).MarshalBinary() + return b +} + +func (e remoteEndpoint) DstIP() netip.Addr { + return (netip.AddrPort)(e).Addr() +} + +func (e remoteEndpoint) SrcIP() netip.Addr { + return netip.Addr{} +} diff --git a/transport/wireguard/device.go b/transport/wireguard/device.go index 14e04bf56c..d5d3b78151 100644 --- a/transport/wireguard/device.go +++ b/transport/wireguard/device.go @@ -1,13 +1,44 @@ package wireguard import ( + "context" + "net/netip" + "time" + + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/wireguard-go/tun" + "github.com/sagernet/wireguard-go/device" + wgTun "github.com/sagernet/wireguard-go/tun" ) type Device interface { - tun.Device + wgTun.Device N.Dialer Start() error - // NewEndpoint() (stack.LinkEndpoint, error) + SetDevice(device *device.Device) +} + +type DeviceOptions struct { + Context context.Context + Logger logger.ContextLogger + System bool + Handler tun.Handler + UDPTimeout time.Duration + CreateDialer func(interfaceName string) N.Dialer + Name string + MTU uint32 + GSO bool + Address []netip.Prefix + AllowedAddress []netip.Prefix +} + +func NewDevice(options DeviceOptions) (Device, error) { + if !options.System { + return newStackDevice(options) + } else if options.Handler == nil { + return newSystemDevice(options) + } else { + return newSystemStackDevice(options) + } } diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index 61286e6a9a..f9440f02fa 100644 --- a/transport/wireguard/device_stack.go +++ b/transport/wireguard/device_stack.go @@ -5,7 +5,6 @@ package wireguard import ( "context" "net" - "net/netip" "os" "github.com/sagernet/gvisor/pkg/buffer" @@ -15,52 +14,41 @@ 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-tun" - "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" + "github.com/sagernet/wireguard-go/device" wgTun "github.com/sagernet/wireguard-go/tun" ) -var _ Device = (*StackDevice)(nil) - -const defaultNIC tcpip.NICID = 1 - -type StackDevice struct { - stack *stack.Stack - mtu uint32 - events chan wgTun.Event - outbound chan *stack.PacketBuffer - packetOutbound chan *buf.Buffer - done chan struct{} - dispatcher stack.NetworkDispatcher - addr4 tcpip.Address - addr6 tcpip.Address -} - -func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, error) { - ipStack := stack.New(stack.Options{ - NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, - TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6}, - HandleLocal: true, - }) - tunDevice := &StackDevice{ - stack: ipStack, - mtu: mtu, - events: make(chan wgTun.Event, 1), - outbound: make(chan *stack.PacketBuffer, 256), - packetOutbound: make(chan *buf.Buffer, 256), - done: make(chan struct{}), +var _ Device = (*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 +} + +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{}), } - err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice)) + ipStack, err := tun.NewGVisorStack((*wireEndpoint)(tunDevice)) if err != nil { - return nil, E.New(err.String()) + return nil, err } - for _, prefix := range localAddresses { + for _, prefix := range options.Address { addr := tun.AddressFromAddr(prefix.Addr()) protoAddr := tcpip.ProtocolAddress{ AddressWithPrefix: tcpip.AddressWithPrefix{ @@ -75,32 +63,27 @@ func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, er tunDevice.addr6 = addr protoAddr.Protocol = ipv6.ProtocolNumber } - err = ipStack.AddProtocolAddress(defaultNIC, protoAddr, stack.AddressProperties{}) - if err != nil { - return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String()) + gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{}) + if gErr != nil { + return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String()) } } - sOpt := tcpip.TCPSACKEnabled(true) - ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt) - cOpt := tcpip.CongestionControlOption("cubic") - ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt) - ipStack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: defaultNIC}) - ipStack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: defaultNIC}) + tunDevice.stack = ipStack + 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) + } return tunDevice, nil } -func (w *StackDevice) NewEndpoint() (stack.LinkEndpoint, error) { - return (*wireEndpoint)(w), nil -} - -func (w *StackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (w *stackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { addr := tcpip.FullAddress{ - NIC: defaultNIC, + NIC: tun.DefaultNIC, Port: destination.Port, Addr: tun.AddressFromAddr(destination.Addr), } bind := tcpip.FullAddress{ - NIC: defaultNIC, + NIC: tun.DefaultNIC, } var networkProtocol tcpip.NetworkProtocolNumber if destination.IsIPv4() { @@ -128,9 +111,9 @@ func (w *StackDevice) DialContext(ctx context.Context, network string, destinati } } -func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { bind := tcpip.FullAddress{ - NIC: defaultNIC, + NIC: tun.DefaultNIC, } var networkProtocol tcpip.NetworkProtocolNumber if destination.IsIPv4() { @@ -147,24 +130,19 @@ func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) return udpConn, nil } -func (w *StackDevice) Inet4Address() netip.Addr { - return tun.AddrFromAddress(w.addr4) -} - -func (w *StackDevice) Inet6Address() netip.Addr { - return tun.AddrFromAddress(w.addr6) +func (w *stackDevice) SetDevice(device *device.Device) { } -func (w *StackDevice) Start() error { +func (w *stackDevice) Start() error { w.events <- wgTun.EventUp return nil } -func (w *StackDevice) File() *os.File { +func (w *stackDevice) File() *os.File { return nil } -func (w *StackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { +func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { select { case packetBuffer, ok := <-w.outbound: if !ok { @@ -180,17 +158,12 @@ func (w *StackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, e sizes[0] = n count = 1 return - case packet := <-w.packetOutbound: - defer packet.Release() - sizes[0] = copy(bufs[0][offset:], packet.Bytes()) - count = 1 - return case <-w.done: return 0, os.ErrClosed } } -func (w *StackDevice) Write(bufs [][]byte, offset int) (count int, err error) { +func (w *stackDevice) Write(bufs [][]byte, offset int) (count int, err error) { for _, b := range bufs { b = b[offset:] if len(b) == 0 { @@ -213,23 +186,23 @@ func (w *StackDevice) Write(bufs [][]byte, offset int) (count int, err error) { return } -func (w *StackDevice) Flush() error { +func (w *stackDevice) Flush() error { return nil } -func (w *StackDevice) MTU() (int, error) { +func (w *stackDevice) MTU() (int, error) { return int(w.mtu), nil } -func (w *StackDevice) Name() (string, error) { +func (w *stackDevice) Name() (string, error) { return "sing-box", nil } -func (w *StackDevice) Events() <-chan wgTun.Event { +func (w *stackDevice) Events() <-chan wgTun.Event { return w.events } -func (w *StackDevice) Close() error { +func (w *stackDevice) Close() error { close(w.done) close(w.events) w.stack.Close() @@ -240,13 +213,13 @@ func (w *StackDevice) Close() error { return nil } -func (w *StackDevice) BatchSize() int { +func (w *stackDevice) BatchSize() int { return 1 } var _ stack.LinkEndpoint = (*wireEndpoint)(nil) -type wireEndpoint StackDevice +type wireEndpoint stackDevice func (ep *wireEndpoint) MTU() uint32 { return ep.mtu diff --git a/transport/wireguard/gonet.go b/transport/wireguard/device_stack_gonet.go similarity index 100% rename from transport/wireguard/gonet.go rename to transport/wireguard/device_stack_gonet.go diff --git a/transport/wireguard/device_stack_stub.go b/transport/wireguard/device_stack_stub.go index b383ab3825..ea413559e6 100644 --- a/transport/wireguard/device_stack_stub.go +++ b/transport/wireguard/device_stack_stub.go @@ -2,12 +2,12 @@ package wireguard -import ( - "net/netip" +import "github.com/sagernet/sing-tun" - "github.com/sagernet/sing-tun" -) +func newStackDevice(options DeviceOptions) (Device, error) { + return nil, tun.ErrGVisorNotIncluded +} -func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (Device, error) { +func newSystemStackDevice(options DeviceOptions) (Device, error) { return nil, tun.ErrGVisorNotIncluded } diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index 8a54a75ef9..667443bab7 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -6,96 +6,89 @@ import ( "net" "net/netip" "os" + "runtime" "sync" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + "github.com/sagernet/wireguard-go/device" wgTun "github.com/sagernet/wireguard-go/tun" ) -var _ Device = (*SystemDevice)(nil) - -type SystemDevice struct { - dialer N.Dialer - device tun.Tun - batchDevice tun.LinuxTUN - name string - mtu uint32 - inet4Addresses []netip.Prefix - inet6Addresses []netip.Prefix - gso bool - events chan wgTun.Event - closeOnce sync.Once +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 } -func NewSystemDevice(networkManager adapter.NetworkManager, interfaceName string, localPrefixes []netip.Prefix, mtu uint32, gso bool) (*SystemDevice, error) { - var inet4Addresses []netip.Prefix - var inet6Addresses []netip.Prefix - for _, prefixes := range localPrefixes { - if prefixes.Addr().Is4() { - inet4Addresses = append(inet4Addresses, prefixes) - } else { - inet6Addresses = append(inet6Addresses, prefixes) - } - } - if interfaceName == "" { - interfaceName = tun.CalculateInterfaceName("wg") +func newSystemDevice(options DeviceOptions) (*systemDevice, error) { + if options.Name == "" { + options.Name = tun.CalculateInterfaceName("wg") } - - return &SystemDevice{ - dialer: common.Must1(dialer.NewDefault(networkManager, option.DialerOptions{ - BindInterface: interfaceName, - })), - name: interfaceName, - mtu: mtu, - inet4Addresses: inet4Addresses, - inet6Addresses: inet6Addresses, - gso: gso, - events: make(chan wgTun.Event, 1), + return &systemDevice{ + options: options, + dialer: options.CreateDialer(options.Name), + events: make(chan wgTun.Event, 1), }, nil } -func (w *SystemDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (w *systemDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { return w.dialer.DialContext(ctx, network, destination) } -func (w *SystemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (w *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return w.dialer.ListenPacket(ctx, destination) } -func (w *SystemDevice) Inet4Address() netip.Addr { - if len(w.inet4Addresses) == 0 { - return netip.Addr{} +func (w *systemDevice) SetDevice(device *device.Device) { +} + +func (w *systemDevice) Start() error { + networkManager := service.FromContext[adapter.NetworkManager](w.options.Context) + tunOptions := tun.Options{ + Name: w.options.Name, + Inet4Address: common.Filter(w.options.Address, func(it netip.Prefix) bool { + return it.Addr().Is4() + }), + Inet6Address: common.Filter(w.options.Address, func(it netip.Prefix) bool { + return it.Addr().Is6() + }), + MTU: w.options.MTU, + GSO: w.options.GSO, + InterfaceScope: true, + Inet4RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { + return it.Addr().Is4() + }), + Inet6RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { return it.Addr().Is6() }), + InterfaceMonitor: networkManager.InterfaceMonitor(), + InterfaceFinder: networkManager.InterfaceFinder(), + Logger: w.options.Logger, } - return w.inet4Addresses[0].Addr() -} - -func (w *SystemDevice) Inet6Address() netip.Addr { - if len(w.inet6Addresses) == 0 { - return netip.Addr{} + // works with Linux, macOS with IFSCOPE routes, not tested on Windows + if runtime.GOOS == "darwin" { + tunOptions.AutoRoute = true } - return w.inet6Addresses[0].Addr() -} - -func (w *SystemDevice) Start() error { - tunInterface, err := tun.New(tun.Options{ - Name: w.name, - Inet4Address: w.inet4Addresses, - Inet6Address: w.inet6Addresses, - MTU: w.mtu, - GSO: w.gso, - }) + tunInterface, err := tun.New(tunOptions) + if err != nil { + return err + } + err = tunInterface.Start() if err != nil { return err } + w.options.Logger.Info("started at ", w.options.Name) w.device = tunInterface - if w.gso { + if w.options.GSO { batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN) if !isBatchTUN { tunInterface.Close() @@ -107,15 +100,15 @@ func (w *SystemDevice) Start() error { return nil } -func (w *SystemDevice) File() *os.File { +func (w *systemDevice) File() *os.File { return nil } -func (w *SystemDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { +func (w *systemDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { if w.batchDevice != nil { - count, err = w.batchDevice.BatchRead(bufs, offset, sizes) + count, err = w.batchDevice.BatchRead(bufs, offset-tun.PacketOffset, sizes) } else { - sizes[0], err = w.device.Read(bufs[0][offset:]) + sizes[0], err = w.device.Read(bufs[0][offset-tun.PacketOffset:]) if err == nil { count = 1 } else if errors.Is(err, tun.ErrTooManySegments) { @@ -125,12 +118,16 @@ func (w *SystemDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, return } -func (w *SystemDevice) Write(bufs [][]byte, offset int) (count int, err error) { +func (w *systemDevice) Write(bufs [][]byte, offset int) (count int, err error) { if w.batchDevice != nil { - return 0, w.batchDevice.BatchWrite(bufs, offset) + return w.batchDevice.BatchWrite(bufs, offset) } else { - for _, b := range bufs { - _, err = w.device.Write(b[offset:]) + for _, packet := range bufs { + if tun.PacketOffset > 0 { + common.ClearArray(packet[offset-tun.PacketOffset : offset]) + tun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:])) + } + _, err = w.device.Write(packet[offset-tun.PacketOffset:]) if err != nil { return } @@ -140,28 +137,28 @@ func (w *SystemDevice) Write(bufs [][]byte, offset int) (count int, err error) { return } -func (w *SystemDevice) Flush() error { +func (w *systemDevice) Flush() error { return nil } -func (w *SystemDevice) MTU() (int, error) { - return int(w.mtu), nil +func (w *systemDevice) MTU() (int, error) { + return int(w.options.MTU), nil } -func (w *SystemDevice) Name() (string, error) { - return w.name, nil +func (w *systemDevice) Name() (string, error) { + return w.options.Name, nil } -func (w *SystemDevice) Events() <-chan wgTun.Event { +func (w *systemDevice) Events() <-chan wgTun.Event { return w.events } -func (w *SystemDevice) Close() error { +func (w *systemDevice) Close() error { close(w.events) return w.device.Close() } -func (w *SystemDevice) BatchSize() int { +func (w *systemDevice) BatchSize() int { if w.batchDevice != nil { return w.batchDevice.BatchSize() } diff --git a/transport/wireguard/device_system_stack.go b/transport/wireguard/device_system_stack.go new file mode 100644 index 0000000000..4249e53eaa --- /dev/null +++ b/transport/wireguard/device_system_stack.go @@ -0,0 +1,182 @@ +//go:build with_gvisor + +package wireguard + +import ( + "net/netip" + + "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/stack" + "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" + "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/wireguard-go/device" +) + +var _ Device = (*systemStackDevice)(nil) + +type systemStackDevice struct { + *systemDevice + stack *stack.Stack + endpoint *deviceEndpoint + writeBufs [][]byte +} + +func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) { + system, err := newSystemDevice(options) + if err != nil { + return nil, err + } + endpoint := &deviceEndpoint{ + mtu: options.MTU, + done: make(chan struct{}), + } + ipStack, err := tun.NewGVisorStack(endpoint) + 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) + return &systemStackDevice{ + systemDevice: system, + stack: ipStack, + endpoint: endpoint, + }, nil +} + +func (w *systemStackDevice) SetDevice(device *device.Device) { + w.endpoint.device = device +} + +func (w *systemStackDevice) Write(bufs [][]byte, offset int) (count int, err error) { + if w.batchDevice != nil { + w.writeBufs = w.writeBufs[:0] + for _, packet := range bufs { + if !w.writeStack(packet[offset:]) { + w.writeBufs = append(w.writeBufs, packet) + } + } + if len(w.writeBufs) > 0 { + return w.batchDevice.BatchWrite(bufs, offset) + } + } else { + for _, packet := range bufs { + if !w.writeStack(packet[offset:]) { + if tun.PacketOffset > 0 { + common.ClearArray(packet[offset-tun.PacketOffset : offset]) + tun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:])) + } + _, err = w.device.Write(packet[offset-tun.PacketOffset:]) + } + if err != nil { + return + } + } + } + // WireGuard will not read count + return +} + +func (w *systemStackDevice) Close() error { + close(w.endpoint.done) + w.stack.Close() + for _, endpoint := range w.stack.CleanupEndpoints() { + endpoint.Abort() + } + w.stack.Wait() + return w.systemDevice.Close() +} + +func (w *systemStackDevice) writeStack(packet []byte) bool { + var ( + networkProtocol tcpip.NetworkProtocolNumber + destination netip.Addr + ) + switch header.IPVersion(packet) { + case header.IPv4Version: + networkProtocol = header.IPv4ProtocolNumber + destination = netip.AddrFrom4(header.IPv4(packet).DestinationAddress().As4()) + case header.IPv6Version: + networkProtocol = header.IPv6ProtocolNumber + destination = netip.AddrFrom16(header.IPv6(packet).DestinationAddress().As16()) + } + for _, prefix := range w.options.Address { + if prefix.Contains(destination) { + return false + } + } + packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: buffer.MakeWithData(packet), + }) + w.endpoint.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer) + packetBuffer.DecRef() + return true +} + +type deviceEndpoint struct { + mtu uint32 + done chan struct{} + device *device.Device + dispatcher stack.NetworkDispatcher +} + +func (ep *deviceEndpoint) MTU() uint32 { + return ep.mtu +} + +func (ep *deviceEndpoint) SetMTU(mtu uint32) { +} + +func (ep *deviceEndpoint) MaxHeaderLength() uint16 { + return 0 +} + +func (ep *deviceEndpoint) LinkAddress() tcpip.LinkAddress { + return "" +} + +func (ep *deviceEndpoint) SetLinkAddress(addr tcpip.LinkAddress) { +} + +func (ep *deviceEndpoint) Capabilities() stack.LinkEndpointCapabilities { + return stack.CapabilityRXChecksumOffload +} + +func (ep *deviceEndpoint) Attach(dispatcher stack.NetworkDispatcher) { + ep.dispatcher = dispatcher +} + +func (ep *deviceEndpoint) IsAttached() bool { + return ep.dispatcher != nil +} + +func (ep *deviceEndpoint) Wait() { +} + +func (ep *deviceEndpoint) ARPHardwareType() header.ARPHardwareType { + return header.ARPHardwareNone +} + +func (ep *deviceEndpoint) AddHeader(buffer *stack.PacketBuffer) { +} + +func (ep *deviceEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool { + return true +} + +func (ep *deviceEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) { + for _, packetBuffer := range list.AsSlice() { + destination := packetBuffer.Network().DestinationAddress() + ep.device.InputPacket(destination.AsSlice(), packetBuffer.AsSlices()) + } + return list.Len(), nil +} + +func (ep *deviceEndpoint) Close() { +} + +func (ep *deviceEndpoint) SetOnCloseAction(f func()) { +} diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 3c3ec7db5c..bdac39e8ba 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -1,35 +1,260 @@ package wireguard import ( + "context" + "encoding/base64" + "encoding/hex" + "fmt" + "net" "net/netip" + "os" + "strings" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" "github.com/sagernet/wireguard-go/conn" + "github.com/sagernet/wireguard-go/device" + + "go4.org/netipx" ) -var _ conn.Endpoint = (*Endpoint)(nil) +type Endpoint struct { + options EndpointOptions + peers []peerConfig + ipcConf string + allowedAddress []netip.Prefix + tunDevice Device + device *device.Device + pauseManager pause.Manager + pauseCallback *list.Element[pause.Callback] +} + +func NewEndpoint(options EndpointOptions) (*Endpoint, error) { + if options.PrivateKey == "" { + return nil, E.New("missing private key") + } + privateKeyBytes, err := base64.StdEncoding.DecodeString(options.PrivateKey) + if err != nil { + return nil, E.Cause(err, "decode private key") + } + privateKey := hex.EncodeToString(privateKeyBytes) + ipcConf := "private_key=" + privateKey + if options.ListenPort != 0 { + ipcConf += "\nlisten_port=" + F.ToString(options.ListenPort) + } + var peers []peerConfig + for peerIndex, rawPeer := range options.Peers { + peer := peerConfig{ + allowedIPs: rawPeer.AllowedIPs, + keepalive: rawPeer.PersistentKeepaliveInterval, + } + if rawPeer.Endpoint.Addr.IsValid() { + peer.endpoint = rawPeer.Endpoint.AddrPort() + } else if rawPeer.Endpoint.IsFqdn() { + peer.destination = rawPeer.Endpoint + } + publicKeyBytes, err := base64.StdEncoding.DecodeString(rawPeer.PublicKey) + if err != nil { + return nil, E.Cause(err, "decode public key for peer ", peerIndex) + } + peer.publicKeyHex = hex.EncodeToString(publicKeyBytes) + if rawPeer.PreSharedKey != "" { + preSharedKeyBytes, err := base64.StdEncoding.DecodeString(rawPeer.PreSharedKey) + if err != nil { + return nil, E.Cause(err, "decode pre shared key for peer ", peerIndex) + } + peer.preSharedKeyHex = hex.EncodeToString(preSharedKeyBytes) + } + if len(rawPeer.AllowedIPs) == 0 { + return nil, E.New("missing allowed ips for peer ", peerIndex) + } + if len(rawPeer.Reserved) > 0 { + if len(rawPeer.Reserved) != 3 { + return nil, E.New("invalid reserved value for peer ", peerIndex, ", required 3 bytes, got ", len(peer.reserved)) + } + copy(peer.reserved[:], rawPeer.Reserved[:]) + } + peers = append(peers, peer) + } + var allowedPrefixBuilder netipx.IPSetBuilder + for _, peer := range options.Peers { + for _, prefix := range peer.AllowedIPs { + allowedPrefixBuilder.AddPrefix(prefix) + } + } + allowedIPSet, err := allowedPrefixBuilder.IPSet() + if err != nil { + return nil, err + } + allowedAddresses := allowedIPSet.Prefixes() + if options.MTU == 0 { + options.MTU = 1408 + } + deviceOptions := DeviceOptions{ + Context: options.Context, + Logger: options.Logger, + System: options.System, + Handler: options.Handler, + UDPTimeout: options.UDPTimeout, + CreateDialer: options.CreateDialer, + Name: options.Name, + MTU: options.MTU, + GSO: options.GSO, + Address: options.Address, + AllowedAddress: allowedAddresses, + } + tunDevice, err := NewDevice(deviceOptions) + if err != nil { + return nil, E.Cause(err, "create WireGuard device") + } + return &Endpoint{ + options: options, + peers: peers, + ipcConf: ipcConf, + allowedAddress: allowedAddresses, + tunDevice: tunDevice, + }, nil +} -type Endpoint netip.AddrPort +func (e *Endpoint) Start(resolve bool) error { + if common.Any(e.peers, func(peer peerConfig) bool { + return !peer.endpoint.IsValid() && peer.destination.IsFqdn() + }) { + if !resolve { + return nil + } + for peerIndex, peer := range e.peers { + if peer.endpoint.IsValid() || !peer.destination.IsFqdn() { + continue + } + destinationAddress, err := e.options.ResolvePeer(peer.destination.Fqdn) + if err != nil { + return E.Cause(err, "resolve endpoint domain for peer[", peerIndex, "]: ", peer.destination) + } + e.peers[peerIndex].endpoint = netip.AddrPortFrom(destinationAddress, peer.destination.Port) + } + } else if resolve { + return nil + } + var bind conn.Bind + wgListener, isWgListener := e.options.Dialer.(conn.Listener) + if isWgListener { + bind = conn.NewStdNetBind(wgListener) + } else { + var ( + isConnect bool + connectAddr netip.AddrPort + reserved [3]uint8 + ) + if len(e.peers) == 1 { + isConnect = true + connectAddr = e.peers[0].endpoint + reserved = e.peers[0].reserved + } + bind = NewClientBind(e.options.Context, e.options.Logger, e.options.Dialer, isConnect, connectAddr, reserved) + } + if isWgListener || len(e.peers) > 1 { + for _, peer := range e.peers { + if peer.reserved != [3]uint8{} { + bind.SetReservedForEndpoint(peer.endpoint, peer.reserved) + } + } + } + err := e.tunDevice.Start() + if err != nil { + return err + } + logger := &device.Logger{ + Verbosef: func(format string, args ...interface{}) { + e.options.Logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) + }, + Errorf: func(format string, args ...interface{}) { + e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) + }, + } + wgDevice := device.NewDevice(e.options.Context, e.tunDevice, bind, logger, e.options.Workers) + e.tunDevice.SetDevice(wgDevice) + ipcConf := e.ipcConf + for _, peer := range e.peers { + ipcConf += peer.GenerateIpcLines() + } + err = wgDevice.IpcSet(ipcConf) + if err != nil { + return E.Cause(err, "setup wireguard: \n", ipcConf) + } + e.device = wgDevice + e.pauseManager = service.FromContext[pause.Manager](e.options.Context) + if e.pauseManager != nil { + e.pauseCallback = e.pauseManager.RegisterCallback(e.onPauseUpdated) + } + return nil +} + +func (e *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if !destination.Addr.IsValid() { + return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination") + } + return e.tunDevice.DialContext(ctx, network, destination) +} -func (e Endpoint) ClearSrc() { +func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if !destination.Addr.IsValid() { + return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination") + } + return e.tunDevice.ListenPacket(ctx, destination) } -func (e Endpoint) SrcToString() string { - return "" +func (e *Endpoint) BindUpdate() error { + return e.device.BindUpdate() } -func (e Endpoint) DstToString() string { - return (netip.AddrPort)(e).String() +func (e *Endpoint) Close() error { + if e.device != nil { + e.device.Close() + } + if e.pauseCallback != nil { + e.pauseManager.UnregisterCallback(e.pauseCallback) + } + return nil } -func (e Endpoint) DstToBytes() []byte { - b, _ := (netip.AddrPort)(e).MarshalBinary() - return b +func (e *Endpoint) onPauseUpdated(event int) { + switch event { + case pause.EventDevicePaused: + e.device.Down() + case pause.EventDeviceWake: + e.device.Up() + } } -func (e Endpoint) DstIP() netip.Addr { - return (netip.AddrPort)(e).Addr() +type peerConfig struct { + destination M.Socksaddr + endpoint netip.AddrPort + publicKeyHex string + preSharedKeyHex string + allowedIPs []netip.Prefix + keepalive uint16 + reserved [3]uint8 } -func (e Endpoint) SrcIP() netip.Addr { - return netip.Addr{} +func (c peerConfig) GenerateIpcLines() string { + ipcLines := "\npublic_key=" + c.publicKeyHex + if c.endpoint.IsValid() { + ipcLines += "\nendpoint=" + c.endpoint.String() + } + if c.preSharedKeyHex != "" { + ipcLines += "\npreshared_key=" + c.preSharedKeyHex + } + for _, allowedIP := range c.allowedIPs { + ipcLines += "\nallowed_ip=" + allowedIP.String() + } + if c.keepalive > 0 { + ipcLines += "\npersistent_keepalive_interval=" + F.ToString(c.keepalive) + } + return ipcLines } diff --git a/transport/wireguard/endpoint_options.go b/transport/wireguard/endpoint_options.go new file mode 100644 index 0000000000..d44422e334 --- /dev/null +++ b/transport/wireguard/endpoint_options.go @@ -0,0 +1,40 @@ +package wireguard + +import ( + "context" + "net/netip" + "time" + + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type EndpointOptions struct { + Context context.Context + Logger logger.ContextLogger + System bool + Handler tun.Handler + UDPTimeout time.Duration + Dialer N.Dialer + CreateDialer func(interfaceName string) N.Dialer + Name string + MTU uint32 + GSO bool + Address []netip.Prefix + PrivateKey string + ListenPort uint16 + ResolvePeer func(domain string) (netip.Addr, error) + Peers []PeerOptions + Workers int +} + +type PeerOptions struct { + Endpoint M.Socksaddr + PublicKey string + PreSharedKey string + AllowedIPs []netip.Prefix + PersistentKeepaliveInterval uint16 + Reserved []uint8 +} diff --git a/transport/wireguard/resolve.go b/transport/wireguard/resolve.go deleted file mode 100644 index d7a1d19c03..0000000000 --- a/transport/wireguard/resolve.go +++ /dev/null @@ -1,148 +0,0 @@ -package wireguard - -import ( - "context" - "encoding/base64" - "encoding/hex" - "net/netip" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" -) - -type PeerConfig struct { - destination M.Socksaddr - domainStrategy dns.DomainStrategy - Endpoint netip.AddrPort - PublicKey string - PreSharedKey string - AllowedIPs []string - Reserved [3]uint8 -} - -func (c PeerConfig) GenerateIpcLines() string { - ipcLines := "\npublic_key=" + c.PublicKey - ipcLines += "\nendpoint=" + c.Endpoint.String() - if c.PreSharedKey != "" { - ipcLines += "\npreshared_key=" + c.PreSharedKey - } - for _, allowedIP := range c.AllowedIPs { - ipcLines += "\nallowed_ip=" + allowedIP - } - return ipcLines -} - -func ParsePeers(options option.WireGuardOutboundOptions) ([]PeerConfig, error) { - var peers []PeerConfig - if len(options.Peers) > 0 { - for peerIndex, rawPeer := range options.Peers { - peer := PeerConfig{ - AllowedIPs: rawPeer.AllowedIPs, - } - destination := rawPeer.ServerOptions.Build() - if destination.IsFqdn() { - peer.destination = destination - peer.domainStrategy = dns.DomainStrategy(options.DomainStrategy) - } else { - peer.Endpoint = destination.AddrPort() - } - { - bytes, err := base64.StdEncoding.DecodeString(rawPeer.PublicKey) - if err != nil { - return nil, E.Cause(err, "decode public key for peer ", peerIndex) - } - peer.PublicKey = hex.EncodeToString(bytes) - } - if rawPeer.PreSharedKey != "" { - bytes, err := base64.StdEncoding.DecodeString(rawPeer.PreSharedKey) - if err != nil { - return nil, E.Cause(err, "decode pre shared key for peer ", peerIndex) - } - peer.PreSharedKey = hex.EncodeToString(bytes) - } - if len(rawPeer.AllowedIPs) == 0 { - return nil, E.New("missing allowed_ips for peer ", peerIndex) - } - if len(rawPeer.Reserved) > 0 { - if len(rawPeer.Reserved) != 3 { - return nil, E.New("invalid reserved value for peer ", peerIndex, ", required 3 bytes, got ", len(peer.Reserved)) - } - copy(peer.Reserved[:], options.Reserved) - } - peers = append(peers, peer) - } - } else { - peer := PeerConfig{} - var ( - addressHas4 bool - addressHas6 bool - ) - for _, localAddress := range options.LocalAddress { - if localAddress.Addr().Is4() { - addressHas4 = true - } else { - addressHas6 = true - } - } - if addressHas4 { - peer.AllowedIPs = append(peer.AllowedIPs, netip.PrefixFrom(netip.IPv4Unspecified(), 0).String()) - } - if addressHas6 { - peer.AllowedIPs = append(peer.AllowedIPs, netip.PrefixFrom(netip.IPv6Unspecified(), 0).String()) - } - destination := options.ServerOptions.Build() - if destination.IsFqdn() { - peer.destination = destination - peer.domainStrategy = dns.DomainStrategy(options.DomainStrategy) - } else { - peer.Endpoint = destination.AddrPort() - } - { - bytes, err := base64.StdEncoding.DecodeString(options.PeerPublicKey) - if err != nil { - return nil, E.Cause(err, "decode peer public key") - } - peer.PublicKey = hex.EncodeToString(bytes) - } - if options.PreSharedKey != "" { - bytes, err := base64.StdEncoding.DecodeString(options.PreSharedKey) - if err != nil { - return nil, E.Cause(err, "decode pre shared key") - } - peer.PreSharedKey = hex.EncodeToString(bytes) - } - if len(options.Reserved) > 0 { - if len(options.Reserved) != 3 { - return nil, E.New("invalid reserved value, required 3 bytes, got ", len(peer.Reserved)) - } - copy(peer.Reserved[:], options.Reserved) - } - peers = append(peers, peer) - } - return peers, nil -} - -func ResolvePeers(ctx context.Context, router adapter.Router, peers []PeerConfig) error { - for peerIndex, peer := range peers { - if peer.Endpoint.IsValid() { - continue - } - destinationAddresses, err := router.Lookup(ctx, peer.destination.Fqdn, peer.domainStrategy) - if err != nil { - if len(peers) == 1 { - return E.Cause(err, "resolve endpoint domain") - } else { - return E.Cause(err, "resolve endpoint domain for peer ", peerIndex) - } - } - if len(destinationAddresses) == 0 { - return E.New("no addresses found for endpoint domain: ", peer.destination.Fqdn) - } - peers[peerIndex].Endpoint = netip.AddrPortFrom(destinationAddresses[0], peer.destination.Port) - - } - return nil -} From 0c66888691a5a346fad6cefb4457183290d5c0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 23 Nov 2024 22:34:02 +0800 Subject: [PATCH 0177/1272] Fix lint --- adapter/handler.go | 3 + adapter/inbound.go | 3 +- adapter/upstream_legacy.go | 26 +++++- common/dialer/default_parallel_interface.go | 3 - common/mux/router.go | 10 ++- go.mod | 2 +- go.sum | 4 +- option/tun.go | 3 +- protocol/direct/inbound.go | 45 +++++----- protocol/direct/outbound.go | 4 + protocol/http/inbound.go | 30 +++---- protocol/hysteria/inbound.go | 4 + protocol/hysteria2/inbound.go | 4 + protocol/mixed/inbound.go | 4 - protocol/naive/inbound.go | 2 + protocol/redirect/redirect.go | 2 - protocol/redirect/tproxy.go | 2 - protocol/shadowsocks/inbound.go | 6 +- protocol/shadowsocks/inbound_multi.go | 7 ++ protocol/shadowsocks/inbound_relay.go | 7 ++ protocol/shadowtls/inbound.go | 2 + protocol/socks/inbound.go | 4 - protocol/tor/proxy.go | 24 +++--- protocol/trojan/inbound.go | 96 ++++++++++----------- protocol/tuic/inbound.go | 4 + protocol/tun/inbound.go | 20 ++--- protocol/vless/inbound.go | 58 ++++--------- protocol/vmess/inbound.go | 58 ++++--------- route/network.go | 2 +- route/route.go | 4 +- transport/trojan/mux.go | 22 +++-- transport/trojan/service.go | 29 +++---- 32 files changed, 245 insertions(+), 249 deletions(-) diff --git a/adapter/handler.go b/adapter/handler.go index 6da158ab20..f8912110f9 100644 --- a/adapter/handler.go +++ b/adapter/handler.go @@ -46,6 +46,9 @@ type PacketConnectionHandlerEx interface { NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) } +// Deprecated: use TCPConnectionHandlerEx instead +// +//nolint:staticcheck type UpstreamHandlerAdapter interface { N.TCPConnectionHandler N.UDPConnectionHandler diff --git a/adapter/inbound.go b/adapter/inbound.go index b4eaca3d4d..fd9c5405d4 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -65,7 +65,8 @@ type InboundContext struct { LastInbound string OriginDestination M.Socksaddr RouteOriginalDestination M.Socksaddr - // Deprecated + // Deprecated: to be removed + //nolint:staticcheck InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool UDPConnect bool diff --git a/adapter/upstream_legacy.go b/adapter/upstream_legacy.go index 1c6c15c017..60f5fdb466 100644 --- a/adapter/upstream_legacy.go +++ b/adapter/upstream_legacy.go @@ -18,6 +18,8 @@ type ( ) // Deprecated +// +//nolint:staticcheck func NewUpstreamHandler( metadata InboundContext, connectionHandler ConnectionHandlerFunc, @@ -34,7 +36,9 @@ func NewUpstreamHandler( var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil) -// Deprecated +// Deprecated: use myUpstreamHandlerWrapperEx instead. +// +//nolint:staticcheck type myUpstreamHandlerWrapper struct { metadata InboundContext connectionHandler ConnectionHandlerFunc @@ -42,6 +46,7 @@ type myUpstreamHandlerWrapper struct { errorHandler E.Handler } +// Deprecated: use myUpstreamHandlerWrapperEx instead. func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { myMetadata := w.metadata if metadata.Source.IsValid() { @@ -53,6 +58,7 @@ func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.C return w.connectionHandler(ctx, conn, myMetadata) } +// Deprecated: use myUpstreamHandlerWrapperEx instead. func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { myMetadata := w.metadata if metadata.Source.IsValid() { @@ -64,11 +70,12 @@ func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn return w.packetHandler(ctx, conn, myMetadata) } +// Deprecated: use myUpstreamHandlerWrapperEx instead. func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { w.errorHandler.NewError(ctx, err) } -// Deprecated +// Deprecated: removed func UpstreamMetadata(metadata InboundContext) M.Metadata { return M.Metadata{ Source: metadata.Source, @@ -76,14 +83,14 @@ func UpstreamMetadata(metadata InboundContext) M.Metadata { } } -// Deprecated +// Deprecated: Use NewUpstreamContextHandlerEx instead. type myUpstreamContextHandlerWrapper struct { connectionHandler ConnectionHandlerFunc packetHandler PacketConnectionHandlerFunc errorHandler E.Handler } -// Deprecated +// Deprecated: Use NewUpstreamContextHandlerEx instead. func NewUpstreamContextHandler( connectionHandler ConnectionHandlerFunc, packetHandler PacketConnectionHandlerFunc, @@ -96,6 +103,7 @@ func NewUpstreamContextHandler( } } +// Deprecated: Use NewUpstreamContextHandlerEx instead. func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { myMetadata := ContextFrom(ctx) if metadata.Source.IsValid() { @@ -107,6 +115,7 @@ func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, con return w.connectionHandler(ctx, conn, *myMetadata) } +// Deprecated: Use NewUpstreamContextHandlerEx instead. func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { myMetadata := ContextFrom(ctx) if metadata.Source.IsValid() { @@ -118,6 +127,7 @@ func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Contex return w.packetHandler(ctx, conn, *myMetadata) } +// Deprecated: Use NewUpstreamContextHandlerEx instead. func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) { w.errorHandler.NewError(ctx, err) } @@ -149,12 +159,15 @@ func NewRouteContextHandler( var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil) // Deprecated: Use ConnectionRouterEx instead. +// +//nolint:staticcheck type routeHandlerWrapper struct { metadata InboundContext router ConnectionRouter logger logger.ContextLogger } +// Deprecated: Use ConnectionRouterEx instead. func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { myMetadata := w.metadata if metadata.Source.IsValid() { @@ -166,6 +179,7 @@ func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, return w.router.RouteConnection(ctx, conn, myMetadata) } +// Deprecated: Use ConnectionRouterEx instead. func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { myMetadata := w.metadata if metadata.Source.IsValid() { @@ -177,6 +191,7 @@ func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.Pa return w.router.RoutePacketConnection(ctx, conn, myMetadata) } +// Deprecated: Use ConnectionRouterEx instead. func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) { w.logger.ErrorContext(ctx, err) } @@ -189,6 +204,7 @@ type routeContextHandlerWrapper struct { logger logger.ContextLogger } +// Deprecated: Use ConnectionRouterEx instead. func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { myMetadata := ContextFrom(ctx) if metadata.Source.IsValid() { @@ -200,6 +216,7 @@ func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net return w.router.RouteConnection(ctx, conn, *myMetadata) } +// Deprecated: Use ConnectionRouterEx instead. func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { myMetadata := ContextFrom(ctx) if metadata.Source.IsValid() { @@ -211,6 +228,7 @@ func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, co return w.router.RoutePacketConnection(ctx, conn, *myMetadata) } +// Deprecated: Use ConnectionRouterEx instead. func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) { w.logger.ErrorContext(ctx, err) } diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go index 71d9814bd2..37a1f79cab 100644 --- a/common/dialer/default_parallel_interface.go +++ b/common/dialer/default_parallel_interface.go @@ -149,9 +149,6 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, E.New("no available network interface") } - if fallbackDelay == 0 { - fallbackDelay = N.DefaultFallbackDelay - } var errors []error for _, primaryInterface := range primaryInterfaces { perNetListener := listener diff --git a/common/mux/router.go b/common/mux/router.go index 2d4e7705d9..ec78808600 100644 --- a/common/mux/router.go +++ b/common/mux/router.go @@ -41,10 +41,10 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context { return log.ContextWithNewID(ctx) }, - Logger: logger, - Handler: adapter.NewRouteContextHandler(router, logger), - Padding: options.Padding, - Brutal: brutalOptions, + Logger: logger, + HandlerEx: adapter.NewRouteContextHandlerEx(router), + Padding: options.Padding, + Brutal: brutalOptions, }) if err != nil { return nil, err @@ -52,6 +52,7 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte return &Router{router, service}, nil } +// Deprecated: Use RouteConnectionEx instead. func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if metadata.Destination == mux.Destination { // TODO: check if WithContext is necessary @@ -61,6 +62,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad } } +// Deprecated: Use RoutePacketConnectionEx instead. func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return r.router.RoutePacketConnection(ctx, conn, metadata) } diff --git a/go.mod b/go.mod index 9b60b615f5..1ad0c046d7 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 github.com/sagernet/sing-tun v0.6.0-beta.8 - github.com/sagernet/sing-vmess v0.1.13 + github.com/sagernet/sing-vmess v0.2.0-beta.2 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.1-beta.5 diff --git a/go.sum b/go.sum index 0c075a08b3..d20329135e 100644 --- a/go.sum +++ b/go.sum @@ -135,8 +135,8 @@ github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjS github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw= github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= -github.com/sagernet/sing-vmess v0.1.13 h1:/GSfD1Rt6/mVfE80WFHNBykNT7KJNWWmvcMP9DoElEs= -github.com/sagernet/sing-vmess v0.1.13/go.mod h1:D+g+lhv4iOk1Pn08pd3MtUd72khL5+wgEE3xVH8J+ow= +github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs= +github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk= 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/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= diff --git a/option/tun.go b/option/tun.go index a7a0f6bc07..d729e341a1 100644 --- a/option/tun.go +++ b/option/tun.go @@ -35,7 +35,6 @@ type TunInboundOptions struct { 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"` - EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` Stack string `json:"stack,omitempty"` Platform *TunPlatformOptions `json:"platform,omitempty"` @@ -53,6 +52,8 @@ type TunInboundOptions struct { Inet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` // Deprecated: merged to RouteExcludeAddress Inet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` + // Deprecated: removed + EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` } type FwMark uint32 diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go index c9594f9a7c..93994761e1 100644 --- a/protocol/direct/inbound.go +++ b/protocol/direct/inbound.go @@ -80,36 +80,25 @@ func (i *Inbound) Close() error { } func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - var destination M.Socksaddr - switch i.overrideOption { - case 1: - destination = i.overrideDestination - case 2: - destination = i.overrideDestination - destination.Port = i.listener.UDPAddr().Port - case 3: - destination = source - destination.Port = i.overrideDestination.Port - } - i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil) + i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.Socksaddr{}, nil) } func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + destination := metadata.OriginDestination switch i.overrideOption { case 1: - metadata.Destination = i.overrideDestination + destination = i.overrideDestination case 2: - destination := i.overrideDestination - destination.Port = metadata.Destination.Port - metadata.Destination = destination + destination.Addr = i.overrideDestination.Addr case 3: - metadata.Destination.Port = i.overrideDestination.Port + destination.Port = metadata.Destination.Port + } + metadata.Destination = destination + if i.overrideOption != 0 { + i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) } - i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - metadata.Inbound = i.Tag() - metadata.InboundType = i.Type() - metadata.InboundDetour = i.listener.ListenOptions().Detour - metadata.InboundOptions = i.listener.ListenOptions().InboundOptions i.router.RouteConnectionEx(ctx, conn, metadata, onClose) } @@ -119,9 +108,21 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, var metadata adapter.InboundContext metadata.Inbound = i.Tag() metadata.InboundType = i.Type() + //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 { + case 1: + destination = i.overrideDestination + case 2: + destination.Addr = i.overrideDestination.Addr + case 3: + destination.Port = i.overrideDestination.Port + default: + } metadata.Destination = destination metadata.OriginDestination = i.listener.UDPAddr() i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 4962ea7025..42fd284de5 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -63,9 +63,11 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL dialer: outboundDialer, // 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) @@ -161,6 +163,7 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination if h.domainStrategy != dns.DomainStrategyAsIS { domainStrategy = h.domainStrategy } else { + //nolint:staticcheck domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) } switch domainStrategy { @@ -200,6 +203,7 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest if h.domainStrategy != dns.DomainStrategyAsIS { domainStrategy = h.domainStrategy } else { + //nolint:staticcheck domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) } switch domainStrategy { diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go index a7a463f76b..8aa1d4159a 100644 --- a/protocol/http/inbound.go +++ b/protocol/http/inbound.go @@ -82,33 +82,25 @@ func (h *Inbound) Close() error { } func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.newConnection(ctx, conn, metadata, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - if E.IsClosedOrCanceled(err) { - h.logger.DebugContext(ctx, "connection closed: ", err) - } else { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } - } -} - -func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - var err error if h.tlsConfig != nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) if err != nil { - return err + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return } + conn = tlsConn + } + err := http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) } - return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) } func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) @@ -123,8 +115,6 @@ func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) diff --git a/protocol/hysteria/inbound.go b/protocol/hysteria/inbound.go index 6fa03479a2..872e4dd770 100644 --- a/protocol/hysteria/inbound.go +++ b/protocol/hysteria/inbound.go @@ -123,7 +123,9 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source @@ -144,7 +146,9 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source diff --git a/protocol/hysteria2/inbound.go b/protocol/hysteria2/inbound.go index a3260e55ee..8d00072cc4 100644 --- a/protocol/hysteria2/inbound.go +++ b/protocol/hysteria2/inbound.go @@ -134,7 +134,9 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source @@ -155,7 +157,9 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go index ad7bed7de2..d1d343a991 100644 --- a/protocol/mixed/inbound.go +++ b/protocol/mixed/inbound.go @@ -94,8 +94,6 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) @@ -110,8 +108,6 @@ func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go index 18acd2accd..5e77af435c 100644 --- a/protocol/naive/inbound.go +++ b/protocol/naive/inbound.go @@ -195,7 +195,9 @@ func (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net var metadata adapter.InboundContext metadata.Inbound = n.Tag() metadata.InboundType = n.Type() + //nolint:staticcheck metadata.InboundDetour = n.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = n.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination diff --git a/protocol/redirect/redirect.go b/protocol/redirect/redirect.go index 0950d102b9..e04db8c4df 100644 --- a/protocol/redirect/redirect.go +++ b/protocol/redirect/redirect.go @@ -62,8 +62,6 @@ func (h *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata } metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.Destination = M.SocksaddrFromNetIP(destination) h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) h.router.RouteConnectionEx(ctx, conn, metadata, onClose) diff --git a/protocol/redirect/tproxy.go b/protocol/redirect/tproxy.go index 78f0079fde..7860e173d6 100644 --- a/protocol/redirect/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -106,8 +106,6 @@ func (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, s var metadata adapter.InboundContext metadata.Inbound = t.Tag() metadata.InboundType = t.Type() - metadata.InboundDetour = t.listener.ListenOptions().Detour - metadata.InboundOptions = t.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination metadata.OriginDestination = t.listener.UDPAddr() diff --git a/protocol/shadowsocks/inbound.go b/protocol/shadowsocks/inbound.go index 8332a93cc0..89af24ea43 100644 --- a/protocol/shadowsocks/inbound.go +++ b/protocol/shadowsocks/inbound.go @@ -104,6 +104,7 @@ func (h *Inbound) Close() error { return h.listener.Close() } +//nolint:staticcheck func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) @@ -116,6 +117,7 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a } } +//nolint:staticcheck func (h *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) if err != nil { @@ -127,8 +129,6 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RouteConnection(ctx, conn, metadata) } @@ -138,8 +138,6 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RoutePacketConnection(ctx, conn, metadata) } diff --git a/protocol/shadowsocks/inbound_multi.go b/protocol/shadowsocks/inbound_multi.go index ec55713430..5b6043657b 100644 --- a/protocol/shadowsocks/inbound_multi.go +++ b/protocol/shadowsocks/inbound_multi.go @@ -112,6 +112,7 @@ func (h *MultiInbound) Close() error { return h.listener.Close() } +//nolint:staticcheck func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) @@ -124,6 +125,7 @@ func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metad } } +//nolint:staticcheck func (h *MultiInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) if err != nil { @@ -145,7 +147,9 @@ func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadat h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RouteConnection(ctx, conn, metadata) } @@ -166,11 +170,14 @@ func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketCon h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RoutePacketConnection(ctx, conn, metadata) } +//nolint:staticcheck func (h *MultiInbound) NewError(ctx context.Context, err error) { NewError(h.logger, ctx, err) } diff --git a/protocol/shadowsocks/inbound_relay.go b/protocol/shadowsocks/inbound_relay.go index bb20de3f58..9760b2f0ec 100644 --- a/protocol/shadowsocks/inbound_relay.go +++ b/protocol/shadowsocks/inbound_relay.go @@ -97,6 +97,7 @@ func (h *RelayInbound) Close() error { return h.listener.Close() } +//nolint:staticcheck func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) @@ -109,6 +110,7 @@ func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metad } } +//nolint:staticcheck func (h *RelayInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) if err != nil { @@ -130,7 +132,9 @@ func (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadat h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RouteConnection(ctx, conn, metadata) } @@ -151,11 +155,14 @@ func (h *RelayInbound) newPacketConnection(ctx context.Context, conn N.PacketCon h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination) metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RoutePacketConnection(ctx, conn, metadata) } +//nolint:staticcheck func (h *RelayInbound) NewError(ctx context.Context, err error) { NewError(h.logger, ctx, err) } diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index ce4238fd49..5ae5656f48 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -119,7 +119,9 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go index 115db30308..1490cee5cf 100644 --- a/protocol/socks/inbound.go +++ b/protocol/socks/inbound.go @@ -76,8 +76,6 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) @@ -92,8 +90,6 @@ func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions user, loaded := auth.UserFromContext[string](ctx) if !loaded { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) diff --git a/protocol/tor/proxy.go b/protocol/tor/proxy.go index ef60bd1f7e..1ed303350b 100644 --- a/protocol/tor/proxy.go +++ b/protocol/tor/proxy.go @@ -1,13 +1,13 @@ package tor import ( + std_bufio "bufio" "context" "crypto/rand" "encoding/hex" "net" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" @@ -15,12 +15,14 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/protocol/socks" + "github.com/sagernet/sing/service" ) type ProxyListener struct { ctx context.Context logger log.ContextLogger dialer N.Dialer + connection adapter.ConnectionManager tcpListener *net.TCPListener username string password string @@ -38,6 +40,7 @@ func NewProxyListener(ctx context.Context, logger log.ContextLogger, dialer N.Di ctx: ctx, logger: logger, dialer: dialer, + connection: service.FromContext[adapter.ConnectionManager](ctx), authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}), username: username, password: password, @@ -95,25 +98,24 @@ func (l *ProxyListener) acceptLoop() { } } -// TODO: migrate to new api -// -//nolint:staticcheck func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error { - return socks.HandleConnection(ctx, conn, l.authenticator, l, M.Metadata{}) + return socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, nil, l, M.SocksaddrFromNet(conn.RemoteAddr()), M.Socksaddr{}, nil) } -func (l *ProxyListener) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error { +func (l *ProxyListener) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { var metadata adapter.InboundContext + metadata.Source = source + metadata.Destination = destination metadata.Network = N.NetworkTCP - metadata.Destination = upstreamMetadata.Destination l.logger.InfoContext(ctx, "proxy connection to ", metadata.Destination) - return outbound.NewConnection(ctx, l.dialer, conn, metadata) + l.connection.NewConnection(ctx, l.dialer, conn, metadata, onClose) } -func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { +func (l *ProxyListener) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { var metadata adapter.InboundContext + metadata.Source = source + metadata.Destination = destination metadata.Network = N.NetworkUDP - metadata.Destination = upstreamMetadata.Destination l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.Destination) - return outbound.NewPacketConnection(ctx, l.dialer, conn, metadata) + l.connection.NewPacketConnection(ctx, l.dialer, conn, metadata, onClose) } diff --git a/protocol/trojan/inbound.go b/protocol/trojan/inbound.go index 107667c580..ec95a81eab 100644 --- a/protocol/trojan/inbound.go +++ b/protocol/trojan/inbound.go @@ -56,7 +56,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } inbound.tlsConfig = tlsConfig } - var fallbackHandler N.TCPConnectionHandler + var fallbackHandler N.TCPConnectionHandlerEx if options.Fallback != nil && options.Fallback.Server != "" || len(options.FallbackForALPN) > 0 { if options.Fallback != nil && options.Fallback.Server != "" { inbound.fallbackAddr = options.Fallback.Build() @@ -78,9 +78,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } inbound.fallbackAddrTLSNextProto = fallbackAddrNextProto } - fallbackHandler = adapter.NewUpstreamContextHandler(inbound.fallbackConnection, nil, nil) + fallbackHandler = adapter.NewUpstreamContextHandlerEx(inbound.fallbackConnection, nil) } - service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, nil), fallbackHandler, logger) + service := trojan.NewService[int](adapter.NewUpstreamContextHandlerEx(inbound.newConnection, inbound.newPacketConnection), fallbackHandler, logger) err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.TrojanUser) int { return index }), common.Map(options.Users, func(it option.TrojanUser) string { @@ -158,37 +158,30 @@ func (h *Inbound) Close() error { ) } -func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - var err error +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if h.tlsConfig != nil && h.transport == nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) if err != nil { - return err + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return } + conn = tlsConn } - return h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.NewConnection(ctx, conn, metadata) - N.CloseOnHandshakeFailure(conn, onClose, err) + err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose) if err != nil { - if E.IsClosedOrCanceled(err) { - h.logger.DebugContext(ctx, "connection closed: ", err) - } else { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) } } -func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { - return os.ErrInvalid + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } user := h.users[userIndex].Name if user == "" { @@ -197,53 +190,54 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada metadata.User = user } h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + userIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return + } + user := h.users[userIndex].Name + if user == "" { + user = F.ToString(userIndex) + } else { + metadata.User = user + } + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { var fallbackAddr M.Socksaddr if len(h.fallbackAddrTLSNextProto) > 0 { if tlsConn, loaded := common.Cast[tls.Conn](conn); loaded { connectionState := tlsConn.ConnectionState() if connectionState.NegotiatedProtocol != "" { if fallbackAddr, loaded = h.fallbackAddrTLSNextProto[connectionState.NegotiatedProtocol]; !loaded { - return E.New("fallback disabled for ALPN: ", connectionState.NegotiatedProtocol) + h.logger.DebugContext(ctx, "process connection from ", metadata.Source, ": fallback disabled for ALPN: ", connectionState.NegotiatedProtocol) + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } } } } if !fallbackAddr.IsValid() { if !h.fallbackAddr.IsValid() { - return E.New("fallback disabled by default") + h.logger.DebugContext(ctx, "process connection from ", metadata.Source, ": fallback disabled by default") + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } fallbackAddr = h.fallbackAddr } metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions - h.logger.InfoContext(ctx, "fallback connection to ", fallbackAddr) metadata.Destination = fallbackAddr - return h.router.RouteConnection(ctx, conn, metadata) -} - -func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - metadata.Inbound = h.Tag() - metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions - userIndex, loaded := auth.UserFromContext[int](ctx) - if !loaded { - return os.ErrInvalid - } - user := h.users[userIndex].Name - if user == "" { - user = F.ToString(userIndex) - } else { - metadata.User = user - } - h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.logger.InfoContext(ctx, "fallback connection to ", fallbackAddr) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) @@ -254,6 +248,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net. var metadata adapter.InboundContext metadata.Source = source metadata.Destination = destination + //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 a21b72ea5b..c4c632363f 100644 --- a/protocol/tuic/inbound.go +++ b/protocol/tuic/inbound.go @@ -105,7 +105,9 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source @@ -126,7 +128,9 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, var metadata adapter.InboundContext metadata.Inbound = h.Tag() metadata.InboundType = h.Type() + //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index b3ada90550..6463f215ca 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -41,11 +41,9 @@ type Inbound struct { router adapter.Router networkManager adapter.NetworkManager logger log.ContextLogger - // Deprecated - inboundOptions option.InboundOptions - tunOptions tun.Options - // Deprecated - endpointIndependentNat bool + //nolint:staticcheck + inboundOptions option.InboundOptions + tunOptions tun.Options udpTimeout time.Duration stack string tunIf tun.Tun @@ -202,11 +200,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo ExcludePackage: options.ExcludePackage, InterfaceMonitor: networkManager.InterfaceMonitor(), }, - endpointIndependentNat: options.EndpointIndependentNat, - udpTimeout: udpTimeout, - stack: options.Stack, - platformInterface: service.FromContext[platform.Interface](ctx), - platformOptions: common.PtrValueOrDefault(options.Platform), + udpTimeout: udpTimeout, + stack: options.Stack, + platformInterface: service.FromContext[platform.Interface](ctx), + platformOptions: common.PtrValueOrDefault(options.Platform), } if options.AutoRedirect { if !options.AutoRoute { @@ -436,6 +433,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) @@ -449,6 +447,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) @@ -464,6 +463,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) diff --git a/protocol/vless/inbound.go b/protocol/vless/inbound.go index b0aa959e06..a977f73952 100644 --- a/protocol/vless/inbound.go +++ b/protocol/vless/inbound.go @@ -58,7 +58,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if err != nil { return nil, err } - service := vless.NewService[int](logger, adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) + service := vless.NewService[int](logger, adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx)) service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int { return index }), common.Map(inbound.users, func(it option.VLESSUser) string { @@ -138,37 +138,30 @@ func (h *Inbound) Close() error { ) } -func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - var err error +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if h.tlsConfig != nil && h.transport == nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) if err != nil { - return err + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return } + conn = tlsConn } - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.NewConnection(ctx, conn, metadata) - N.CloseOnHandshakeFailure(conn, onClose, err) + err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose) if err != nil { - if E.IsClosedOrCanceled(err) { - h.logger.DebugContext(ctx, "connection closed: ", err) - } else { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) } } -func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { - return os.ErrInvalid + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } user := h.users[userIndex].Name if user == "" { @@ -177,17 +170,16 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada metadata.User = user } h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { - return os.ErrInvalid + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } user := h.users[userIndex].Name if user == "" { @@ -202,7 +194,7 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me } else { h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) } - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) @@ -216,17 +208,3 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net. h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) } - -func (h *Inbound) NewError(ctx context.Context, err error) { - NewError(h.logger, ctx, err) -} - -// Deprecated: remove -func NewError(logger logger.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/protocol/vmess/inbound.go b/protocol/vmess/inbound.go index 9f1009a301..0b8c96c697 100644 --- a/protocol/vmess/inbound.go +++ b/protocol/vmess/inbound.go @@ -65,7 +65,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if options.Transport != nil && options.Transport.Type != "" { serviceOptions = append(serviceOptions, vmess.ServiceWithDisableHeaderProtection()) } - service := vmess.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), serviceOptions...) + service := vmess.NewService[int](adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx), serviceOptions...) inbound.service = service err = service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.VMessUser) int { return index @@ -152,37 +152,30 @@ func (h *Inbound) Close() error { ) } -func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - var err error +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if h.tlsConfig != nil && h.transport == nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) if err != nil { - return err + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return } + conn = tlsConn } - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.NewConnection(ctx, conn, metadata) - N.CloseOnHandshakeFailure(conn, onClose, err) + err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose) if err != nil { - if E.IsClosedOrCanceled(err) { - h.logger.DebugContext(ctx, "connection closed: ", err) - } else { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) } } -func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { - return os.ErrInvalid + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } user := h.users[userIndex].Name if user == "" { @@ -191,17 +184,16 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada metadata.User = user } h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = h.Tag() metadata.InboundType = h.Type() - metadata.InboundDetour = h.listener.ListenOptions().Detour - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { - return os.ErrInvalid + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } user := h.users[userIndex].Name if user == "" { @@ -216,7 +208,7 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me } else { h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) } - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) @@ -230,17 +222,3 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net. h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) } - -func (h *Inbound) NewError(ctx context.Context, err error) { - NewError(h.logger, ctx, err) -} - -// Deprecated: remove -func NewError(logger logger.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/route/network.go b/route/network.go index 7cb3df8d1c..d11508b125 100644 --- a/route/network.go +++ b/route/network.go @@ -202,7 +202,7 @@ func (r *NetworkManager) Close() error { }) monitor.Finish() } - return nil + return err } func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { diff --git a/route/route.go b/route/route.go index a56016f93a..67eb2c691b 100644 --- a/route/route.go +++ b/route/route.go @@ -53,6 +53,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad return E.New("reject connection to ", metadata.Destination, " while device paused") } + //nolint:staticcheck if metadata.InboundDetour != "" { if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) @@ -182,6 +183,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if r.pauseManager.IsDevicePaused() { return E.New("reject packet connection to ", metadata.Destination, " while device paused") } + //nolint:staticcheck if metadata.InboundDetour != "" { if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) @@ -281,7 +283,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error { if !isReject { return nil } - return rejectAction.Error(nil) + return rejectAction.Error(context.Background()) } func (r *Router) matchRule( diff --git a/transport/trojan/mux.go b/transport/trojan/mux.go index 0329bd40fe..72d5a77621 100644 --- a/transport/trojan/mux.go +++ b/transport/trojan/mux.go @@ -4,17 +4,19 @@ import ( std_bufio "bufio" "context" "net" + "os" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/task" "github.com/sagernet/smux" ) -func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) error { +func HandleMuxConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler, logger logger.ContextLogger, onClose N.CloseHandlerFunc) error { session, err := smux.Server(conn, smuxConfig()) if err != nil { return err @@ -27,29 +29,32 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata if err != nil { return err } - go newMuxConnection(ctx, stream, metadata, handler, logger) + go newMuxConnection(ctx, stream, source, handler, logger) } }) group.Cleanup(func() { session.Close() + if onClose != nil { + onClose(os.ErrClosed) + } }) return group.Run(ctx) } -func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) { - err := newMuxConnection0(ctx, conn, metadata, handler) +func newMuxConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler, logger logger.ContextLogger) { + err := newMuxConnection0(ctx, conn, source, handler) if err != nil { logger.ErrorContext(ctx, E.Cause(err, "process trojan-go multiplex connection")) } } -func newMuxConnection0(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) error { +func newMuxConnection0(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler) error { reader := std_bufio.NewReader(conn) command, err := reader.ReadByte() if err != nil { return E.Cause(err, "read command") } - metadata.Destination, err = M.SocksaddrSerializer.ReadAddrPort(reader) + destination, err := M.SocksaddrSerializer.ReadAddrPort(reader) if err != nil { return E.Cause(err, "read destination") } @@ -63,12 +68,13 @@ func newMuxConnection0(ctx context.Context, conn net.Conn, metadata M.Metadata, } switch command { case CommandTCP: - return handler.NewConnection(ctx, conn, metadata) + handler.NewConnectionEx(ctx, conn, source, destination, nil) case CommandUDP: - return handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata) + handler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, source, destination, nil) default: return E.New("unknown command ", command) } + return nil } func smuxConfig() *smux.Config { diff --git a/transport/trojan/service.go b/transport/trojan/service.go index 978d737fe5..7f1803bb38 100644 --- a/transport/trojan/service.go +++ b/transport/trojan/service.go @@ -16,19 +16,19 @@ import ( ) type Handler interface { - N.TCPConnectionHandler - N.UDPConnectionHandler + N.TCPConnectionHandlerEx + N.UDPConnectionHandlerEx } type Service[K comparable] struct { users map[K][56]byte keys map[[56]byte]K handler Handler - fallbackHandler N.TCPConnectionHandler + fallbackHandler N.TCPConnectionHandlerEx logger logger.ContextLogger } -func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandler, logger logger.ContextLogger) *Service[K] { +func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandlerEx, logger logger.ContextLogger) *Service[K] { return &Service[K]{ users: make(map[K][56]byte), keys: make(map[[56]byte]K), @@ -59,19 +59,19 @@ func (s *Service[K]) UpdateUsers(userList []K, passwordList []string) error { return nil } -func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { +func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, onClose N.CloseHandlerFunc) error { var key [KeyLength]byte n, err := conn.Read(key[:]) if err != nil { return err } else if n != KeyLength { - return s.fallback(ctx, conn, metadata, key[:n], E.New("bad request size")) + return s.fallback(ctx, conn, source, key[:n], E.New("bad request size"), onClose) } if user, loaded := s.keys[key]; loaded { ctx = auth.ContextWithUser(ctx, user) } else { - return s.fallback(ctx, conn, metadata, key[:], E.New("bad request")) + return s.fallback(ctx, conn, source, key[:], E.New("bad request"), onClose) } err = rw.SkipN(conn, 2) @@ -102,26 +102,25 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata return E.Cause(err, "skip crlf") } - metadata.Protocol = "trojan" - metadata.Destination = destination - switch command { case CommandTCP: - return s.handler.NewConnection(ctx, conn, metadata) + s.handler.NewConnectionEx(ctx, conn, source, destination, onClose) case CommandUDP: - return s.handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata) + s.handler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, source, destination, onClose) // case CommandMux: default: - return HandleMuxConnection(ctx, conn, metadata, s.handler, s.logger) + return HandleMuxConnection(ctx, conn, source, s.handler, s.logger, onClose) } + return nil } -func (s *Service[K]) fallback(ctx context.Context, conn net.Conn, metadata M.Metadata, header []byte, err error) error { +func (s *Service[K]) fallback(ctx context.Context, conn net.Conn, source M.Socksaddr, header []byte, err error, onClose N.CloseHandlerFunc) error { if s.fallbackHandler == nil { return E.Extend(err, "fallback disabled") } conn = bufio.NewCachedConn(conn, buf.As(header).ToOwned()) - return s.fallbackHandler.NewConnection(ctx, conn, metadata) + s.fallbackHandler.NewConnectionEx(ctx, conn, source, M.Socksaddr{}, onClose) + return nil } type PacketConn struct { From c4b6d0eadb75c6e2a66bf3b81ca291eae65998dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 22 Nov 2024 17:17:01 +0800 Subject: [PATCH 0178/1272] Make GSO adaptive --- docs/configuration/dns/rule.zh.md | 2 +- docs/configuration/endpoint/wireguard.md | 13 ++-- docs/configuration/endpoint/wireguard.zh.md | 13 ++-- docs/configuration/inbound/tun.md | 77 +++++++++++---------- docs/configuration/inbound/tun.zh.md | 75 ++++++++++---------- docs/configuration/outbound/wireguard.md | 13 +++- docs/configuration/outbound/wireguard.zh.md | 13 +++- docs/configuration/route/rule.zh.md | 2 +- docs/deprecated.md | 6 ++ docs/deprecated.zh.md | 6 ++ docs/migration.md | 1 - docs/migration.zh.md | 1 - experimental/deprecated/constants.go | 34 +++++++-- experimental/deprecated/stderr.go | 2 +- option/tun.go | 3 +- option/wireguard.go | 1 - protocol/tun/inbound.go | 18 +++-- protocol/wireguard/endpoint.go | 3 - protocol/wireguard/outbound.go | 4 +- transport/wireguard/device.go | 1 - transport/wireguard/device_system.go | 11 +-- transport/wireguard/endpoint.go | 1 - transport/wireguard/endpoint_options.go | 1 - 23 files changed, 177 insertions(+), 124 deletions(-) diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index e904f8cd02..bf0a03e23a 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -379,7 +379,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. !!! failure "已在 sing-box 1.10.0 废弃" - `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。 + `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。 使规则集中的 `ip_cidr` 规则匹配源 IP。 diff --git a/docs/configuration/endpoint/wireguard.md b/docs/configuration/endpoint/wireguard.md index 2d792e0279..65bb692910 100644 --- a/docs/configuration/endpoint/wireguard.md +++ b/docs/configuration/endpoint/wireguard.md @@ -14,7 +14,6 @@ icon: material/new-box "system": false, "name": "", "mtu": 1408, - "gso": false, "address": [], "private_key": "", "listen_port": 10000, @@ -36,6 +35,10 @@ icon: material/new-box } ``` +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + ### Fields #### system @@ -54,14 +57,6 @@ WireGuard MTU. `1408` will be used by default. -#### gso - -!!! quote "" - - Only supported on Linux. - -Try to enable generic segmentation offload. - #### address ==Required== diff --git a/docs/configuration/endpoint/wireguard.zh.md b/docs/configuration/endpoint/wireguard.zh.md index 8941b63095..918e7cbfe3 100644 --- a/docs/configuration/endpoint/wireguard.zh.md +++ b/docs/configuration/endpoint/wireguard.zh.md @@ -14,7 +14,6 @@ icon: material/new-box "system": false, "name": "", "mtu": 1408, - "gso": false, "address": [], "private_key": "", "listen_port": 10000, @@ -36,6 +35,10 @@ icon: material/new-box } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + ### 字段 #### system_interface @@ -54,14 +57,6 @@ WireGuard MTU。 默认使用 1408。 -#### gso - -!!! quote "" - - 仅支持 Linux。 - -尝试启用通用分段卸载。 - #### address ==必填== diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 51b8a10a3c..d82d451b2e 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -1,7 +1,11 @@ --- -icon: material/new-box +icon: material/alert-decagram --- +!!! quote "Changes in sing-box 1.11.0" + + :material-delete-alert: [gso](#gso) + !!! quote "Changes in sing-box 1.10.0" :material-plus: [address](#address) @@ -46,16 +50,7 @@ icon: material/new-box "172.18.0.1/30", "fdfe:dcba:9876::1/126" ], - // deprecated - "inet4_address": [ - "172.19.0.1/30" - ], - // deprecated - "inet6_address": [ - "fdfe:dcba:9876::1/126" - ], "mtu": 9000, - "gso": false, "auto_route": true, "iproute2_table_index": 2022, "iproute2_rule_index": 9000, @@ -69,28 +64,11 @@ icon: material/new-box "::/1", "8000::/1" ], - // deprecated - "inet4_route_address": [ - "0.0.0.0/1", - "128.0.0.0/1" - ], - // deprecated - "inet6_route_address": [ - "::/1", - "8000::/1" - ], + "route_exclude_address": [ "192.168.0.0/16", "fc00::/7" ], - // deprecated - "inet4_route_exclude_address": [ - "192.168.0.0/16" - ], - // deprecated - "inet6_route_exclude_address": [ - "fc00::/7" - ], "route_address_set": [ "geoip-cloudflare" ], @@ -137,8 +115,31 @@ icon: material/new-box "match_domain": [] } }, - ... - // Listen Fields + + // Deprecated + "gso": false, + "inet4_address": [ + "172.19.0.1/30" + ], + "inet6_address": [ + "fdfe:dcba:9876::1/126" + ], + "inet4_route_address": [ + "0.0.0.0/1", + "128.0.0.0/1" + ], + "inet6_route_address": [ + "::/1", + "8000::/1" + ], + "inet4_route_exclude_address": [ + "192.168.0.0/16" + ], + "inet6_route_exclude_address": [ + "fc00::/7" + ], + + ... // Listen Fields } ``` @@ -166,7 +167,7 @@ IPv4 and IPv6 prefix for the tun interface. !!! failure "Deprecated in sing-box 1.10.0" - `inet4_address` is merged to `address` and will be removed in sing-box 1.11.0. + `inet4_address` is merged to `address` and will be removed in sing-box 1.12.0. IPv4 prefix for the tun interface. @@ -174,7 +175,7 @@ IPv4 prefix for the tun interface. !!! failure "Deprecated in sing-box 1.10.0" - `inet6_address` is merged to `address` and will be removed in sing-box 1.11.0. + `inet6_address` is merged to `address` and will be removed in sing-box 1.12.0. IPv6 prefix for the tun interface. @@ -184,6 +185,10 @@ The maximum transmission unit. #### gso +!!! failure "Deprecated in sing-box 1.11.0" + + GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works, and will be removed in sing-box 1.12.0. + !!! question "Since sing-box 1.8.0" !!! quote "" @@ -284,7 +289,7 @@ Use custom routes instead of default when `auto_route` is enabled. !!! failure "Deprecated in sing-box 1.10.0" -`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) +`inet4_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address) instead. Use custom routes instead of default when `auto_route` is enabled. @@ -293,7 +298,7 @@ Use custom routes instead of default when `auto_route` is enabled. !!! failure "Deprecated in sing-box 1.10.0" -`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) +`inet6_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address) instead. Use custom routes instead of default when `auto_route` is enabled. @@ -308,7 +313,7 @@ Exclude custom routes when `auto_route` is enabled. !!! failure "Deprecated in sing-box 1.10.0" -`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please +`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_exclude_address](#route_exclude_address) instead. Exclude custom routes when `auto_route` is enabled. @@ -317,7 +322,7 @@ Exclude custom routes when `auto_route` is enabled. !!! failure "Deprecated in sing-box 1.10.0" -`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please +`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_exclude_address](#route_exclude_address) instead. Exclude custom routes when `auto_route` is enabled. diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 0b8a6aa127..2b5752ba44 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -1,8 +1,12 @@ --- -icon: material/new-box +icon: material/alert-decagram --- -!!! quote "Changes in sing-box 1.10.0" +!!! quote "sing-box 1.11.0 中的更改" + + :material-delete-alert: [gso](#gso) + +!!! quote "sing-box 1.10.0 中的更改" :material-plus: [address](#address) :material-delete-clock: [inet4_address](#inet4_address) @@ -46,16 +50,7 @@ icon: material/new-box "172.18.0.1/30", "fdfe:dcba:9876::1/126" ], - // 已弃用 - "inet4_address": [ - "172.19.0.1/30" - ], - // 已弃用 - "inet6_address": [ - "fdfe:dcba:9876::1/126" - ], "mtu": 9000, - "gso": false, "auto_route": true, "iproute2_table_index": 2022, "iproute2_rule_index": 9000, @@ -69,28 +64,11 @@ icon: material/new-box "::/1", "8000::/1" ], - // 已弃用 - "inet4_route_address": [ - "0.0.0.0/1", - "128.0.0.0/1" - ], - // 已弃用 - "inet6_route_address": [ - "::/1", - "8000::/1" - ], + "route_exclude_address": [ "192.168.0.0/16", "fc00::/7" ], - // 已弃用 - "inet4_route_exclude_address": [ - "192.168.0.0/16" - ], - // 已弃用 - "inet6_route_exclude_address": [ - "fc00::/7" - ], "route_address_set": [ "geoip-cloudflare" ], @@ -137,6 +115,29 @@ icon: material/new-box "match_domain": [] } }, + + // 已弃用 + "gso": false, + "inet4_address": [ + "172.19.0.1/30" + ], + "inet6_address": [ + "fdfe:dcba:9876::1/126" + ], + "inet4_route_address": [ + "0.0.0.0/1", + "128.0.0.0/1" + ], + "inet6_route_address": [ + "::/1", + "8000::/1" + ], + "inet4_route_exclude_address": [ + "192.168.0.0/16" + ], + "inet6_route_exclude_address": [ + "fc00::/7" + ], ... // 监听字段 } @@ -168,7 +169,7 @@ tun 接口的 IPv4 和 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。 + `inet4_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。 ==必填== @@ -178,7 +179,7 @@ tun 接口的 IPv4 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。 + `inet6_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。 tun 接口的 IPv6 前缀。 @@ -188,6 +189,10 @@ tun 接口的 IPv6 前缀。 #### gso +!!! failure "已在 sing-box 1.11.0 废弃" + + GSO 对于透明代理场景没有优势,已废弃和不再生效,且将在 sing-box 1.12.0 中被移除。 + !!! question "自 sing-box 1.8.0 起" !!! quote "" @@ -288,7 +293,7 @@ tun 接口的 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。 + `inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。 启用 `auto_route` 时使用自定义路由而不是默认路由。 @@ -296,7 +301,7 @@ tun 接口的 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。 + `inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。 启用 `auto_route` 时使用自定义路由而不是默认路由。 @@ -310,7 +315,7 @@ tun 接口的 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。 + `inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。 启用 `auto_route` 时排除自定义路由。 @@ -318,7 +323,7 @@ tun 接口的 IPv6 前缀。 !!! failure "已在 sing-box 1.10.0 废弃" - `inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。 + `inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。 启用 `auto_route` 时排除自定义路由。 diff --git a/docs/configuration/outbound/wireguard.md b/docs/configuration/outbound/wireguard.md index e3d2671a43..96c5dc7588 100644 --- a/docs/configuration/outbound/wireguard.md +++ b/docs/configuration/outbound/wireguard.md @@ -6,6 +6,10 @@ icon: material/delete-clock WireGuard outbound is deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-wireguard-outbound-to-endpoint). +!!! quote "Changes in sing-box 1.11.0" + + :material-delete-alert: [gso](#gso) + !!! quote "Changes in sing-box 1.8.0" :material-plus: [gso](#gso) @@ -20,7 +24,6 @@ icon: material/delete-clock "server": "127.0.0.1", "server_port": 1080, "system_interface": false, - "gso": false, "interface_name": "wg0", "local_address": [ "10.0.0.1/32" @@ -45,6 +48,10 @@ icon: material/delete-clock "mtu": 1408, "network": "tcp", + // Deprecated + + "gso": false, + ... // Dial Fields } ``` @@ -77,6 +84,10 @@ Custom interface name for system interface. #### gso +!!! failure "Deprecated in sing-box 1.11.0" + + GSO will be automatically enabled when available since sing-box 1.11.0. + !!! question "Since sing-box 1.8.0" !!! quote "" diff --git a/docs/configuration/outbound/wireguard.zh.md b/docs/configuration/outbound/wireguard.zh.md index 63f2ddfd7d..c4e77c24f8 100644 --- a/docs/configuration/outbound/wireguard.zh.md +++ b/docs/configuration/outbound/wireguard.zh.md @@ -6,6 +6,10 @@ icon: material/delete-clock WireGuard 出站已被启用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。 +!!! quote "sing-box 1.11.0 中的更改" + + :material-delete-alert: [gso](#gso) + !!! quote "sing-box 1.8.0 中的更改" :material-plus: [gso](#gso) @@ -20,7 +24,6 @@ icon: material/delete-clock "server": "127.0.0.1", "server_port": 1080, "system_interface": false, - "gso": false, "interface_name": "wg0", "local_address": [ "10.0.0.1/32" @@ -32,6 +35,10 @@ icon: material/delete-clock "workers": 4, "mtu": 1408, "network": "tcp", + + // 废弃的 + + "gso": false, ... // 拨号字段 } @@ -65,6 +72,10 @@ icon: material/delete-clock #### gso +!!! failure "已在 sing-box 1.11.0 废弃" + + 自 sing-box 1.11.0 起,GSO 将可用时自动启用。 + !!! question "自 sing-box 1.8.0 起" !!! quote "" diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index e5c5f01776..8deab2f332 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -388,7 +388,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. !!! failure "已在 sing-box 1.10.0 废弃" - `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。 + `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。 使规则集中的 `ip_cidr` 规则匹配源 IP。 diff --git a/docs/deprecated.md b/docs/deprecated.md index b72ee11e7d..1868d95205 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -35,6 +35,12 @@ check [Migration](../migration/#migrate-wireguard-outbound-to-endpoint). Old outbound will be removed in sing-box 1.13.0. +#### GSO option in TUN + +GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works in TUN. + +Old fields will be removed in sing-box 1.13.0. + ## 1.10.0 #### TUN address fields are merged diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 220725a9af..2cda6386c6 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -34,6 +34,12 @@ WireGuard 出站已废弃且可以通过端点替代, 旧出站将在 sing-box 1.13.0 中被移除。 +#### TUN 的 GSO 字段 + +GSO 对透明代理场景没有优势,已废弃且在 TUN 中不再起作用。 + +旧字段将在 sing-box 1.13.0 中被移除。 + ## 1.10.0 #### Match source 规则项已重命名 diff --git a/docs/migration.md b/docs/migration.md index 480c0d50ce..c8b876f7cf 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -242,7 +242,6 @@ WireGuard outbound is deprecated and can be replaced by endpoint. "system": true, "name": "wg0", "mtu": 1408, - "gso": true, "address": [ "10.0.0.2/32" ], diff --git a/docs/migration.zh.md b/docs/migration.zh.md index f03f63b095..32be560427 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -243,7 +243,6 @@ WireGuard 出站已被弃用,且可以被端点替代。 "system": true, "name": "wg0", "mtu": 1408, - "gso": true, "address": [ "10.0.0.2/32" ], diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 6eadea6a44..68aa9aca05 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -44,10 +44,17 @@ func (n Note) Message() string { } func (n Note) MessageWithLink() string { - return F.ToString( - n.Description, " is deprecated in sing-box ", n.DeprecatedVersion, - " and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink, - ) + if n.MigrationLink != "" { + return F.ToString( + n.Description, " is deprecated in sing-box ", n.DeprecatedVersion, + " and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink, + ) + } else { + return F.ToString( + n.Description, " is deprecated in sing-box ", n.DeprecatedVersion, + " and will be removed in sing-box ", n.ScheduledVersion, ".", + ) + } } var OptionBadMatchSource = Note{ @@ -122,6 +129,23 @@ var OptionWireGuardOutbound = Note{ 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", +} + var Options = []Note{ OptionBadMatchSource, OptionGEOIP, @@ -131,4 +155,6 @@ var Options = []Note{ OptionInboundOptions, OptionDestinationOverrideFields, OptionWireGuardOutbound, + OptionWireGuardGSO, + OptionTUNGSO, } diff --git a/experimental/deprecated/stderr.go b/experimental/deprecated/stderr.go index 0826baf94b..f29b4754f4 100644 --- a/experimental/deprecated/stderr.go +++ b/experimental/deprecated/stderr.go @@ -34,5 +34,5 @@ func (f *stderrManager) ReportDeprecated(feature Note) { return } f.logger.Error(feature.MessageWithLink()) - f.logger.Fatal("to continuing using this feature, set ENABLE_DEPRECATED_" + feature.EnvName + "=true") + f.logger.Fatal("to continuing using this feature, set environment variable ENABLE_DEPRECATED_" + feature.EnvName + "=true") } diff --git a/option/tun.go b/option/tun.go index d729e341a1..9263dd1671 100644 --- a/option/tun.go +++ b/option/tun.go @@ -13,7 +13,6 @@ import ( type TunInboundOptions struct { InterfaceName string `json:"interface_name,omitempty"` MTU uint32 `json:"mtu,omitempty"` - GSO bool `json:"gso,omitempty"` Address badoption.Listable[netip.Prefix] `json:"address,omitempty"` AutoRoute bool `json:"auto_route,omitempty"` IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"` @@ -40,6 +39,8 @@ type TunInboundOptions struct { Platform *TunPlatformOptions `json:"platform,omitempty"` InboundOptions + // Deprecated: removed + GSO bool `json:"gso,omitempty"` // Deprecated: merged to Address Inet4Address badoption.Listable[netip.Prefix] `json:"inet4_address,omitempty"` // Deprecated: merged to Address diff --git a/option/wireguard.go b/option/wireguard.go index 62ef332ae7..b9860d1199 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -10,7 +10,6 @@ type WireGuardEndpointOptions struct { System bool `json:"system,omitempty"` Name string `json:"name,omitempty"` MTU uint32 `json:"mtu,omitempty"` - GSO bool `json:"gso,omitempty"` Address badoption.Listable[netip.Prefix] `json:"address"` PrivateKey string `json:"private_key"` ListenPort uint16 `json:"listen_port,omitempty"` diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 6463f215ca..26158d7a56 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -62,14 +62,14 @@ type Inbound struct { func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) { address := options.Address var deprecatedAddressUsed bool + //nolint:staticcheck - //goland:noinspection GoDeprecation if len(options.Inet4Address) > 0 { address = append(address, options.Inet4Address...) deprecatedAddressUsed = true } + //nolint:staticcheck - //goland:noinspection GoDeprecation if len(options.Inet6Address) > 0 { address = append(address, options.Inet6Address...) deprecatedAddressUsed = true @@ -82,14 +82,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo }) routeAddress := options.RouteAddress + //nolint:staticcheck - //goland:noinspection GoDeprecation if len(options.Inet4RouteAddress) > 0 { routeAddress = append(routeAddress, options.Inet4RouteAddress...) deprecatedAddressUsed = true } + //nolint:staticcheck - //goland:noinspection GoDeprecation if len(options.Inet6RouteAddress) > 0 { routeAddress = append(routeAddress, options.Inet6RouteAddress...) deprecatedAddressUsed = true @@ -102,14 +102,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo }) routeExcludeAddress := options.RouteExcludeAddress + //nolint:staticcheck - //goland:noinspection GoDeprecation if len(options.Inet4RouteExcludeAddress) > 0 { routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...) deprecatedAddressUsed = true } + //nolint:staticcheck - //goland:noinspection GoDeprecation if len(options.Inet6RouteExcludeAddress) > 0 { routeExcludeAddress = append(routeExcludeAddress, options.Inet6RouteExcludeAddress...) deprecatedAddressUsed = true @@ -125,6 +125,11 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo deprecated.Report(ctx, deprecated.OptionTUNAddressX) } + //nolint:staticcheck + if options.GSO { + deprecated.Report(ctx, deprecated.OptionTUNGSO) + } + tunMTU := options.MTU if tunMTU == 0 { tunMTU = 9000 @@ -178,7 +183,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo tunOptions: tun.Options{ Name: options.InterfaceName, MTU: tunMTU, - GSO: options.GSO, Inet4Address: inet4Address, Inet6Address: inet6Address, AutoRoute: options.AutoRoute, diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 5465099c02..dc40b61330 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -51,8 +51,6 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL } if options.Detour == "" { options.IsWireGuardListener = true - } else if options.GSO { - return nil, E.New("gso is conflict with detour") } outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { @@ -72,7 +70,6 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL }, Name: options.Name, MTU: options.MTU, - GSO: options.GSO, Address: options.Address, PrivateKey: options.PrivateKey, ListenPort: options.ListenPort, diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 0831c2d7cf..a1fce79610 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -42,6 +42,9 @@ type Outbound struct { 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}, options.DialerOptions), ctx: ctx, @@ -89,7 +92,6 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL }, Name: options.InterfaceName, MTU: options.MTU, - GSO: options.GSO, Address: options.LocalAddress, PrivateKey: options.PrivateKey, ResolvePeer: func(domain string) (netip.Addr, error) { diff --git a/transport/wireguard/device.go b/transport/wireguard/device.go index d5d3b78151..7a17b8f35c 100644 --- a/transport/wireguard/device.go +++ b/transport/wireguard/device.go @@ -28,7 +28,6 @@ type DeviceOptions struct { CreateDialer func(interfaceName string) N.Dialer Name string MTU uint32 - GSO bool Address []netip.Prefix AllowedAddress []netip.Prefix } diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index 667443bab7..fa54f33293 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -12,7 +12,6 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" @@ -64,7 +63,7 @@ func (w *systemDevice) Start() error { return it.Addr().Is6() }), MTU: w.options.MTU, - GSO: w.options.GSO, + GSO: true, InterfaceScope: true, Inet4RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { return it.Addr().Is4() @@ -88,12 +87,8 @@ func (w *systemDevice) Start() error { } w.options.Logger.Info("started at ", w.options.Name) w.device = tunInterface - if w.options.GSO { - batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN) - if !isBatchTUN { - tunInterface.Close() - return E.New("GSO is not supported on current platform") - } + batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN) + if isBatchTUN { w.batchDevice = batchTUN } w.events <- wgTun.EventUp diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index bdac39e8ba..69ce917020 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -104,7 +104,6 @@ func NewEndpoint(options EndpointOptions) (*Endpoint, error) { CreateDialer: options.CreateDialer, Name: options.Name, MTU: options.MTU, - GSO: options.GSO, Address: options.Address, AllowedAddress: allowedAddresses, } diff --git a/transport/wireguard/endpoint_options.go b/transport/wireguard/endpoint_options.go index d44422e334..bb9a46e69f 100644 --- a/transport/wireguard/endpoint_options.go +++ b/transport/wireguard/endpoint_options.go @@ -21,7 +21,6 @@ type EndpointOptions struct { CreateDialer func(interfaceName string) N.Dialer Name string MTU uint32 - GSO bool Address []netip.Prefix PrivateKey string ListenPort uint16 From 1d2720bf5eb61d8d9885ab02577b01c04ac96598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 24 Nov 2024 14:45:40 +0800 Subject: [PATCH 0179/1272] Add UDP timeout route option --- adapter/inbound.go | 10 +- adapter/outbound/default.go | 157 --------------------- constant/protocol.go | 1 + constant/timeout.go | 17 ++- docs/configuration/route/rule_action.md | 25 +++- docs/configuration/route/rule_action.zh.md | 25 +++- option/rule_action.go | 5 +- option/wireguard.go | 2 +- protocol/dns/outbound.go | 15 +- protocol/group/selector.go | 59 ++++---- protocol/group/urltest.go | 22 ++- route/conn.go | 18 +++ route/route.go | 36 ++--- route/rule/rule_action.go | 2 + 14 files changed, 155 insertions(+), 239 deletions(-) delete mode 100644 adapter/outbound/default.go diff --git a/adapter/inbound.go b/adapter/inbound.go index fd9c5405d4..f5d5c95b8f 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -70,10 +70,12 @@ type InboundContext struct { InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool UDPConnect bool - NetworkStrategy C.NetworkStrategy - NetworkType []C.InterfaceType - FallbackNetworkType []C.InterfaceType - FallbackDelay time.Duration + UDPTimeout time.Duration + + NetworkStrategy C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType + FallbackDelay time.Duration DNSServer string diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go deleted file mode 100644 index 573673f287..0000000000 --- a/adapter/outbound/default.go +++ /dev/null @@ -1,157 +0,0 @@ -package outbound - -import ( - "context" - "net" - "net/netip" - "os" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/canceler" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error { - defer conn.Close() - ctx = adapter.WithContext(ctx, &metadata) - var outConn net.Conn - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) - } else { - outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - err = N.ReportConnHandshakeSuccess(conn, outConn) - if err != nil { - outConn.Close() - return err - } - return CopyEarlyConn(ctx, conn, outConn) -} - -func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error { - defer conn.Close() - ctx = adapter.WithContext(ctx, &metadata) - var ( - outPacketConn net.PacketConn - outConn net.Conn - destinationAddress netip.Addr - err error - ) - if metadata.UDPConnect { - if len(metadata.DestinationAddresses) > 0 { - if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) - } else { - outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) - } - } else { - outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - outPacketConn = bufio.NewUnbindPacketConn(outConn) - connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr()) - if connRemoteAddr != metadata.Destination.Addr { - destinationAddress = connRemoteAddr - } - } else { - if len(metadata.DestinationAddresses) > 0 { - outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) - } else { - outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - } - err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn) - if err != nil { - outPacketConn.Close() - return err - } - if destinationAddress.IsValid() { - var originDestination M.Socksaddr - if metadata.RouteOriginalDestination.IsValid() { - originDestination = metadata.RouteOriginalDestination - } else { - originDestination = metadata.Destination - } - if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) { - if metadata.UDPDisableDomainUnmapping { - outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination) - } else { - outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination) - } - } - if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { - natConn.UpdateDestination(destinationAddress) - } - } - switch metadata.Protocol { - case C.ProtocolSTUN: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout) - case C.ProtocolQUIC: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout) - case C.ProtocolDNS: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) - } - return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn)) -} - -func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error { - if cachedReader, isCached := conn.(N.CachedReader); isCached { - payload := cachedReader.ReadCached() - if payload != nil && !payload.IsEmpty() { - _, err := serverConn.Write(payload.Bytes()) - payload.Release() - if err != nil { - serverConn.Close() - return err - } - return bufio.CopyConn(ctx, conn, serverConn) - } - } - if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](serverConn); isEarlyConn && earlyConn.NeedHandshake() { - payload := buf.NewPacket() - err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout)) - if err != os.ErrInvalid { - if err != nil { - payload.Release() - serverConn.Close() - return err - } - _, err = payload.ReadOnceFrom(conn) - if err != nil && !E.IsTimeout(err) { - payload.Release() - serverConn.Close() - return E.Cause(err, "read payload") - } - err = conn.SetReadDeadline(time.Time{}) - if err != nil { - payload.Release() - serverConn.Close() - return err - } - } - _, err = serverConn.Write(payload.Bytes()) - payload.Release() - if err != nil { - serverConn.Close() - return N.ReportHandshakeFailure(conn, err) - } - } - return bufio.CopyConn(ctx, conn, serverConn) -} diff --git a/constant/protocol.go b/constant/protocol.go index 1485408945..dbe16e51c4 100644 --- a/constant/protocol.go +++ b/constant/protocol.go @@ -10,6 +10,7 @@ const ( ProtocolDTLS = "dtls" ProtocolSSH = "ssh" ProtocolRDP = "rdp" + ProtocolNTP = "ntp" ) const ( diff --git a/constant/timeout.go b/constant/timeout.go index 67ae6f6644..3b5a452bed 100644 --- a/constant/timeout.go +++ b/constant/timeout.go @@ -9,8 +9,6 @@ const ( TCPTimeout = 15 * time.Second ReadPayloadTimeout = 300 * time.Millisecond DNSTimeout = 10 * time.Second - QUICTimeout = 30 * time.Second - STUNTimeout = 15 * time.Second UDPTimeout = 5 * time.Minute DefaultURLTestInterval = 3 * time.Minute DefaultURLTestIdleTimeout = 30 * time.Minute @@ -19,3 +17,18 @@ const ( FatalStopTimeout = 10 * time.Second FakeIPMetadataSaveInterval = 10 * time.Second ) + +var PortProtocols = map[uint16]string{ + 53: ProtocolDNS, + 123: ProtocolNTP, + 3478: ProtocolSTUN, + 443: ProtocolQUIC, +} + +var ProtocolTimeouts = map[string]time.Duration{ + ProtocolDNS: 10 * time.Second, + ProtocolNTP: 10 * time.Second, + ProtocolSTUN: 10 * time.Second, + ProtocolQUIC: 30 * time.Second, + ProtocolDTLS: 30 * time.Second, +} diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 63e2b00b75..fae52e8540 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -41,7 +41,8 @@ See `route-options` fields below. "network_strategy": "", "fallback_delay": "", "udp_disable_domain_unmapping": false, - "udp_connect": false + "udp_connect": false, + "udp_timeout": "" } ``` @@ -86,6 +87,28 @@ do not support receiving UDP packets with domain addresses, such as Surge. If enabled, attempts to connect UDP connection to the destination instead of listen. +#### udp_timeout + +Timeout for UDP connections. + +Setting a larger value than the UDP timeout in inbounds will have no effect. + +Default value for protocol sniffed connections: + +| Timeout | Protocol | +|---------|----------------------| +| `10s` | `dns`, `ntp`, `stun` | +| `30s` | `quic`, `dtls` | + +If no protocol is sniffed, the following ports will be recognized as protocols by default: + +| Port | Protocol | +|------|----------| +| 53 | `dns` | +| 123 | `ntp` | +| 443 | `quic` | +| 3478 | `stun` | + ### reject ```json diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 7959fced45..2f558f4e46 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -37,7 +37,8 @@ icon: material/new-box "network_strategy": "", "fallback_delay": "", "udp_disable_domain_unmapping": false, - "udp_connect": false + "udp_connect": false, + "udp_timeout": "" } ``` @@ -84,6 +85,28 @@ icon: material/new-box 如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 +#### udp_timeout + +UDP 连接超时时间。 + +设置比入站 UDP 超时更大的值将无效。 + +已探测协议连接的默认值: + +| 超时 | 协议 | +|-------|----------------------| +| `10s` | `dns`, `ntp`, `stun` | +| `30s` | `quic`, `dtls` | + +如果没有探测到协议,以下端口将默认识别为协议: + +| 端口 | 协议 | +|------|--------| +| 53 | `dns` | +| 123 | `ntp` | +| 443 | `quic` | +| 3478 | `stun` | + ### reject ```json diff --git a/option/rule_action.go b/option/rule_action.go index ce3b92d9e9..29c5a0c30e 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -148,8 +148,9 @@ type RawRouteOptionsActionOptions struct { NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` FallbackDelay uint32 `json:"fallback_delay,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - UDPConnect bool `json:"udp_connect,omitempty"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` + UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` } type RouteOptionsActionOptions RawRouteOptionsActionOptions diff --git a/option/wireguard.go b/option/wireguard.go index b9860d1199..43d3139c5e 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -14,7 +14,7 @@ type WireGuardEndpointOptions struct { PrivateKey string `json:"private_key"` ListenPort uint16 `json:"listen_port,omitempty"` Peers []WireGuardPeer `json:"peers,omitempty"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` Workers int `json:"workers,omitempty"` DialerOptions } diff --git a/protocol/dns/outbound.go b/protocol/dns/outbound.go index 3c493f80e3..5f06557ba4 100644 --- a/protocol/dns/outbound.go +++ b/protocol/dns/outbound.go @@ -42,20 +42,21 @@ func (d *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return nil, os.ErrInvalid } -// Deprecated -func (d *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (d *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Destination = M.Socksaddr{} - defer conn.Close() for { conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) if err != nil { - return err + conn.Close() + if onClose != nil { + onClose(err) + } + return } } } -// Deprecated -func (d *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) +func (d *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) } diff --git a/protocol/group/selector.go b/protocol/group/selector.go index 0bb3cd6644..9806e03354 100644 --- a/protocol/group/selector.go +++ b/protocol/group/selector.go @@ -10,6 +10,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/common/atomic" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" @@ -21,17 +22,22 @@ func RegisterSelector(registry *outbound.Registry) { outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector) } -var _ adapter.OutboundGroup = (*Selector)(nil) +var ( + _ adapter.OutboundGroup = (*Selector)(nil) + _ adapter.ConnectionHandlerEx = (*Selector)(nil) + _ adapter.PacketConnectionHandlerEx = (*Selector)(nil) +) type Selector struct { outbound.Adapter ctx context.Context - outboundManager adapter.OutboundManager + outbound adapter.OutboundManager + connection adapter.ConnectionManager logger logger.ContextLogger tags []string defaultTag string outbounds map[string]adapter.Outbound - selected adapter.Outbound + selected atomic.TypedValue[adapter.Outbound] interruptGroup *interrupt.Group interruptExternalConnections bool } @@ -40,7 +46,8 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL outbound := &Selector{ Adapter: outbound.NewAdapter(C.TypeSelector, tag, nil, options.Outbounds), ctx: ctx, - outboundManager: service.FromContext[adapter.OutboundManager](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + connection: service.FromContext[adapter.ConnectionManager](ctx), logger: logger, tags: options.Outbounds, defaultTag: options.Default, @@ -55,15 +62,16 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL } func (s *Selector) Network() []string { - if s.selected == nil { + selected := s.selected.Load() + if selected == nil { return []string{N.NetworkTCP, N.NetworkUDP} } - return s.selected.Network() + return selected.Network() } func (s *Selector) Start() error { for i, tag := range s.tags { - detour, loaded := s.outboundManager.Outbound(tag) + detour, loaded := s.outbound.Outbound(tag) if !loaded { return E.New("outbound ", i, " not found: ", tag) } @@ -77,7 +85,7 @@ func (s *Selector) Start() error { if selected != "" { detour, loaded := s.outbounds[selected] if loaded { - s.selected = detour + s.selected.Store(detour) return nil } } @@ -89,16 +97,16 @@ func (s *Selector) Start() error { if !loaded { return E.New("default outbound not found: ", s.defaultTag) } - s.selected = detour + s.selected.Store(detour) return nil } - s.selected = s.outbounds[s.tags[0]] + s.selected.Store(s.outbounds[s.tags[0]]) return nil } func (s *Selector) Now() string { - selected := s.selected + selected := s.selected.Load() if selected == nil { return s.tags[0] } @@ -114,10 +122,9 @@ func (s *Selector) SelectOutbound(tag string) bool { if !loaded { return false } - if s.selected == detour { + if s.selected.Swap(detour) == detour { return true } - s.selected = detour if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { @@ -132,7 +139,7 @@ func (s *Selector) SelectOutbound(tag string) bool { } func (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - conn, err := s.selected.DialContext(ctx, network, destination) + conn, err := s.selected.Load().DialContext(ctx, network, destination) if err != nil { return nil, err } @@ -140,32 +147,30 @@ func (s *Selector) DialContext(ctx context.Context, network string, destination } func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - conn, err := s.selected.ListenPacket(ctx, destination) + conn, err := s.selected.Load().ListenPacket(ctx, destination) if err != nil { return nil, err } return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil } -// TODO -// Deprecated -func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) - if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { - return legacyHandler.NewConnection(ctx, conn, metadata) + selected := s.selected.Load() + if outboundHandler, isHandler := selected.(adapter.ConnectionHandlerEx); isHandler { + outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose) } else { - return outbound.NewConnection(ctx, s.selected, conn, metadata) + s.connection.NewConnection(ctx, selected, conn, metadata, onClose) } } -// TODO -// Deprecated -func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) - if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { - return legacyHandler.NewPacketConnection(ctx, conn, metadata) + selected := s.selected.Load() + if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandlerEx); isHandler { + outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose) } else { - return outbound.NewPacketConnection(ctx, s.selected, conn, metadata) + s.connection.NewPacketConnection(ctx, selected, conn, metadata, onClose) } } diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index fcada7dc3b..564c2373ea 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -36,7 +36,8 @@ type URLTest struct { outbound.Adapter ctx context.Context router adapter.Router - outboundManager adapter.OutboundManager + outbound adapter.OutboundManager + connection adapter.ConnectionManager logger log.ContextLogger tags []string link string @@ -52,7 +53,8 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo Adapter: outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds), ctx: ctx, router: router, - outboundManager: service.FromContext[adapter.OutboundManager](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + connection: service.FromContext[adapter.ConnectionManager](ctx), logger: logger, tags: options.Outbounds, link: options.URL, @@ -70,13 +72,13 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo func (s *URLTest) Start() error { outbounds := make([]adapter.Outbound, 0, len(s.tags)) for i, tag := range s.tags { - detour, loaded := s.outboundManager.Outbound(tag) + detour, loaded := s.outbound.Outbound(tag) if !loaded { return E.New("outbound ", i, " not found: ", tag) } outbounds = append(outbounds, detour) } - group, err := NewURLTestGroup(s.ctx, s.outboundManager, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections) + group, err := NewURLTestGroup(s.ctx, s.outbound, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections) if err != nil { return err } @@ -160,18 +162,14 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne return nil, err } -// TODO -// Deprecated -func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (s *URLTest) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return outbound.NewConnection(ctx, s, conn, metadata) + s.connection.NewConnection(ctx, s, conn, metadata, onClose) } -// TODO -// Deprecated -func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return outbound.NewPacketConnection(ctx, s, conn, metadata) + s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose) } func (s *URLTest) InterfaceUpdated() { diff --git a/route/conn.go b/route/conn.go index 594379cc93..4a2192e0da 100644 --- a/route/conn.go +++ b/route/conn.go @@ -6,11 +6,14 @@ import ( "net" "net/netip" "sync/atomic" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/canceler" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" @@ -208,6 +211,21 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial natConn.UpdateDestination(destinationAddress) } } + var udpTimeout time.Duration + if metadata.UDPTimeout > 0 { + udpTimeout = metadata.UDPTimeout + } else { + protocol := metadata.Protocol + if protocol == "" { + protocol = C.PortProtocols[metadata.Destination.Port] + } + if protocol != "" { + udpTimeout = C.ProtocolTimeouts[protocol] + } + } + if udpTimeout > 0 { + ctx, conn = canceler.NewPacketConn(ctx, conn, udpTimeout) + } destination := bufio.NewPacketConn(remotePacketConn) var done atomic.Bool if ctx.Done() != nil { diff --git a/route/route.go b/route/route.go index 67eb2c691b..05e22c2514 100644 --- a/route/route.go +++ b/route/route.go @@ -132,23 +132,11 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if r.tracker != nil { conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound) } - legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler) - if isLegacy { - err = legacyOutbound.NewConnection(ctx, conn, metadata) - if err != nil { - conn.Close() - if onClose != nil { - onClose(err) - } - return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) - } else { - if onClose != nil { - onClose(nil) - } - } - return nil + if outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandlerEx); isHandler { + outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose) + } else { + r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose) } - r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose) return nil } @@ -258,16 +246,11 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m if metadata.FakeIP { conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) } - legacyOutbound, isLegacy := selectedOutbound.(adapter.PacketConnectionHandler) - if isLegacy { - err = legacyOutbound.NewPacketConnection(ctx, conn, metadata) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]")) - } - return nil + if outboundHandler, isHandler := selectedOutbound.(adapter.PacketConnectionHandlerEx); isHandler { + outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose) + } else { + r.connection.NewPacketConnection(ctx, selectedOutbound, conn, metadata, onClose) } - r.connection.NewPacketConnection(ctx, selectedOutbound, conn, metadata, onClose) return nil } @@ -440,6 +423,9 @@ match: if routeOptions.UDPConnect { metadata.UDPConnect = true } + if routeOptions.UDPTimeout > 0 { + metadata.UDPTimeout = routeOptions.UDPTimeout + } } switch action := currentRule.Action().(type) { case *rule.RuleActionSniff: diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 1b4099c903..34354cc0d3 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -47,6 +47,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, UDPConnect: action.RouteOptionsOptions.UDPConnect, + UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout), }, nil case C.RuleActionTypeDirect: directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions)) @@ -152,6 +153,7 @@ type RuleActionRouteOptions struct { FallbackDelay time.Duration UDPDisableDomainUnmapping bool UDPConnect bool + UDPTimeout time.Duration } func (r *RuleActionRouteOptions) Type() string { From 83889178ed3c19324cb4210edbea2fce78e5688d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 27 Nov 2024 18:08:19 +0800 Subject: [PATCH 0180/1272] Improve timeouts --- protocol/wireguard/endpoint.go | 8 +- route/conn.go | 270 ++++++++++++--------------------- route/conn_monitor.go | 128 ---------------- route/conn_monitor_test.go | 43 ------ 4 files changed, 104 insertions(+), 345 deletions(-) delete mode 100644 route/conn_monitor.go delete mode 100644 route/conn_monitor_test.go diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index dc40b61330..937f84dd9a 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -56,12 +56,18 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ Context: ctx, Logger: logger, System: options.System, Handler: ep, - UDPTimeout: time.Duration(options.UDPTimeout), + UDPTimeout: udpTimeout, Dialer: outboundDialer, CreateDialer: func(interfaceName string) N.Dialer { return common.Must1(dialer.NewDefault(service.FromContext[adapter.NetworkManager](ctx), option.DialerOptions{ diff --git a/route/conn.go b/route/conn.go index 4a2192e0da..93ac33e359 100644 --- a/route/conn.go +++ b/route/conn.go @@ -5,6 +5,7 @@ import ( "io" "net" "net/netip" + "sync" "sync/atomic" "time" @@ -18,31 +19,35 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" ) var _ adapter.ConnectionManager = (*ConnectionManager)(nil) type ConnectionManager struct { - logger logger.ContextLogger - monitor *ConnectionMonitor + logger logger.ContextLogger + access sync.Mutex + connections list.List[io.Closer] } func NewConnectionManager(logger logger.ContextLogger) *ConnectionManager { return &ConnectionManager{ - logger: logger, - monitor: NewConnectionMonitor(), + logger: logger, } } func (m *ConnectionManager) Start(stage adapter.StartStage) error { - if stage != adapter.StartStateInitialize { - return nil - } - return m.monitor.Start() + return nil } func (m *ConnectionManager) Close() error { - return m.monitor.Close() + m.access.Lock() + defer m.access.Unlock() + for element := m.connections.Front(); element != nil; element = element.Next() { + common.Close(element.Value) + } + m.connections.Init() + return nil } func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { @@ -57,95 +62,32 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co remoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) } if err != nil { + err = E.Cause(err, "open outbound connection") N.CloseOnHandshakeFailure(conn, onClose, err) - m.logger.ErrorContext(ctx, "open outbound connection: ", err) + m.logger.ErrorContext(ctx, err) return } err = N.ReportConnHandshakeSuccess(conn, remoteConn) if err != nil { + err = E.Cause(err, "report handshake success") remoteConn.Close() N.CloseOnHandshakeFailure(conn, onClose, err) - m.logger.ErrorContext(ctx, "report handshake success: ", err) + m.logger.ErrorContext(ctx, err) return } + 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 ctx.Done() != nil { - onClose = N.AppendClose(onClose, m.monitor.Add(ctx, conn)) - } go m.connectionCopy(ctx, conn, remoteConn, false, &done, onClose) go m.connectionCopy(ctx, remoteConn, conn, true, &done, onClose) } -func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader, destination io.Writer, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { - originSource := source - originDestination := destination - var readCounters, writeCounters []N.CountFunc - for { - source, readCounters = N.UnwrapCountReader(source, readCounters) - destination, writeCounters = N.UnwrapCountWriter(destination, writeCounters) - if cachedSrc, isCached := source.(N.CachedReader); isCached { - cachedBuffer := cachedSrc.ReadCached() - if cachedBuffer != nil { - dataLen := cachedBuffer.Len() - _, err := destination.Write(cachedBuffer.Bytes()) - cachedBuffer.Release() - if err != nil { - m.logger.ErrorContext(ctx, "connection upload payload: ", err) - if done.Swap(true) { - if onClose != nil { - onClose(err) - } - } - common.Close(originSource, originDestination) - return - } - for _, counter := range readCounters { - counter(int64(dataLen)) - } - for _, counter := range writeCounters { - counter(int64(dataLen)) - } - } - continue - } - break - } - _, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters) - if err != nil { - common.Close(originSource, originDestination) - } else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex { - err = duplexDst.CloseWrite() - if err != nil { - common.Close(originSource, originDestination) - } - } else { - common.Close(originDestination) - } - if done.Swap(true) { - if onClose != nil { - onClose(err) - } - common.Close(originSource, originDestination) - } - if !direction { - if err == nil { - m.logger.DebugContext(ctx, "connection upload finished") - } else if !E.IsClosedOrCanceled(err) { - m.logger.ErrorContext(ctx, "connection upload closed: ", err) - } else { - m.logger.TraceContext(ctx, "connection upload closed") - } - } else { - if err == nil { - m.logger.DebugContext(ctx, "connection download finished") - } else if !E.IsClosedOrCanceled(err) { - m.logger.ErrorContext(ctx, "connection download closed: ", err) - } else { - m.logger.TraceContext(ctx, "connection download closed") - } - } -} - func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = adapter.WithContext(ctx, &metadata) var ( @@ -227,58 +169,91 @@ 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 - if ctx.Done() != nil { - onClose = N.AppendClose(onClose, m.monitor.Add(ctx, conn)) - } go m.packetConnectionCopy(ctx, conn, destination, false, &done, onClose) go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose) } -func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { - _, err := bufio.CopyPacket(destination, source) - /*var readCounters, writeCounters []N.CountFunc - var cachedPackets []*N.PacketBuffer +func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader, destination io.Writer, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { originSource := source + originDestination := destination + var readCounters, writeCounters []N.CountFunc for { - source, readCounters = N.UnwrapCountPacketReader(source, readCounters) - destination, writeCounters = N.UnwrapCountPacketWriter(destination, writeCounters) - if cachedReader, isCached := source.(N.CachedPacketReader); isCached { - packet := cachedReader.ReadCachedPacket() - if packet != nil { - cachedPackets = append(cachedPackets, packet) - continue + source, readCounters = N.UnwrapCountReader(source, readCounters) + destination, writeCounters = N.UnwrapCountWriter(destination, writeCounters) + if cachedSrc, isCached := source.(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(originSource, originDestination) + 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 } - var handled bool - if natConn, isNatConn := source.(udpnat.Conn); isNatConn { - natConn.SetHandler(&udpHijacker{ - ctx: ctx, - logger: m.logger, - source: natConn, - destination: destination, - direction: direction, - readCounters: readCounters, - writeCounters: writeCounters, - done: done, - onClose: onClose, - }) - handled = true - } - if cachedPackets != nil { - _, err := bufio.WritePacketWithPool(originSource, destination, cachedPackets, readCounters, writeCounters) + _, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters) + if err != nil { + common.Close(originDestination) + } else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex { + err = duplexDst.CloseWrite() if err != nil { - common.Close(source, destination) - m.logger.ErrorContext(ctx, "packet upload payload: ", err) - return + common.Close(originSource, originDestination) } + } else { + common.Close(originDestination) } - if handled { - return + if done.Swap(true) { + onClose(err) + common.Close(originSource, originDestination) + } + if !direction { + if err == nil { + m.logger.DebugContext(ctx, "connection upload finished") + } else if !E.IsClosedOrCanceled(err) { + m.logger.ErrorContext(ctx, "connection upload closed: ", err) + } else { + m.logger.TraceContext(ctx, "connection upload closed") + } + } else { + if err == nil { + m.logger.DebugContext(ctx, "connection download finished") + } else if !E.IsClosedOrCanceled(err) { + m.logger.ErrorContext(ctx, "connection download closed: ", err) + } else { + m.logger.TraceContext(ctx, "connection download closed") + } } - _, err := bufio.CopyPacketWithCounters(destination, source, originSource, readCounters, writeCounters)*/ +} + +func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { + _, err := bufio.CopyPacket(destination, source) if !direction { if E.IsClosedOrCanceled(err) { m.logger.TraceContext(ctx, "packet upload closed") @@ -293,58 +268,7 @@ func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.P } } if !done.Swap(true) { - if onClose != nil { - onClose(err) - } + onClose(err) } common.Close(source, destination) } - -/*type udpHijacker struct { - ctx context.Context - logger logger.ContextLogger - source io.Closer - destination N.PacketWriter - direction bool - readCounters []N.CountFunc - writeCounters []N.CountFunc - done *atomic.Bool - onClose N.CloseHandlerFunc -} - -func (u *udpHijacker) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - dataLen := buffer.Len() - for _, counter := range u.readCounters { - counter(int64(dataLen)) - } - err := u.destination.WritePacket(buffer, source) - if err != nil { - common.Close(u.source, u.destination) - u.logger.DebugContext(u.ctx, "packet upload closed: ", err) - return - } - for _, counter := range u.writeCounters { - counter(int64(dataLen)) - } -} - -func (u *udpHijacker) Close() error { - var err error - if !u.done.Swap(true) { - err = common.Close(u.source, u.destination) - if u.onClose != nil { - u.onClose(net.ErrClosed) - } - } - if u.direction { - u.logger.TraceContext(u.ctx, "packet download closed") - } else { - u.logger.TraceContext(u.ctx, "packet upload closed") - } - return err -} - -func (u *udpHijacker) Upstream() any { - return u.destination -} -*/ diff --git a/route/conn_monitor.go b/route/conn_monitor.go deleted file mode 100644 index 9e271b82a0..0000000000 --- a/route/conn_monitor.go +++ /dev/null @@ -1,128 +0,0 @@ -package route - -import ( - "context" - "io" - "reflect" - "sync" - "time" - - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/x/list" -) - -type ConnectionMonitor struct { - access sync.RWMutex - reloadChan chan struct{} - connections list.List[*monitorEntry] -} - -type monitorEntry struct { - ctx context.Context - closer io.Closer -} - -func NewConnectionMonitor() *ConnectionMonitor { - return &ConnectionMonitor{ - reloadChan: make(chan struct{}, 1), - } -} - -func (m *ConnectionMonitor) Add(ctx context.Context, closer io.Closer) N.CloseHandlerFunc { - m.access.Lock() - defer m.access.Unlock() - element := m.connections.PushBack(&monitorEntry{ - ctx: ctx, - closer: closer, - }) - select { - case <-m.reloadChan: - return nil - default: - select { - case m.reloadChan <- struct{}{}: - default: - } - } - return func(it error) { - m.access.Lock() - defer m.access.Unlock() - m.connections.Remove(element) - select { - case <-m.reloadChan: - default: - select { - case m.reloadChan <- struct{}{}: - default: - } - } - } -} - -func (m *ConnectionMonitor) Start() error { - go m.monitor() - return nil -} - -func (m *ConnectionMonitor) Close() error { - m.access.Lock() - defer m.access.Unlock() - close(m.reloadChan) - for element := m.connections.Front(); element != nil; element = element.Next() { - element.Value.closer.Close() - } - return nil -} - -func (m *ConnectionMonitor) monitor() { - var ( - selectCases []reflect.SelectCase - elements []*list.Element[*monitorEntry] - ) - rootCase := reflect.SelectCase{ - Dir: reflect.SelectRecv, - Chan: reflect.ValueOf(m.reloadChan), - } - for { - m.access.RLock() - if m.connections.Len() == 0 { - m.access.RUnlock() - if _, loaded := <-m.reloadChan; !loaded { - return - } else { - continue - } - } - if len(elements) < m.connections.Len() { - elements = make([]*list.Element[*monitorEntry], 0, m.connections.Len()) - } - if len(selectCases) < m.connections.Len()+1 { - selectCases = make([]reflect.SelectCase, 0, m.connections.Len()+1) - } - elements = elements[:0] - selectCases = selectCases[:1] - selectCases[0] = rootCase - for element := m.connections.Front(); element != nil; element = element.Next() { - elements = append(elements, element) - selectCases = append(selectCases, reflect.SelectCase{ - Dir: reflect.SelectRecv, - Chan: reflect.ValueOf(element.Value.ctx.Done()), - }) - } - m.access.RUnlock() - selected, _, loaded := reflect.Select(selectCases) - if selected == 0 { - if !loaded { - return - } else { - time.Sleep(time.Second) - continue - } - } - element := elements[selected-1] - m.access.Lock() - m.connections.Remove(element) - m.access.Unlock() - element.Value.closer.Close() // maybe go close - } -} diff --git a/route/conn_monitor_test.go b/route/conn_monitor_test.go deleted file mode 100644 index a712bddcdb..0000000000 --- a/route/conn_monitor_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package route_test - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/sagernet/sing-box/route" - - "github.com/stretchr/testify/require" -) - -func TestMonitor(t *testing.T) { - t.Parallel() - var closer myCloser - closer.Add(1) - monitor := route.NewConnectionMonitor() - require.NoError(t, monitor.Start()) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - monitor.Add(ctx, &closer) - done := make(chan struct{}) - go func() { - closer.Wait() - close(done) - }() - select { - case <-done: - case <-time.After(time.Second + 100*time.Millisecond): - t.Fatal("timeout") - } - cancel() - require.NoError(t, monitor.Close()) -} - -type myCloser struct { - sync.WaitGroup -} - -func (c *myCloser) Close() error { - c.Done() - return nil -} From 30704a15a77ee26a89ec39dec5028a6fd21525d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 28 Nov 2024 22:39:19 +0800 Subject: [PATCH 0181/1272] hysteria2: Add more masquerade options --- constant/hysteria2.go | 7 ++ docs/configuration/inbound/hysteria2.md | 58 ++++++++++++-- docs/configuration/inbound/hysteria2.zh.md | 58 ++++++++++++-- docs/configuration/outbound/direct.md | 4 +- option/hysteria2.go | 91 +++++++++++++++++++++- protocol/hysteria2/inbound.go | 36 ++++++--- 6 files changed, 229 insertions(+), 25 deletions(-) create mode 100644 constant/hysteria2.go diff --git a/constant/hysteria2.go b/constant/hysteria2.go new file mode 100644 index 0000000000..35c0b14f20 --- /dev/null +++ b/constant/hysteria2.go @@ -0,0 +1,7 @@ +package constant + +const ( + Hysterai2MasqueradeTypeFile = "file" + Hysterai2MasqueradeTypeProxy = "proxy" + Hysterai2MasqueradeTypeString = "string" +) diff --git a/docs/configuration/inbound/hysteria2.md b/docs/configuration/inbound/hysteria2.md index 7c611e6491..6230d3154b 100644 --- a/docs/configuration/inbound/hysteria2.md +++ b/docs/configuration/inbound/hysteria2.md @@ -1,11 +1,19 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-alert: [masquerade](#masquerade) + ### Structure ```json { "type": "hysteria2", "tag": "hy2-in", - ... - // Listen Fields + + ... // Listen Fields "up_mbps": 100, "down_mbps": 100, @@ -21,7 +29,7 @@ ], "ignore_client_bandwidth": false, "tls": {}, - "masquerade": "", + "masquerade": "", // or {} "brutal_debug": false } ``` @@ -79,14 +87,54 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound). #### masquerade -HTTP3 server behavior when authentication fails. +HTTP3 server behavior (URL string configuration) when authentication fails. | Scheme | Example | Description | |--------------|-------------------------|--------------------| | `file` | `file:///var/www` | As a file server | | `http/https` | `http://127.0.0.1:8080` | As a reverse proxy | -A 404 page will be returned if empty. +Conflict with `masquerade.type`. + +A 404 page will be returned if masquerade is not configured. + +#### masquerade.type + +HTTP3 server behavior (Object configuration) when authentication fails. + +| Type | Description | Fields | +|----------|-----------------------------|-------------------------------------| +| `file` | As a file server | `directory` | +| `proxy` | As a reverse proxy | `url`, `rewrite_host` | +| `string` | Reply with a fixed response | `status_code`, `headers`, `content` | + +Conflict with `masquerade`. + +A 404 page will be returned if masquerade is not configured. + +#### masquerade.directory + +File server root directory. + +#### masquerade.url + +Reverse proxy target URL. + +#### masquerade.rewrite_host + +Rewrite the `Host` header to the target URL. + +#### masquerade.status_code + +Fixed response status code. + +#### masquerade.headers + +Fixed response headers. + +#### masquerade.content + +Fixed response content. #### brutal_debug diff --git a/docs/configuration/inbound/hysteria2.zh.md b/docs/configuration/inbound/hysteria2.zh.md index c936aae8f8..fceacf9325 100644 --- a/docs/configuration/inbound/hysteria2.zh.md +++ b/docs/configuration/inbound/hysteria2.zh.md @@ -1,11 +1,19 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-alert: [masquerade](#masquerade) + ### 结构 ```json { "type": "hysteria2", "tag": "hy2-in", - ... - // 监听字段 + + ... // 监听字段 "up_mbps": 100, "down_mbps": 100, @@ -21,7 +29,7 @@ ], "ignore_client_bandwidth": false, "tls": {}, - "masquerade": "", + "masquerade": "", // 或 {} "brutal_debug": false } ``` @@ -76,14 +84,54 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 #### masquerade -HTTP3 服务器认证失败时的行为。 +HTTP3 服务器认证失败时的行为 (URL 字符串配置)。 | Scheme | 示例 | 描述 | |--------------|-------------------------|---------| | `file` | `file:///var/www` | 作为文件服务器 | | `http/https` | `http://127.0.0.1:8080` | 作为反向代理 | -如果为空,则返回 404 页。 +如果 masquerade 未配置,则返回 404 页。 + +与 `masquerade.type` 冲突。 + +#### masquerade.type + +HTTP3 服务器认证失败时的行为 (对象配置)。 + +| Type | 描述 | 字段 | +|----------|---------|-------------------------------------| +| `file` | 作为文件服务器 | `directory` | +| `proxy` | 作为反向代理 | `url`, `rewrite_host` | +| `string` | 返回固定响应 | `status_code`, `headers`, `content` | + +如果 masquerade 未配置,则返回 404 页。 + +与 `masquerade` 冲突。 + +#### masquerade.directory + +文件服务器根目录。 + +#### masquerade.url + +反向代理目标 URL。 + +#### masquerade.rewrite_host + +重写请求头中的 Host 字段到目标 URL。 + +#### masquerade.status_code + +固定响应状态码。 + +#### masquerade.headers + +固定响应头。 + +#### masquerade.content + +固定响应内容。 #### brutal_debug diff --git a/docs/configuration/outbound/direct.md b/docs/configuration/outbound/direct.md index 71649243a5..3e28db8fc6 100644 --- a/docs/configuration/outbound/direct.md +++ b/docs/configuration/outbound/direct.md @@ -4,8 +4,8 @@ icon: material/alert-decagram !!! quote "Changes in sing-box 1.11.0" - :material-alert-decagram: [override_address](#override_address) - :material-alert-decagram: [override_port](#override_port) + :material-delete-clock: [override_address](#override_address) + :material-delete-clock: [override_port](#override_port) `direct` outbound send requests directly. diff --git a/option/hysteria2.go b/option/hysteria2.go index 5032c734a3..8d5e114815 100644 --- a/option/hysteria2.go +++ b/option/hysteria2.go @@ -1,5 +1,15 @@ package option +import ( + "net/url" + + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + type Hysteria2InboundOptions struct { ListenOptions UpMbps int `json:"up_mbps,omitempty"` @@ -8,8 +18,8 @@ type Hysteria2InboundOptions struct { Users []Hysteria2User `json:"users,omitempty"` IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"` InboundTLSOptionsContainer - Masquerade string `json:"masquerade,omitempty"` - BrutalDebug bool `json:"brutal_debug,omitempty"` + Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"` + BrutalDebug bool `json:"brutal_debug,omitempty"` } type Hysteria2Obfs struct { @@ -22,6 +32,83 @@ type Hysteria2User struct { Password string `json:"password,omitempty"` } +type _Hysteria2Masquerade struct { + Type string `json:"type,omitempty"` + FileOptions Hysteria2MasqueradeFile `json:"-"` + ProxyOptions Hysteria2MasqueradeProxy `json:"-"` + StringOptions Hysteria2MasqueradeString `json:"-"` +} + +type Hysteria2Masquerade _Hysteria2Masquerade + +func (m Hysteria2Masquerade) MarshalJSON() ([]byte, error) { + var v any + switch m.Type { + case C.Hysterai2MasqueradeTypeFile: + v = m.FileOptions + case C.Hysterai2MasqueradeTypeProxy: + v = m.ProxyOptions + case C.Hysterai2MasqueradeTypeString: + v = m.StringOptions + default: + return nil, E.New("unknown masquerade type: ", m.Type) + } + return badjson.MarshallObjects((_Hysteria2Masquerade)(m), v) +} + +func (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error { + var urlString string + err := json.Unmarshal(bytes, &urlString) + if err == nil { + masqueradeURL, err := url.Parse(urlString) + if err != nil { + return E.Cause(err, "invalid masquerade URL") + } + switch masqueradeURL.Scheme { + case "file": + m.Type = C.Hysterai2MasqueradeTypeFile + m.FileOptions.Directory = masqueradeURL.Path + case "http", "https": + m.Type = C.Hysterai2MasqueradeTypeProxy + m.ProxyOptions.URL = urlString + default: + return E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) + } + return nil + } + err = json.Unmarshal(bytes, (*_Hysteria2Masquerade)(m)) + if err != nil { + return err + } + var v any + switch m.Type { + case C.Hysterai2MasqueradeTypeFile: + v = &m.FileOptions + case C.Hysterai2MasqueradeTypeProxy: + v = &m.ProxyOptions + case C.Hysterai2MasqueradeTypeString: + v = &m.StringOptions + default: + return E.New("unknown masquerade type: ", m.Type) + } + return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Masquerade)(m), v) +} + +type Hysteria2MasqueradeFile struct { + Directory string `json:"directory"` +} + +type Hysteria2MasqueradeProxy struct { + URL string `json:"url"` + RewriteHost bool `json:"rewrite_host,omitempty"` +} + +type Hysteria2MasqueradeString struct { + StatusCode int `json:"status_code,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + Content string `json:"content"` +} + type Hysteria2OutboundOptions struct { DialerOptions ServerOptions diff --git a/protocol/hysteria2/inbound.go b/protocol/hysteria2/inbound.go index 8d00072cc4..f55b6ae877 100644 --- a/protocol/hysteria2/inbound.go +++ b/protocol/hysteria2/inbound.go @@ -60,26 +60,40 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } } var masqueradeHandler http.Handler - if options.Masquerade != "" { - masqueradeURL, err := url.Parse(options.Masquerade) - if err != nil { - return nil, E.Cause(err, "parse masquerade URL") - } - switch masqueradeURL.Scheme { - case "file": - masqueradeHandler = http.FileServer(http.Dir(masqueradeURL.Path)) - case "http", "https": + if options.Masquerade != nil && options.Masquerade.Type != "" { + switch options.Masquerade.Type { + case C.Hysterai2MasqueradeTypeFile: + masqueradeHandler = http.FileServer(http.Dir(options.Masquerade.FileOptions.Directory)) + case C.Hysterai2MasqueradeTypeProxy: + masqueradeURL, err := url.Parse(options.Masquerade.ProxyOptions.URL) + if err != nil { + return nil, E.Cause(err, "parse masquerade URL") + } masqueradeHandler = &httputil.ReverseProxy{ Rewrite: func(r *httputil.ProxyRequest) { r.SetURL(masqueradeURL) - r.Out.Host = r.In.Host + if !options.Masquerade.ProxyOptions.RewriteHost { + r.Out.Host = r.In.Host + } }, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { w.WriteHeader(http.StatusBadGateway) }, } + case C.Hysterai2MasqueradeTypeString: + masqueradeHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if options.Masquerade.StringOptions.StatusCode != 0 { + w.WriteHeader(options.Masquerade.StringOptions.StatusCode) + } + for key, values := range options.Masquerade.StringOptions.Headers { + for _, value := range values { + w.Header().Add(key, value) + } + } + w.Write([]byte(options.Masquerade.StringOptions.Content)) + }) default: - return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) + return nil, E.New("unknown masquerade type: ", options.Masquerade.Type) } } inbound := &Inbound{ From 4d41f03bd5016a5ae69f85923262005d80f6f07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 6 Dec 2024 16:47:30 +0800 Subject: [PATCH 0182/1272] clash-api: Fix missing endpoints --- experimental/clashapi/api_meta_group.go | 6 ++-- experimental/clashapi/proxies.go | 11 ++++++-- experimental/clashapi/server.go | 34 ++++++++++++----------- experimental/clashapi/server_resources.go | 4 +-- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/experimental/clashapi/api_meta_group.go b/experimental/clashapi/api_meta_group.go index c5c07ba6a3..8f09ced9e3 100644 --- a/experimental/clashapi/api_meta_group.go +++ b/experimental/clashapi/api_meta_group.go @@ -32,7 +32,7 @@ func groupRouter(server *Server) http.Handler { func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - groups := common.Map(common.Filter(server.outboundManager.Outbounds(), func(it adapter.Outbound) bool { + groups := common.Map(common.Filter(server.outbound.Outbounds(), func(it adapter.Outbound) bool { _, isGroup := it.(adapter.OutboundGroup) return isGroup }), func(it adapter.Outbound) *badjson.JSONObject { @@ -86,7 +86,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) result, err = urlTestGroup.URLTest(ctx) } else { outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { - itOutbound, _ := server.outboundManager.Outbound(it) + itOutbound, _ := server.outbound.Outbound(it) return itOutbound })) b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10)) @@ -100,7 +100,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) continue } checked[realTag] = true - p, loaded := server.outboundManager.Outbound(realTag) + p, loaded := server.outbound.Outbound(realTag) if !loaded { continue } diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index 8d8ecb3845..6246b9da0d 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -46,7 +46,7 @@ func findProxyByName(server *Server) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { name := r.Context().Value(CtxKeyProxyName).(string) - proxy, exist := server.outboundManager.Outbound(name) + proxy, exist := server.outbound.Outbound(name) if !exist { render.Status(r, http.StatusNotFound) render.JSON(w, r, ErrNotFound) @@ -86,9 +86,14 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject { func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var proxyMap badjson.JSONObject - outbounds := common.Filter(server.outboundManager.Outbounds(), func(detour adapter.Outbound) bool { + outbounds := common.Filter(server.outbound.Outbounds(), func(detour adapter.Outbound) bool { return detour.Tag() != "" }) + outbounds = append(outbounds, common.Map(common.Filter(server.endpoint.Endpoints(), func(detour adapter.Endpoint) bool { + return detour.Tag() != "" + }), func(it adapter.Endpoint) adapter.Outbound { + return it + })...) allProxies := make([]string, 0, len(outbounds)) @@ -100,7 +105,7 @@ func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) { allProxies = append(allProxies, detour.Tag()) } - defaultTag := server.outboundManager.Default().Tag() + defaultTag := server.outbound.Default().Tag() sort.SliceStable(allProxies, func(i, j int) bool { return allProxies[i] == defaultTag diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 106bc73ff7..2b4da4a4a1 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -40,16 +40,17 @@ func init() { var _ adapter.ClashServer = (*Server)(nil) type Server struct { - ctx context.Context - router adapter.Router - outboundManager adapter.OutboundManager - logger log.Logger - httpServer *http.Server - trafficManager *trafficontrol.Manager - urlTestHistory *urltest.HistoryStorage - mode string - modeList []string - modeUpdateHook chan<- struct{} + ctx context.Context + router adapter.Router + outbound adapter.OutboundManager + endpoint adapter.EndpointManager + logger log.Logger + httpServer *http.Server + trafficManager *trafficontrol.Manager + urlTestHistory *urltest.HistoryStorage + mode string + modeList []string + modeUpdateHook chan<- struct{} externalController bool externalUI string @@ -61,10 +62,11 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op trafficManager := trafficontrol.NewManager() chiRouter := chi.NewRouter() s := &Server{ - ctx: ctx, - router: service.FromContext[adapter.Router](ctx), - outboundManager: service.FromContext[adapter.OutboundManager](ctx), - logger: logFactory.NewLogger("clash-api"), + ctx: ctx, + router: service.FromContext[adapter.Router](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + endpoint: service.FromContext[adapter.EndpointManager](ctx), + logger: logFactory.NewLogger("clash-api"), httpServer: &http.Server{ Addr: options.ExternalController, Handler: chiRouter, @@ -239,11 +241,11 @@ func (s *Server) TrafficManager() *trafficontrol.Manager { } func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn { - return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound) + return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound) } func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn { - return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound) + return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound) } func authentication(serverSecret string) func(next http.Handler) http.Handler { diff --git a/experimental/clashapi/server_resources.go b/experimental/clashapi/server_resources.go index e5b28e300c..2e73121f1a 100644 --- a/experimental/clashapi/server_resources.go +++ b/experimental/clashapi/server_resources.go @@ -44,13 +44,13 @@ func (s *Server) downloadExternalUI() error { s.logger.Info("downloading external ui") var detour adapter.Outbound if s.externalUIDownloadDetour != "" { - outbound, loaded := s.outboundManager.Outbound(s.externalUIDownloadDetour) + outbound, loaded := s.outbound.Outbound(s.externalUIDownloadDetour) if !loaded { return E.New("detour outbound not found: ", s.externalUIDownloadDetour) } detour = outbound } else { - outbound := s.outboundManager.Default() + outbound := s.outbound.Default() detour = outbound } httpClient := &http.Client{ From d5e7af7a7ea4f140a472c98f2ef8315ef740d538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 10 Dec 2024 20:36:09 +0800 Subject: [PATCH 0183/1272] Fix socks5 UDP implementation --- protocol/http/inbound.go | 2 +- protocol/mixed/inbound.go | 16 ++++++++++++---- protocol/socks/inbound.go | 14 +++++++++++--- protocol/tor/proxy.go | 2 +- route/route.go | 11 +++++++---- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go index 8aa1d4159a..68150fa741 100644 --- a/protocol/http/inbound.go +++ b/protocol/http/inbound.go @@ -91,7 +91,7 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a } conn = tlsConn } - err := http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) + err := http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) if err != nil { N.CloseOnHandshakeFailure(conn, onClose, err) h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go index d1d343a991..b675a5362e 100644 --- a/protocol/mixed/inbound.go +++ b/protocol/mixed/inbound.go @@ -85,9 +85,9 @@ 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, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) + return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) default: - return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) + return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) } } @@ -110,11 +110,19 @@ func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketC metadata.InboundType = h.Type() user, loaded := auth.UserFromContext[string](ctx) if !loaded { - h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + if !metadata.Destination.IsValid() { + h.logger.InfoContext(ctx, "inbound packet connection") + } else { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + } h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) return } metadata.User = user - h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + if !metadata.Destination.IsValid() { + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection") + } else { + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + } h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go index 1490cee5cf..820c7bbb5d 100644 --- a/protocol/socks/inbound.go +++ b/protocol/socks/inbound.go @@ -62,7 +62,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, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) + err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { if E.IsClosedOrCanceled(err) { @@ -92,11 +92,19 @@ func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketC metadata.InboundType = h.Type() user, loaded := auth.UserFromContext[string](ctx) if !loaded { - h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + if !metadata.Destination.IsValid() { + h.logger.InfoContext(ctx, "inbound packet connection") + } else { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + } h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) return } metadata.User = user - h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + if !metadata.Destination.IsValid() { + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection") + } else { + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + } h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } diff --git a/protocol/tor/proxy.go b/protocol/tor/proxy.go index 1ed303350b..feab7971c0 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, nil, l, M.SocksaddrFromNet(conn.RemoteAddr()), M.Socksaddr{}, nil) + return socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, l, M.SocksaddrFromNet(conn.RemoteAddr()), nil) } func (l *ProxyListener) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { diff --git a/route/route.go b/route/route.go index 05e22c2514..fb2de85d51 100644 --- a/route/route.go +++ b/route/route.go @@ -461,8 +461,12 @@ match: break match } } - if !preMatch && metadata.Destination.Addr.IsUnspecified() { - newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) + if !preMatch && inputPacketConn != nil && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() { + var timeout time.Duration + if metadata.InboundType == C.TypeSOCKS { + timeout = C.TCPTimeout + } + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: timeout}, inputConn, inputPacketConn) if newErr != nil { fatalErr = newErr return @@ -558,8 +562,7 @@ func (r *Router) actionSniff( return } } else { - // TODO: maybe always override destination - if metadata.Destination.Addr.IsUnspecified() { + if !metadata.Destination.Addr.IsGlobalUnicast() { metadata.Destination = destination } if len(packetBuffers) > 0 { From 906c21f45883df813dae315a98ce55c5eb91f4ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 14 Dec 2024 22:41:58 +0800 Subject: [PATCH 0184/1272] Fix time service --- box.go | 16 ++++++++++++---- common/tls/time_wrapper.go | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 common/tls/time_wrapper.go diff --git a/box.go b/box.go index 5dc76ebc7b..1908f6dbfe 100644 --- a/box.go +++ b/box.go @@ -14,6 +14,7 @@ import ( "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/taskmonitor" + "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/cachefile" @@ -149,6 +150,14 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "initialize router") } + + ntpOptions := common.PtrValueOrDefault(options.NTP) + var timeService *tls.TimeServiceWrapper + if ntpOptions.Enabled { + timeService = new(tls.TimeServiceWrapper) + service.MustRegister[ntp.TimeService](ctx, timeService) + } + for i, endpointOptions := range options.Endpoints { var tag string if endpointOptions.Tag != "" { @@ -254,13 +263,12 @@ func New(options Options) (*Box, error) { service.MustRegister[adapter.V2RayServer](ctx, v2rayServer) } } - ntpOptions := common.PtrValueOrDefault(options.NTP) if ntpOptions.Enabled { ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) if err != nil { return nil, E.Cause(err, "create NTP service") } - timeService := ntp.NewService(ntp.Options{ + ntpService := ntp.NewService(ntp.Options{ Context: ctx, Dialer: ntpDialer, Logger: logFactory.NewLogger("ntp"), @@ -268,8 +276,8 @@ func New(options Options) (*Box, error) { Interval: time.Duration(ntpOptions.Interval), WriteToSystem: ntpOptions.WriteToSystem, }) - service.MustRegister[ntp.TimeService](ctx, timeService) - services = append(services, adapter.NewLifecycleService(timeService, "ntp service")) + timeService.TimeService = ntpService + services = append(services, adapter.NewLifecycleService(ntpService, "ntp service")) } return &Box{ network: networkManager, diff --git a/common/tls/time_wrapper.go b/common/tls/time_wrapper.go new file mode 100644 index 0000000000..491fca9890 --- /dev/null +++ b/common/tls/time_wrapper.go @@ -0,0 +1,22 @@ +package tls + +import ( + "time" + + "github.com/sagernet/sing/common/ntp" +) + +type TimeServiceWrapper struct { + ntp.TimeService +} + +func (w *TimeServiceWrapper) TimeFunc() func() time.Time { + if w.TimeService == nil { + return nil + } + return w.TimeService.TimeFunc() +} + +func (w *TimeServiceWrapper) Upstream() any { + return w.TimeService +} From 9a1efbe54d96993fd5e8750c615f8a52a4c05e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Dec 2024 00:45:41 +0800 Subject: [PATCH 0185/1272] Fix domain strategy --- adapter/inbound.go | 2 +- adapter/network.go | 2 +- common/dialer/default.go | 98 +++++++++++++-------- common/dialer/default_parallel_interface.go | 55 +++++++----- common/dialer/default_parallel_network.go | 25 +++++- common/dialer/dialer.go | 21 ++--- common/dialer/resolve.go | 4 +- option/outbound.go | 37 ++++---- option/route.go | 2 +- option/rule_action.go | 4 +- protocol/direct/outbound.go | 36 +++----- protocol/wireguard/endpoint.go | 3 +- protocol/wireguard/outbound.go | 3 +- route/conn.go | 11 ++- route/network.go | 4 +- route/route.go | 14 ++- route/router.go | 2 +- route/rule/rule_action.go | 6 +- transport/dhcp/server.go | 2 +- 19 files changed, 195 insertions(+), 136 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index f5d5c95b8f..93d2ec60c5 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -72,7 +72,7 @@ type InboundContext struct { UDPConnect bool UDPTimeout time.Duration - NetworkStrategy C.NetworkStrategy + NetworkStrategy *C.NetworkStrategy NetworkType []C.InterfaceType FallbackNetworkType []C.InterfaceType FallbackDelay time.Duration diff --git a/adapter/network.go b/adapter/network.go index 08fc00fac0..00ef54b8c0 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -28,7 +28,7 @@ type NetworkManager interface { } type NetworkOptions struct { - NetworkStrategy C.NetworkStrategy + NetworkStrategy *C.NetworkStrategy NetworkType []C.InterfaceType FallbackNetworkType []C.InterfaceType FallbackDelay time.Duration diff --git a/common/dialer/default.go b/common/dialer/default.go index bf553618f7..49bd145cd6 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/conntrack" 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/atomic" @@ -16,6 +17,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/service" ) var ( @@ -33,19 +35,22 @@ type DefaultDialer struct { udpAddr6 string isWireGuardListener bool networkManager adapter.NetworkManager - networkStrategy C.NetworkStrategy + networkStrategy *C.NetworkStrategy networkType []C.InterfaceType fallbackNetworkType []C.InterfaceType networkFallbackDelay time.Duration networkLastFallback atomic.TypedValue[time.Time] } -func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) { +func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { + networkManager := service.FromContext[adapter.NetworkManager](ctx) + platformInterface := service.FromContext[platform.Interface](ctx) + var ( dialer net.Dialer listener net.ListenConfig interfaceFinder control.InterfaceFinder - networkStrategy C.NetworkStrategy + networkStrategy *C.NetworkStrategy networkType []C.InterfaceType fallbackNetworkType []C.InterfaceType networkFallbackDelay time.Duration @@ -74,31 +79,37 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark)) } } - if C.NetworkStrategy(options.NetworkStrategy) != C.NetworkStrategyDefault { - if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil { - return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`") - } - networkStrategy = C.NetworkStrategy(options.NetworkStrategy) - networkType = common.Map(options.NetworkType, option.InterfaceType.Build) - fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build) - networkFallbackDelay = time.Duration(options.NetworkFallbackDelay) - if networkManager == nil || !networkManager.AutoDetectInterface() { - return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") + disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil + if disableDefaultBind || options.TCPFastOpen { + if options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 { + return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`") } } - if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil { + + if networkManager != nil { defaultOptions := networkManager.DefaultOptions() - if options.BindInterface == "" { + if !disableDefaultBind { if defaultOptions.BindInterface != "" { bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) } else if networkManager.AutoDetectInterface() { - if defaultOptions.NetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault { - networkStrategy = defaultOptions.NetworkStrategy - networkType = defaultOptions.NetworkType - fallbackNetworkType = defaultOptions.FallbackNetworkType - networkFallbackDelay = defaultOptions.FallbackDelay + if platformInterface != nil { + networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy) + if networkStrategy == nil { + networkStrategy = common.Ptr(C.NetworkStrategyDefault) + } + networkType = common.Map(options.NetworkType, option.InterfaceType.Build) + fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build) + if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 { + networkStrategy = defaultOptions.NetworkStrategy + networkType = defaultOptions.NetworkType + fallbackNetworkType = defaultOptions.FallbackNetworkType + } + networkFallbackDelay = time.Duration(options.FallbackDelay) + if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 { + networkFallbackDelay = defaultOptions.FallbackDelay + } bindFunc := networkManager.ProtectFunc() dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) @@ -172,9 +183,6 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti listener.Control = control.Append(listener.Control, controlFn) } } - if networkStrategy != C.NetworkStrategyDefault && options.TCPFastOpen { - return nil, E.New("`tcp_fast_open` is conflict with `network_strategy` or `route.default_network_strategy`") - } tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen) if err != nil { return nil, err @@ -204,7 +212,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address if !address.IsValid() { return nil, E.New("invalid address") } - if d.networkStrategy == C.NetworkStrategyDefault { + if d.networkStrategy == nil { switch N.NetworkName(network) { case N.NetworkUDP: if !address.IsIPv6() { @@ -223,12 +231,21 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address } } -func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { - if strategy == C.NetworkStrategyDefault { +func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + if strategy == nil { + strategy = d.networkStrategy + } + if strategy == nil { return d.DialContext(ctx, network, address) } - if !d.networkManager.AutoDetectInterface() { - return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") + if len(interfaceType) == 0 { + interfaceType = d.networkType + } + if len(fallbackInterfaceType) == 0 { + fallbackInterfaceType = d.fallbackNetworkType + } + if fallbackDelay == 0 { + fallbackDelay = d.networkFallbackDelay } var dialer net.Dialer if N.NetworkName(network) == N.NetworkTCP { @@ -243,9 +260,9 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin err error ) if !fastFallback { - conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } else { - conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store) + conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store) } if err != nil { return nil, err @@ -257,7 +274,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin } func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - if d.networkStrategy == C.NetworkStrategyDefault { + if d.networkStrategy == nil { if destination.IsIPv6() { return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)) } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { @@ -270,18 +287,27 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd } } -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 == C.NetworkStrategyDefault { +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 + } + if strategy == nil { return d.ListenPacket(ctx, destination) } - if !d.networkManager.AutoDetectInterface() { - return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") + if len(interfaceType) == 0 { + interfaceType = d.networkType + } + if len(fallbackInterfaceType) == 0 { + fallbackInterfaceType = d.fallbackNetworkType + } + if fallbackDelay == 0 { + fallbackDelay = d.networkFallbackDelay } network := N.NetworkUDP if destination.IsIPv4() && !destination.Addr.IsUnspecified() { network += "4" } - return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, interfaceType, fallbackInterfaceType, fallbackDelay)) + return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)) } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go index 37a1f79cab..269546a413 100644 --- a/common/dialer/default_parallel_interface.go +++ b/common/dialer/default_parallel_interface.go @@ -40,7 +40,7 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di } } else { select { - case results <- dialResult{Conn: conn}: + case results <- dialResult{Conn: conn, primary: primary}: case <-returned: conn.Close() } @@ -112,7 +112,7 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d } } else { select { - case results <- dialResult{Conn: conn}: + case results <- dialResult{Conn: conn, primary: primary}: case <-returned: if primary && time.Since(startAt) <= fallbackDelay { resetFastFallback(time.Time{}) @@ -177,44 +177,57 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS case C.NetworkStrategyDefault: if len(interfaceType) == 0 { defaultIf := networkManager.InterfaceMonitor().DefaultInterface() - for _, iif := range interfaces { - if iif.Index == defaultIf.Index { - primaryInterfaces = append(primaryInterfaces, iif) - } else { - fallbackInterfaces = append(fallbackInterfaces, iif) + if defaultIf != nil { + for _, iif := range interfaces { + if iif.Index == defaultIf.Index { + primaryInterfaces = append(primaryInterfaces, iif) + } } + } else { + primaryInterfaces = interfaces } } else { - primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { - return common.Contains(interfaceType, iif.Type) + primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return common.Contains(interfaceType, it.Type) }) } case C.NetworkStrategyHybrid: if len(interfaceType) == 0 { primaryInterfaces = interfaces } else { - primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { - return common.Contains(interfaceType, iif.Type) + primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return common.Contains(interfaceType, it.Type) }) } case C.NetworkStrategyFallback: if len(interfaceType) == 0 { defaultIf := networkManager.InterfaceMonitor().DefaultInterface() - for _, iif := range interfaces { - if iif.Index == defaultIf.Index { - primaryInterfaces = append(primaryInterfaces, iif) - } else { - fallbackInterfaces = append(fallbackInterfaces, iif) + if defaultIf != nil { + for _, iif := range interfaces { + if iif.Index == defaultIf.Index { + primaryInterfaces = append(primaryInterfaces, iif) + break + } } + } else { + primaryInterfaces = interfaces } } else { - primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { - return common.Contains(interfaceType, iif.Type) + primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return common.Contains(interfaceType, it.Type) + }) + } + if len(fallbackInterfaceType) == 0 { + fallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool { + return it.Index == iif.Index + }) + }) + } else { + fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(fallbackInterfaceType, iif.Type) }) } - fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { - return common.Contains(fallbackInterfaceType, iif.Type) - }) } return primaryInterfaces, fallbackInterfaces } diff --git a/common/dialer/default_parallel_network.go b/common/dialer/default_parallel_network.go index 5145656ba8..006e574759 100644 --- a/common/dialer/default_parallel_network.go +++ b/common/dialer/default_parallel_network.go @@ -13,7 +13,13 @@ import ( N "github.com/sagernet/sing/common/network" ) -func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { +func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + if len(destinationAddresses) == 0 { + if !destination.IsIP() { + panic("invalid usage") + } + destinationAddresses = []netip.Addr{destination.Addr} + } if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } @@ -38,7 +44,14 @@ func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, des return nil, E.Errors(errors...) } -func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { +func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + if len(destinationAddresses) == 0 { + if !destination.IsIP() { + panic("invalid usage") + } + destinationAddresses = []netip.Addr{destination.Addr} + } + if fallbackDelay == 0 { fallbackDelay = N.DefaultFallbackDelay } @@ -116,7 +129,13 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne } } -func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { +func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { + if len(destinationAddresses) == 0 { + if !destination.IsIP() { + panic("invalid usage") + } + destinationAddresses = []netip.Addr{destination.Addr} + } if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index b307a3303b..89d1eeaba9 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -17,16 +17,15 @@ import ( ) func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { - networkManager := service.FromContext[adapter.NetworkManager](ctx) if options.IsWireGuardListener { - return NewDefault(networkManager, options) + return NewDefault(ctx, options) } var ( dialer N.Dialer err error ) if options.Detour == "" { - dialer, err = NewDefault(networkManager, options) + dialer, err = NewDefault(ctx, options) if err != nil { return nil, err } @@ -37,9 +36,6 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { } dialer = NewDetour(outboundManager, options.Detour) } - if networkManager == nil { - return NewDefault(networkManager, options) - } if options.Detour == "" { router := service.FromContext[adapter.Router](ctx) if router != nil { @@ -58,11 +54,10 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter if options.Detour != "" { return nil, E.New("`detour` is not supported in direct context") } - networkManager := service.FromContext[adapter.NetworkManager](ctx) if options.IsWireGuardListener { - return NewDefault(networkManager, options) + return NewDefault(ctx, options) } - dialer, err := NewDefault(networkManager, options) + dialer, err := NewDefault(ctx, options) if err != nil { return nil, err } @@ -77,11 +72,11 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter type ParallelInterfaceDialer interface { N.Dialer - DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) - ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) + DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) } 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) + 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) } diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index b5d922b375..ede1afd614 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -106,7 +106,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } -func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { +func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } @@ -134,7 +134,7 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context } } -func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { +func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } diff --git a/option/outbound.go b/option/outbound.go index 833a2d2030..5cadd3e2ae 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -65,25 +65,24 @@ 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"` - 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:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,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"` - NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` + 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"` + 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:"-"` + DomainStrategy DomainStrategy `json:"domain_strategy,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"` + IsWireGuardListener bool `json:"-"` } func (o *DialerOptions) TakeDialerOptions() DialerOptions { diff --git a/option/route.go b/option/route.go index 0eb1cbf1b2..1eb2294bfe 100644 --- a/option/route.go +++ b/option/route.go @@ -13,7 +13,7 @@ type RouteOptions struct { OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` DefaultInterface string `json:"default_interface,omitempty"` DefaultMark FwMark `json:"default_mark,omitempty"` - DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"` + DefaultNetworkStrategy *NetworkStrategy `json:"default_network_strategy,omitempty"` DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"` DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"` DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"` diff --git a/option/rule_action.go b/option/rule_action.go index 29c5a0c30e..b700362824 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -145,8 +145,8 @@ type RawRouteOptionsActionOptions struct { OverrideAddress string `json:"override_address,omitempty"` OverridePort uint16 `json:"override_port,omitempty"` - NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` - FallbackDelay uint32 `json:"fallback_delay,omitempty"` + NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay uint32 `json:"fallback_delay,omitempty"` UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` UDPConnect bool `json:"udp_connect,omitempty"` diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 42fd284de5..aba5633621 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -32,16 +32,12 @@ var ( type Outbound struct { outbound.Adapter - logger logger.ContextLogger - dialer dialer.ParallelInterfaceDialer - domainStrategy dns.DomainStrategy - fallbackDelay time.Duration - networkStrategy C.NetworkStrategy - networkType []C.InterfaceType - fallbackNetworkType []C.InterfaceType - networkFallbackDelay time.Duration - overrideOption int - overrideDestination M.Socksaddr + logger logger.ContextLogger + dialer dialer.ParallelInterfaceDialer + domainStrategy dns.DomainStrategy + fallbackDelay time.Duration + overrideOption int + overrideDestination M.Socksaddr // loopBack *loopBackDetector } @@ -52,15 +48,11 @@ 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), - logger: logger, - domainStrategy: dns.DomainStrategy(options.DomainStrategy), - fallbackDelay: time.Duration(options.FallbackDelay), - networkStrategy: C.NetworkStrategy(options.NetworkStrategy), - networkType: common.Map(options.NetworkType, option.InterfaceType.Build), - fallbackNetworkType: common.Map(options.FallbackNetworkType, option.InterfaceType.Build), - networkFallbackDelay: time.Duration(options.NetworkFallbackDelay), - dialer: outboundDialer, + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + logger: logger, + domainStrategy: dns.DomainStrategy(options.DomainStrategy), + fallbackDelay: time.Duration(options.FallbackDelay), + dialer: outboundDialer, // loopBack: newLoopBackDetector(router), } //nolint:staticcheck @@ -178,10 +170,10 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination return nil, E.New("no IPv6 address available for ", destination) } } - return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.networkType, h.fallbackNetworkType, h.fallbackDelay) + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, nil, nil, nil, h.fallbackDelay) } -func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { +func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination @@ -221,7 +213,7 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay) } -func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { +func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 937f84dd9a..21d72bd982 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -20,7 +20,6 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/service" ) func RegisterEndpoint(registry *endpoint.Registry) { @@ -70,7 +69,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL UDPTimeout: udpTimeout, Dialer: outboundDialer, CreateDialer: func(interfaceName string) N.Dialer { - return common.Must1(dialer.NewDefault(service.FromContext[adapter.NetworkManager](ctx), option.DialerOptions{ + return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{ BindInterface: interfaceName, })) }, diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index a1fce79610..3e29970535 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -19,7 +19,6 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/service" ) func RegisterOutbound(registry *outbound.Registry) { @@ -86,7 +85,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL System: options.SystemInterface, Dialer: outboundDialer, CreateDialer: func(interfaceName string) N.Dialer { - return common.Must1(dialer.NewDefault(service.FromContext[adapter.NetworkManager](ctx), option.DialerOptions{ + return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{ BindInterface: interfaceName, })) }, diff --git a/route/conn.go b/route/conn.go index 93ac33e359..e010c2cdb6 100644 --- a/route/conn.go +++ b/route/conn.go @@ -56,7 +56,7 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co remoteConn net.Conn err error ) - if len(metadata.DestinationAddresses) > 0 { + if len(metadata.DestinationAddresses) > 0 || metadata.Destination.IsIP() { remoteConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { remoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) @@ -97,12 +97,19 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial err error ) if metadata.UDPConnect { + parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer) if len(metadata.DestinationAddresses) > 0 { - if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { + if isParallelDialer { remoteConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { remoteConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) } + } else if metadata.Destination.IsIP() { + if isParallelDialer { + remoteConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) + } else { + remoteConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) + } } else { remoteConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) } diff --git a/route/network.go b/route/network.go index d11508b125..97d165f119 100644 --- a/route/network.go +++ b/route/network.go @@ -62,7 +62,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp defaultOptions: adapter.NetworkOptions{ BindInterface: routeOptions.DefaultInterface, RoutingMark: uint32(routeOptions.DefaultMark), - NetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy), + 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), @@ -73,7 +73,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp inbound: service.FromContext[adapter.InboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx), } - if C.NetworkStrategy(routeOptions.DefaultNetworkStrategy) != C.NetworkStrategyDefault { + if routeOptions.DefaultNetworkStrategy != nil { if routeOptions.DefaultInterface != "" { return nil, E.New("`default_network_strategy` is conflict with `default_interface`") } diff --git a/route/route.go b/route/route.go index fb2de85d51..ca7474623b 100644 --- a/route/route.go +++ b/route/route.go @@ -415,8 +415,18 @@ match: Fqdn: metadata.Destination.Fqdn, } } - metadata.NetworkStrategy = routeOptions.NetworkStrategy - metadata.FallbackDelay = routeOptions.FallbackDelay + if routeOptions.NetworkStrategy != nil { + metadata.NetworkStrategy = routeOptions.NetworkStrategy + } + if len(routeOptions.NetworkType) > 0 { + metadata.NetworkType = routeOptions.NetworkType + } + if len(routeOptions.FallbackNetworkType) > 0 { + metadata.FallbackNetworkType = routeOptions.FallbackNetworkType + } + if routeOptions.FallbackDelay != 0 { + metadata.FallbackDelay = routeOptions.FallbackDelay + } if routeOptions.UDPDisableDomainUnmapping { metadata.UDPDisableDomainUnmapping = true } diff --git a/route/router.go b/route/router.go index 792391e265..6526778bea 100644 --- a/route/router.go +++ b/route/router.go @@ -262,7 +262,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route Context: ctx, Name: "local", Address: "local", - Dialer: common.Must1(dialer.NewDefault(router.network, option.DialerOptions{})), + Dialer: common.Must1(dialer.NewDefault(ctx, option.DialerOptions{})), }))) } defaultTransport = transports[0] diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 34354cc0d3..f4f2299ae7 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -33,7 +33,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti RuleActionRouteOptions: RuleActionRouteOptions{ OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0), OverridePort: action.RouteOptions.OverridePort, - NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy), + NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptions.NetworkStrategy), FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, UDPConnect: action.RouteOptions.UDPConnect, @@ -43,7 +43,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti return &RuleActionRouteOptions{ OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptionsOptions.OverrideAddress, 0), OverridePort: action.RouteOptionsOptions.OverridePort, - NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy), + NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptionsOptions.NetworkStrategy), FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, UDPConnect: action.RouteOptionsOptions.UDPConnect, @@ -147,7 +147,7 @@ func (r *RuleActionRoute) String() string { type RuleActionRouteOptions struct { OverrideAddress M.Socksaddr OverridePort uint16 - NetworkStrategy C.NetworkStrategy + NetworkStrategy *C.NetworkStrategy NetworkType []C.InterfaceType FallbackNetworkType []C.InterfaceType FallbackDelay time.Duration diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index 29c6bbe022..8b9187f0fb 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -253,7 +253,7 @@ func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []neti return it.String() }), ","), "]") } - serverDialer := common.Must1(dialer.NewDefault(t.networkManager, option.DialerOptions{ + serverDialer := common.Must1(dialer.NewDefault(t.options.Context, option.DialerOptions{ BindInterface: iface.Name, UDPFragmentDefault: true, })) From ff7aaf977b2796ad95e8f44889f83a98e9364ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Dec 2024 21:27:39 +0800 Subject: [PATCH 0186/1272] Fix DNS match --- route/route_dns.go | 119 +++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 64 deletions(-) diff --git a/route/route_dns.go b/route/route_dns.go index 696e167668..3d7dc64faf 100644 --- a/route/route_dns.go +++ b/route/route_dns.go @@ -45,69 +45,64 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, panic("no context") } var options dns.QueryOptions - if ruleIndex < len(r.dnsRules) { - dnsRules := r.dnsRules - if ruleIndex != -1 { - dnsRules = dnsRules[ruleIndex+1:] + var currentRuleIndex int + if ruleIndex != -1 { + currentRuleIndex = ruleIndex + 1 + } + for ; currentRuleIndex < len(r.dnsRules); currentRuleIndex++ { + currentRule := r.dnsRules[currentRuleIndex] + if currentRule.WithAddressLimit() && !isAddressQuery { + continue } - for currentRuleIndex, currentRule := range dnsRules { - if currentRule.WithAddressLimit() && !isAddressQuery { - continue + metadata.ResetRuleCache() + if currentRule.Match(metadata) { + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) } - metadata.ResetRuleCache() - if currentRule.Match(metadata) { - displayRuleIndex := currentRuleIndex - if displayRuleIndex != -1 { - displayRuleIndex += displayRuleIndex + 1 + switch action := currentRule.Action().(type) { + case *R.RuleActionDNSRoute: + transport, loaded := r.transportMap[action.Server] + if !loaded { + r.dnsLogger.ErrorContext(ctx, "transport not found: ", action.Server) + continue + } + _, isFakeIP := transport.(adapter.FakeIPTransport) + if isFakeIP && !allowFakeIP { + continue + } + if isFakeIP || action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL } - ruleDescription := currentRule.String() - if ruleDescription != "" { - r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet + } + if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { + options.Strategy = domainStrategy } else { - r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + options.Strategy = r.defaultDomainStrategy } - switch action := currentRule.Action().(type) { - case *R.RuleActionDNSRoute: - transport, loaded := r.transportMap[action.Server] - if !loaded { - r.dnsLogger.ErrorContext(ctx, "transport not found: ", action.Server) - continue - } - _, isFakeIP := transport.(adapter.FakeIPTransport) - if isFakeIP && !allowFakeIP { - continue - } - if isFakeIP || action.DisableCache { - options.DisableCache = true - } - if action.RewriteTTL != nil { - options.RewriteTTL = action.RewriteTTL - } - if action.ClientSubnet.IsValid() { - options.ClientSubnet = action.ClientSubnet - } - if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { - options.Strategy = domainStrategy - } else { - options.Strategy = r.defaultDomainStrategy - } - r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) - return transport, options, currentRule, currentRuleIndex - case *R.RuleActionDNSRouteOptions: - if action.DisableCache { - options.DisableCache = true - } - if action.RewriteTTL != nil { - options.RewriteTTL = action.RewriteTTL - } - if action.ClientSubnet.IsValid() { - options.ClientSubnet = action.ClientSubnet - } - r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) - case *R.RuleActionReject: - r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) - return nil, options, currentRule, currentRuleIndex + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) + return transport, options, currentRule, currentRuleIndex + case *R.RuleActionDNSRouteOptions: + if action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL + } + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet } + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) + case *R.RuleActionReject: + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) + return nil, options, currentRule, currentRuleIndex } } } @@ -132,7 +127,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er } return &responseMessage, nil } - r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String())) var ( response *mDNS.Msg cached bool @@ -173,14 +167,11 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er } } } + r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()), " via ", transport.Name()) if rule != nil && rule.WithAddressLimit() { addressLimit = true - response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(response *mDNS.Msg) bool { - addresses, addrErr := dns.MessageToAddresses(response) - if addrErr != nil { - return false - } - metadata.DestinationAddresses = addresses + response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(responseAddrs []netip.Addr) bool { + metadata.DestinationAddresses = responseAddrs return rule.MatchAddressLimit(metadata) }) } else { From 50b8f3ab9441a383ff3cc75f93395fff37545634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 16 Dec 2024 12:01:42 +0800 Subject: [PATCH 0187/1272] Add `rule-set merge` command --- Makefile | 2 +- cmd/sing-box/cmd_merge.go | 2 +- cmd/sing-box/cmd_rule_set_merge.go | 162 +++++++++++++++++++++++++++++ option/rule_set.go | 6 +- release/completions/sing-box.bash | 31 ++++++ 5 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 cmd/sing-box/cmd_rule_set_merge.go diff --git a/Makefile b/Makefile index 11cecd17f6..461233271f 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ ci_build: go build $(MAIN_PARAMS) $(MAIN) generate_completions: - go run -v --tags generate,generate_completions $(MAIN) + go run -v --tags $(TAGS),generate,generate_completions $(MAIN) install: go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) diff --git a/cmd/sing-box/cmd_merge.go b/cmd/sing-box/cmd_merge.go index fa194ed342..6ca9a15a10 100644 --- a/cmd/sing-box/cmd_merge.go +++ b/cmd/sing-box/cmd_merge.go @@ -18,7 +18,7 @@ import ( ) var commandMerge = &cobra.Command{ - Use: "merge ", + Use: "merge ", Short: "Merge configurations", Run: func(cmd *cobra.Command, args []string) { err := merge(args[0]) diff --git a/cmd/sing-box/cmd_rule_set_merge.go b/cmd/sing-box/cmd_rule_set_merge.go new file mode 100644 index 0000000000..7c8f7a5353 --- /dev/null +++ b/cmd/sing-box/cmd_rule_set_merge.go @@ -0,0 +1,162 @@ +package main + +import ( + "bytes" + "io" + "os" + "path/filepath" + "sort" + "strings" + + "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" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/rw" + + "github.com/spf13/cobra" +) + +var ( + ruleSetPaths []string + ruleSetDirectories []string +) + +var commandRuleSetMerge = &cobra.Command{ + Use: "merge ", + Short: "Merge rule-set source files", + Run: func(cmd *cobra.Command, args []string) { + err := mergeRuleSet(args[0]) + if err != nil { + log.Fatal(err) + } + }, + Args: cobra.ExactArgs(1), +} + +func init() { + commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path") + commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path") + commandRuleSet.AddCommand(commandRuleSetMerge) +} + +type RuleSetEntry struct { + content []byte + path string + options option.PlainRuleSetCompat +} + +func readRuleSetAt(path string) (*RuleSetEntry, error) { + var ( + configContent []byte + err error + ) + if path == "stdin" { + configContent, err = io.ReadAll(os.Stdin) + } else { + configContent, err = os.ReadFile(path) + } + if err != nil { + return nil, E.Cause(err, "read config at ", path) + } + options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent) + if err != nil { + return nil, E.Cause(err, "decode config at ", path) + } + return &RuleSetEntry{ + content: configContent, + path: path, + options: options, + }, nil +} + +func readRuleSet() ([]*RuleSetEntry, error) { + var optionsList []*RuleSetEntry + for _, path := range ruleSetPaths { + optionsEntry, err := readRuleSetAt(path) + if err != nil { + return nil, err + } + optionsList = append(optionsList, optionsEntry) + } + for _, directory := range ruleSetDirectories { + entries, err := os.ReadDir(directory) + if err != nil { + return nil, E.Cause(err, "read rule-set directory at ", directory) + } + for _, entry := range entries { + if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() { + continue + } + optionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name())) + if err != nil { + return nil, err + } + optionsList = append(optionsList, optionsEntry) + } + } + sort.Slice(optionsList, func(i, j int) bool { + return optionsList[i].path < optionsList[j].path + }) + return optionsList, nil +} + +func readRuleSetAndMerge() (option.PlainRuleSetCompat, error) { + optionsList, err := readRuleSet() + if err != nil { + return option.PlainRuleSetCompat{}, err + } + if len(optionsList) == 1 { + return optionsList[0].options, nil + } + var optionVersion uint8 + for _, options := range optionsList { + if optionVersion < options.options.Version { + optionVersion = options.options.Version + } + } + var mergedMessage json.RawMessage + for _, options := range optionsList { + mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false) + if err != nil { + return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path) + } + } + mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage) + if err != nil { + return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config") + } + mergedOptions.Version = optionVersion + return mergedOptions, nil +} + +func mergeRuleSet(outputPath string) error { + mergedOptions, err := readRuleSetAndMerge() + if err != nil { + return err + } + buffer := new(bytes.Buffer) + encoder := json.NewEncoder(buffer) + encoder.SetIndent("", " ") + err = encoder.Encode(mergedOptions) + if err != nil { + return E.Cause(err, "encode config") + } + if existsContent, err := os.ReadFile(outputPath); err != nil { + if string(existsContent) == buffer.String() { + return nil + } + } + err = rw.MkdirParent(outputPath) + if err != nil { + return err + } + err = os.WriteFile(outputPath, buffer.Bytes(), 0o644) + if err != nil { + return err + } + outputPath, _ = filepath.Abs(outputPath) + os.Stderr.WriteString(outputPath + "\n") + return nil +} diff --git a/option/rule_set.go b/option/rule_set.go index f7a5f3344f..bf644764ad 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -194,8 +194,9 @@ func (r LogicalHeadlessRule) IsValid() bool { } type _PlainRuleSetCompat struct { - Version uint8 `json:"version"` - Options PlainRuleSet `json:"-"` + Version uint8 `json:"version"` + Options PlainRuleSet `json:"-"` + RawMessage json.RawMessage `json:"-"` } type PlainRuleSetCompat _PlainRuleSetCompat @@ -229,6 +230,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { if err != nil { return err } + r.RawMessage = bytes return nil } diff --git a/release/completions/sing-box.bash b/release/completions/sing-box.bash index ab1029770b..22df7763cf 100644 --- a/release/completions/sing-box.bash +++ b/release/completions/sing-box.bash @@ -1179,6 +1179,36 @@ _sing-box_rule-set_match() noun_aliases=() } +_sing-box_rule-set_merge() +{ + last_command="sing-box_rule-set_merge" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + _sing-box_rule-set_upgrade() { last_command="sing-box_rule-set_upgrade" @@ -1225,6 +1255,7 @@ _sing-box_rule-set() commands+=("decompile") commands+=("format") commands+=("match") + commands+=("merge") commands+=("upgrade") flags=() From 96fdf59ee421cf13c5c24f03d96acd4739c08bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 21 Dec 2024 21:26:42 +0800 Subject: [PATCH 0188/1272] Fix default dialer on legacy xiaomi systems --- common/dialer/default.go | 94 +++++++++++++-------- common/dialer/default_parallel_interface.go | 8 +- 2 files changed, 62 insertions(+), 40 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index 49bd145cd6..77536c43db 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -2,8 +2,10 @@ package dialer import ( "context" + "errors" "net" "net/netip" + "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -26,20 +28,21 @@ var ( ) type DefaultDialer struct { - dialer4 tcpDialer - dialer6 tcpDialer - udpDialer4 net.Dialer - udpDialer6 net.Dialer - udpListener net.ListenConfig - udpAddr4 string - udpAddr6 string - isWireGuardListener bool - networkManager adapter.NetworkManager - networkStrategy *C.NetworkStrategy - networkType []C.InterfaceType - fallbackNetworkType []C.InterfaceType - networkFallbackDelay time.Duration - networkLastFallback atomic.TypedValue[time.Time] + dialer4 tcpDialer + dialer6 tcpDialer + udpDialer4 net.Dialer + udpDialer6 net.Dialer + udpListener net.ListenConfig + udpAddr4 string + udpAddr6 string + isWireGuardListener bool + networkManager adapter.NetworkManager + networkStrategy *C.NetworkStrategy + defaultNetworkStrategy bool + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType + networkFallbackDelay time.Duration + networkLastFallback atomic.TypedValue[time.Time] } func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { @@ -47,13 +50,14 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial platformInterface := service.FromContext[platform.Interface](ctx) var ( - dialer net.Dialer - listener net.ListenConfig - interfaceFinder control.InterfaceFinder - networkStrategy *C.NetworkStrategy - networkType []C.InterfaceType - fallbackNetworkType []C.InterfaceType - networkFallbackDelay time.Duration + dialer net.Dialer + listener net.ListenConfig + interfaceFinder control.InterfaceFinder + networkStrategy *C.NetworkStrategy + defaultNetworkStrategy bool + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType + networkFallbackDelay time.Duration ) if networkManager != nil { interfaceFinder = networkManager.InterfaceFinder() @@ -98,6 +102,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy) if networkStrategy == nil { networkStrategy = common.Ptr(C.NetworkStrategyDefault) + defaultNetworkStrategy = true } networkType = common.Map(options.NetworkType, option.InterfaceType.Build) fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build) @@ -192,19 +197,20 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial return nil, err } return &DefaultDialer{ - dialer4: tcpDialer4, - dialer6: tcpDialer6, - udpDialer4: udpDialer4, - udpDialer6: udpDialer6, - udpListener: listener, - udpAddr4: udpAddr4, - udpAddr6: udpAddr6, - isWireGuardListener: options.IsWireGuardListener, - networkManager: networkManager, - networkStrategy: networkStrategy, - networkType: networkType, - fallbackNetworkType: fallbackNetworkType, - networkFallbackDelay: networkFallbackDelay, + dialer4: tcpDialer4, + dialer6: tcpDialer6, + udpDialer4: udpDialer4, + udpDialer6: udpDialer6, + udpListener: listener, + udpAddr4: udpAddr4, + udpAddr6: udpAddr6, + isWireGuardListener: options.IsWireGuardListener, + networkManager: networkManager, + networkStrategy: networkStrategy, + defaultNetworkStrategy: defaultNetworkStrategy, + networkType: networkType, + fallbackNetworkType: fallbackNetworkType, + networkFallbackDelay: networkFallbackDelay, }, nil } @@ -265,7 +271,13 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store) } if err != nil { - return nil, err + // bind interface failed on legacy xiaomi systems + if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) { + d.networkStrategy = nil + return d.DialContext(ctx, network, address) + } else { + return nil, err + } } if !fastFallback && !isPrimary { d.networkLastFallback.Store(time.Now()) @@ -307,7 +319,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina if destination.IsIPv4() && !destination.Addr.IsUnspecified() { network += "4" } - return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)) + packetConn, err := d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + if err != nil { + // bind interface failed on legacy xiaomi systems + if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) { + d.networkStrategy = nil + return d.ListenPacket(ctx, destination) + } else { + return nil, err + } + } + return trackPacketConn(packetConn, nil) } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go index 269546a413..92e3535e81 100644 --- a/common/dialer/default_parallel_interface.go +++ b/common/dialer/default_parallel_interface.go @@ -35,7 +35,7 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di conn, err := perNetDialer.DialContext(ctx, network, addr) if err != nil { select { - case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}: + case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}: case <-returned: } } else { @@ -107,7 +107,7 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d conn, err := perNetDialer.DialContext(ctx, network, addr) if err != nil { select { - case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}: + case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}: case <-returned: } } else { @@ -157,7 +157,7 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene if err == nil { return conn, nil } - errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Name, ")")) + errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Index, ")")) } for _, fallbackInterface := range fallbackInterfaces { perNetListener := listener @@ -166,7 +166,7 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene if err == nil { return conn, nil } - errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Name, ")")) + errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Index, ")")) } return nil, E.Errors(errors...) } From 7aac801ccd4da74ea98ba19bb14664a5bea1ce76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 23 Dec 2024 22:24:10 +0800 Subject: [PATCH 0189/1272] tun: Set address sets to routes --- docs/configuration/inbound/tun.md | 56 ++++++-- docs/configuration/inbound/tun.zh.md | 52 +++++-- experimental/libbox/config.go | 4 + experimental/libbox/platform.go | 1 + experimental/libbox/platform/interface.go | 1 + experimental/libbox/service.go | 18 ++- protocol/tun/inbound.go | 161 ++++++++++++++-------- route/router.go | 2 +- 8 files changed, 209 insertions(+), 86 deletions(-) diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index d82d451b2e..b6bf5c75f3 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -4,7 +4,9 @@ icon: material/alert-decagram !!! quote "Changes in sing-box 1.11.0" - :material-delete-alert: [gso](#gso) + :material-delete-alert: [gso](#gso) + :material-alert-decagram: [route_address_set](#stack) + :material-alert-decagram: [route_exclude_address_set](#stack) !!! quote "Changes in sing-box 1.10.0" @@ -248,7 +250,7 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot). !!! question "Since sing-box 1.10.0" -Connection input mark used by `route_address_set` and `route_exclude_address_set`. +Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`. `0x2023` is used by default. @@ -256,7 +258,7 @@ Connection input mark used by `route_address_set` and `route_exclude_address_set !!! question "Since sing-box 1.10.0" -Connection output mark used by `route_address_set` and `route_exclude_address_set`. +Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`. `0x2024` is used by default. @@ -329,29 +331,55 @@ Exclude custom routes when `auto_route` is enabled. #### route_address_set -!!! question "Since sing-box 1.10.0" +=== "With `auto_redirect` enabled" -!!! quote "" + !!! question "Since sing-box 1.10.0" - Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled. + !!! quote "" + + Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled. + + Add the destination IP CIDR rules in the specified rule-sets to the firewall. + Unmatched traffic will bypass the sing-box routes. + + Conflict with `route.default_mark` and `[dialOptions].routing_mark`. -Add the destination IP CIDR rules in the specified rule-sets to the firewall. -Unmatched traffic will bypass the sing-box routes. +=== "Without `auto_redirect` enabled" -Conflict with `route.default_mark` and `[dialOptions].routing_mark`. + !!! question "Since sing-box 1.11.0" + + Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_address`. + Unmatched traffic will bypass the sing-box routes. + + Note that it **doesn't work on the Android graphical client** due to + the Android VpnService not being able to handle a large number of routes (DeadSystemException), + but otherwise it works fine on all command line clients and Apple platforms. #### route_exclude_address_set -!!! question "Since sing-box 1.10.0" +=== "With `auto_redirect` enabled" -!!! quote "" + !!! question "Since sing-box 1.10.0" + + !!! quote "" Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled. -Add the destination IP CIDR rules in the specified rule-sets to the firewall. -Matched traffic will bypass the sing-box routes. + Add the destination IP CIDR rules in the specified rule-sets to the firewall. + Matched traffic will bypass the sing-box routes. + + Conflict with `route.default_mark` and `[dialOptions].routing_mark`. + +=== "Without `auto_redirect` enabled" + + !!! question "Since sing-box 1.11.0" + + Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_exclude_address`. + Matched traffic will bypass the sing-box routes. -Conflict with `route.default_mark` and `[dialOptions].routing_mark`. + Note that it **doesn't work on the Android graphical client** due to + the Android VpnService not being able to handle a large number of routes (DeadSystemException), + but otherwise it works fine on all command line clients and Apple platforms. #### endpoint_independent_nat diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 2b5752ba44..c9bd844d41 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -4,7 +4,9 @@ icon: material/alert-decagram !!! quote "sing-box 1.11.0 中的更改" - :material-delete-alert: [gso](#gso) + :material-delete-alert: [gso](#gso) + :material-alert-decagram: [route_address_set](#stack) + :material-alert-decagram: [route_exclude_address_set](#stack) !!! quote "sing-box 1.10.0 中的更改" @@ -329,29 +331,53 @@ tun 接口的 IPv6 前缀。 #### route_address_set -!!! question "自 sing-box 1.10.0 起" +=== "`auto_redirect` 已启用" -!!! quote "" + !!! question "自 sing-box 1.10.0 起" + + !!! quote "" + + 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + + 将指定规则集中的目标 IP CIDR 规则添加到防火墙。 + 不匹配的流量将绕过 sing-box 路由。 + + 与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 - 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 +=== "`auto_redirect` 未启用" -将指定规则集中的目标 IP CIDR 规则添加到防火墙。 -不匹配的流量将绕过 sing-box 路由。 + !!! question "自 sing-box 1.11.0 起" -与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 + 将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_address`。 + 不匹配的流量将绕过 sing-box 路由。 + + 请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException), + 因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。 #### route_exclude_address_set -!!! question "自 sing-box 1.10.0 起" +=== "`auto_redirect` 已启用" -!!! quote "" + !!! question "自 sing-box 1.10.0 起" + + !!! quote "" + + 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + + 将指定规则集中的目标 IP CIDR 规则添加到防火墙。 + 匹配的流量将绕过 sing-box 路由。 + + 与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 + +=== "`auto_redirect` 未启用" - 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + !!! question "自 sing-box 1.11.0 起" -将指定规则集中的目标 IP CIDR 规则添加到防火墙。 -匹配的流量将绕过 sing-box 路由。 + 将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_exclude_address`。 + 匹配的流量将绕过 sing-box 路由。 -与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 + 请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException), + 因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。 #### endpoint_independent_nat diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 159fd8f6a0..6a85c963ad 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -66,6 +66,10 @@ func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions op return nil, os.ErrInvalid } +func (s *platformInterfaceStub) UpdateRouteOptions(options *tun.Options, platformInterface option.TunPlatformOptions) error { + return os.ErrInvalid +} + func (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool { return true } diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index d5951cd38f..2503ea442b 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -9,6 +9,7 @@ type PlatformInterface interface { UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int32) error OpenTun(options TunOptions) (int32, error) + UpdateRouteOptions(options TunOptions) error WriteLog(message string) UseProcFS() bool FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index ef37daea22..eda51b4888 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -13,6 +13,7 @@ type Interface interface { UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int) error OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) + UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor Interfaces() ([]adapter.NetworkInterface, error) UnderNetworkExtension() bool diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 8dd4cb22fc..4906c57131 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -148,10 +148,10 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error { func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 { - return nil, E.New("android: unsupported uid options") + return nil, E.New("platform: unsupported uid options") } if len(options.IncludeAndroidUser) > 0 { - return nil, E.New("android: unsupported android_user option") + return nil, E.New("platform: unsupported android_user option") } routeRanges, err := options.BuildAutoRouteRanges(true) if err != nil { @@ -174,6 +174,20 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions return tun.New(*options) } +func (w *platformInterfaceWrapper) UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error { + if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 { + return E.New("android: unsupported uid options") + } + if len(options.IncludeAndroidUser) > 0 { + return E.New("android: unsupported android_user option") + } + routeRanges, err := options.BuildAutoRouteRanges(true) + if err != nil { + return err + } + return w.iif.UpdateRouteOptions(&tunOptions{options, routeRanges, platformOptions}) +} + func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 26158d7a56..34a5db2b84 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -209,6 +209,22 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo platformInterface: service.FromContext[platform.Interface](ctx), platformOptions: common.PtrValueOrDefault(options.Platform), } + for _, routeAddressSet := range options.RouteAddressSet { + ruleSet, loaded := router.RuleSet(routeAddressSet) + if !loaded { + return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet) + } + ruleSet.IncRef() + inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet) + } + for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet { + ruleSet, loaded := router.RuleSet(routeExcludeAddressSet) + if !loaded { + return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet) + } + ruleSet.IncRef() + inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet) + } if options.AutoRedirect { if !options.AutoRoute { return nil, E.New("`auto_route` is required by `auto_redirect`") @@ -229,32 +245,11 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if err != nil { return nil, E.Cause(err, "initialize auto-redirect") } - if runtime.GOOS != "android" { - var markMode bool - for _, routeAddressSet := range options.RouteAddressSet { - ruleSet, loaded := router.RuleSet(routeAddressSet) - if !loaded { - return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet) - } - ruleSet.IncRef() - inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet) - markMode = true - } - for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet { - ruleSet, loaded := router.RuleSet(routeExcludeAddressSet) - if !loaded { - return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet) - } - ruleSet.IncRef() - inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet) - markMode = true - } - if markMode { - inbound.tunOptions.AutoRedirectMarkMode = true - err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) - if err != nil { - return nil, err - } + if !C.IsAndroid && (len(inbound.routeRuleSet) > 0 || len(inbound.routeExcludeRuleSet) > 0) { + inbound.tunOptions.AutoRedirectMarkMode = true + err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) + if err != nil { + return nil, err } } } @@ -310,18 +305,62 @@ func (t *Inbound) Start(stage adapter.StartStage) error { if t.tunOptions.Name == "" { t.tunOptions.Name = tun.CalculateInterfaceName("") } + if t.platformInterface == nil || runtime.GOOS != "android" { + t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) + for _, routeRuleSet := range t.routeRuleSet { + ipSets := routeRuleSet.ExtractIPSet() + if len(ipSets) == 0 { + t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name()) + } + t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + routeRuleSet.DecRef() + t.routeAddressSet = append(t.routeAddressSet, ipSets...) + } + t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) + for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { + ipSets := routeExcludeRuleSet.ExtractIPSet() + if len(ipSets) == 0 { + t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name()) + } + t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + routeExcludeRuleSet.DecRef() + t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...) + } + } var ( tunInterface tun.Tun err error ) monitor := taskmonitor.New(t.logger, C.StartTimeout) - monitor.Start("open tun interface") + tunOptions := t.tunOptions + if t.autoRedirect == nil && !(runtime.GOOS == "android" && t.platformInterface != nil) { + for _, ipSet := range t.routeAddressSet { + for _, prefix := range ipSet.Prefixes() { + if prefix.Addr().Is4() { + tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix) + } else { + tunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix) + } + } + } + for _, ipSet := range t.routeExcludeAddressSet { + for _, prefix := range ipSet.Prefixes() { + if prefix.Addr().Is4() { + tunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix) + } else { + tunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix) + } + } + } + } + monitor.Start("open interface") if t.platformInterface != nil { - tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions) + tunInterface, err = t.platformInterface.OpenTun(&tunOptions, t.platformOptions) } else { - tunInterface, err = tun.New(t.tunOptions) + tunInterface, err = tun.New(tunOptions) } monitor.Finish() + t.tunOptions.Name = tunOptions.Name if err != nil { return E.Cause(err, "configure tun interface") } @@ -366,39 +405,15 @@ func (t *Inbound) Start(stage adapter.StartStage) error { return E.Cause(err, "starting TUN interface") } if t.autoRedirect != nil { - t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) - for _, routeRuleSet := range t.routeRuleSet { - ipSets := routeRuleSet.ExtractIPSet() - if len(ipSets) == 0 { - t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name()) - } - t.routeAddressSet = append(t.routeAddressSet, ipSets...) - } - t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) - for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { - ipSets := routeExcludeRuleSet.ExtractIPSet() - if len(ipSets) == 0 { - t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name()) - } - t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...) - } monitor.Start("initialize auto-redirect") err := t.autoRedirect.Start() monitor.Finish() if err != nil { return E.Cause(err, "auto-redirect") } - for _, routeRuleSet := range t.routeRuleSet { - t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) - routeRuleSet.DecRef() - } - for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { - t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) - routeExcludeRuleSet.DecRef() - } - t.routeAddressSet = nil - t.routeExcludeAddressSet = nil } + t.routeAddressSet = nil + t.routeExcludeAddressSet = nil } return nil } @@ -406,7 +421,41 @@ func (t *Inbound) Start(stage adapter.StartStage) error { func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) - t.autoRedirect.UpdateRouteAddressSet() + if t.autoRedirect != nil { + t.autoRedirect.UpdateRouteAddressSet() + } else { + tunOptions := t.tunOptions + for _, ipSet := range t.routeAddressSet { + for _, prefix := range ipSet.Prefixes() { + if prefix.Addr().Is4() { + tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix) + } else { + tunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix) + } + } + } + for _, ipSet := range t.routeExcludeAddressSet { + for _, prefix := range ipSet.Prefixes() { + if prefix.Addr().Is4() { + tunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix) + } else { + tunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix) + } + } + } + if t.platformInterface != nil { + err := t.platformInterface.UpdateRouteOptions(&tunOptions, t.platformOptions) + if err != nil { + t.logger.Error("update route addresses: ", err) + } + } else { + err := t.tunIf.UpdateRouteOptions(tunOptions) + if err != nil { + t.logger.Error("update route addresses: ", err) + } + } + t.logger.Info("updated route addresses") + } t.routeAddressSet = nil t.routeExcludeAddressSet = nil } diff --git a/route/router.go b/route/router.go index 6526778bea..642340d4ab 100644 --- a/route/router.go +++ b/route/router.go @@ -363,7 +363,6 @@ func (r *Router) Start(stage adapter.StartStage) error { return E.Cause(err, "initialize DNS server[", i, "]") } } - case adapter.StartStatePostStart: var cacheContext *adapter.HTTPStartContext if len(r.ruleSets) > 0 { monitor.Start("initialize rule-set") @@ -419,6 +418,7 @@ func (r *Router) Start(stage adapter.StartStage) error { } } } + case adapter.StartStatePostStart: for i, rule := range r.rules { monitor.Start("initialize rule[", i, "]") err := rule.Start() From 32377a61b7d56965cf8d4f646b664ec23f257837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 29 Dec 2024 08:52:12 +0800 Subject: [PATCH 0190/1272] Add port hopping for hysteria2 --- docs/configuration/inbound/hysteria2.md | 11 +++++-- docs/configuration/inbound/hysteria2.zh.md | 9 ++++-- docs/configuration/outbound/hysteria2.md | 35 +++++++++++++++++++++ docs/configuration/outbound/hysteria2.zh.md | 35 +++++++++++++++++++++ option/hysteria2.go | 12 ++++--- protocol/hysteria2/outbound.go | 3 ++ 6 files changed, 95 insertions(+), 10 deletions(-) diff --git a/docs/configuration/inbound/hysteria2.md b/docs/configuration/inbound/hysteria2.md index 6230d3154b..3b7332b064 100644 --- a/docs/configuration/inbound/hysteria2.md +++ b/docs/configuration/inbound/hysteria2.md @@ -4,7 +4,8 @@ icon: material/alert-decagram !!! quote "Changes in sing-box 1.11.0" - :material-alert: [masquerade](#masquerade) + :material-alert: [masquerade](#masquerade) + :material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth) ### Structure @@ -75,9 +76,13 @@ Authentication password #### ignore_client_bandwidth -Commands the client to use the BBR flow control algorithm instead of Hysteria CC. +*When `up_mbps` and `down_mbps` are not set*: -Conflict with `up_mbps` and `down_mbps`. +Commands clients to use the BBR CC instead of Hysteria CC. + +*When `up_mbps` and `down_mbps` are set*: + +Deny clients to use the BBR CC. #### tls diff --git a/docs/configuration/inbound/hysteria2.zh.md b/docs/configuration/inbound/hysteria2.zh.md index fceacf9325..5ad5d75d6e 100644 --- a/docs/configuration/inbound/hysteria2.zh.md +++ b/docs/configuration/inbound/hysteria2.zh.md @@ -4,7 +4,8 @@ icon: material/alert-decagram !!! quote "sing-box 1.11.0 中的更改" - :material-alert: [masquerade](#masquerade) + :material-alert: [masquerade](#masquerade) + :material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth) ### 结构 @@ -72,9 +73,13 @@ Hysteria 用户 #### ignore_client_bandwidth +*当 `up_mbps` 和 `down_mbps` 未设定时*: + 命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。 -与 `up_mbps` 和 `down_mbps` 冲突。 +*当 `up_mbps` 和 `down_mbps` 已设定时*: + +禁止客户端使用 BBR 拥塞控制算法。 #### tls diff --git a/docs/configuration/outbound/hysteria2.md b/docs/configuration/outbound/hysteria2.md index ae0b96edda..77063fb481 100644 --- a/docs/configuration/outbound/hysteria2.md +++ b/docs/configuration/outbound/hysteria2.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [server_ports](#server_ports) + :material-plus: [hop_interval](#hop_interval) + ### Structure ```json @@ -7,6 +16,10 @@ "server": "127.0.0.1", "server_port": 1080, + "server_ports": [ + "2080:3000" + ], + "hop_interval": "", "up_mbps": 100, "down_mbps": 100, "obfs": { @@ -22,6 +35,10 @@ } ``` +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + !!! warning "Difference from official Hysteria2" The official Hysteria2 supports an authentication method called **userpass**, @@ -44,6 +61,24 @@ The server address. The server port. +Ignored if `server_ports` is set. + +#### server_ports + +!!! question "Since sing-box 1.11.0" + +Server port range list. + +Conflicts with `server_port`. + +#### hop_interval + +!!! question "Since sing-box 1.11.0" + +Port hopping interval. + +`30s` is used by default. + #### up_mbps, down_mbps Max bandwidth, in Mbps. diff --git a/docs/configuration/outbound/hysteria2.zh.md b/docs/configuration/outbound/hysteria2.zh.md index 7176b9a6f7..0c5a631e10 100644 --- a/docs/configuration/outbound/hysteria2.zh.md +++ b/docs/configuration/outbound/hysteria2.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [server_ports](#server_ports) + :material-plus: [hop_interval](#hop_interval) + ### 结构 ```json @@ -7,6 +16,10 @@ "server": "127.0.0.1", "server_port": 1080, + "server_ports": [ + "2080:3000" + ], + "hop_interval": "", "up_mbps": 100, "down_mbps": 100, "obfs": { @@ -22,6 +35,10 @@ } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + !!! warning "与官方 Hysteria2 的区别" 官方程序支持一种名为 **userpass** 的验证方式, @@ -42,6 +59,24 @@ 服务器端口。 +如果设置了 `server_ports`,则忽略此项。 + +#### server_ports + +!!! question "自 sing-box 1.11.0 起" + +服务器端口范围列表。 + +与 `server_port` 冲突。 + +#### hop_interval + +!!! question "自 sing-box 1.11.0 起" + +端口跳跃间隔。 + +默认使用 `30s`。 + #### up_mbps, down_mbps 最大带宽。 diff --git a/option/hysteria2.go b/option/hysteria2.go index 8d5e114815..a014513630 100644 --- a/option/hysteria2.go +++ b/option/hysteria2.go @@ -112,11 +112,13 @@ type Hysteria2MasqueradeString struct { type Hysteria2OutboundOptions struct { DialerOptions ServerOptions - UpMbps int `json:"up_mbps,omitempty"` - DownMbps int `json:"down_mbps,omitempty"` - Obfs *Hysteria2Obfs `json:"obfs,omitempty"` - Password string `json:"password,omitempty"` - Network NetworkList `json:"network,omitempty"` + ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"` + HopInterval badoption.Duration `json:"hop_interval,omitempty"` + UpMbps int `json:"up_mbps,omitempty"` + DownMbps int `json:"down_mbps,omitempty"` + Obfs *Hysteria2Obfs `json:"obfs,omitempty"` + Password string `json:"password,omitempty"` + Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer BrutalDebug bool `json:"brutal_debug,omitempty"` } diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go index 068cc7f7fe..74e87b37cf 100644 --- a/protocol/hysteria2/outbound.go +++ b/protocol/hysteria2/outbound.go @@ -4,6 +4,7 @@ import ( "context" "net" "os" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" @@ -70,6 +71,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL Logger: logger, BrutalDebug: options.BrutalDebug, ServerAddress: options.ServerOptions.Build(), + ServerPorts: options.ServerPorts, + HopInterval: time.Duration(options.HopInterval), SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps), ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps), SalamanderPassword: salamanderPassword, From f2ec319fe18def8c5b93eaaf32a2e39eee65ed47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 11 Jan 2025 20:34:07 +0800 Subject: [PATCH 0191/1272] Fix system time --- cmd/sing-box/cmd_tools_synctime.go | 3 +-- common/settings/time_stub.go | 12 ----------- common/settings/time_unix.go | 14 ------------- common/settings/time_windows.go | 32 ------------------------------ 4 files changed, 1 insertion(+), 60 deletions(-) delete mode 100644 common/settings/time_stub.go delete mode 100644 common/settings/time_unix.go delete mode 100644 common/settings/time_windows.go diff --git a/cmd/sing-box/cmd_tools_synctime.go b/cmd/sing-box/cmd_tools_synctime.go index 38510eb8e1..09d487ef95 100644 --- a/cmd/sing-box/cmd_tools_synctime.go +++ b/cmd/sing-box/cmd_tools_synctime.go @@ -4,7 +4,6 @@ import ( "context" "os" - "github.com/sagernet/sing-box/common/settings" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" E "github.com/sagernet/sing/common/exceptions" @@ -58,7 +57,7 @@ func syncTime() error { return err } if commandSyncTimeWrite { - err = settings.SetSystemTime(response.Time) + err = ntp.SetSystemTime(response.Time) if err != nil { return E.Cause(err, "write time to system") } diff --git a/common/settings/time_stub.go b/common/settings/time_stub.go deleted file mode 100644 index 06204913dc..0000000000 --- a/common/settings/time_stub.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !(windows || linux || darwin) - -package settings - -import ( - "os" - "time" -) - -func SetSystemTime(nowTime time.Time) error { - return os.ErrInvalid -} diff --git a/common/settings/time_unix.go b/common/settings/time_unix.go deleted file mode 100644 index 9eff244dfb..0000000000 --- a/common/settings/time_unix.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build linux || darwin - -package settings - -import ( - "time" - - "golang.org/x/sys/unix" -) - -func SetSystemTime(nowTime time.Time) error { - timeVal := unix.NsecToTimeval(nowTime.UnixNano()) - return unix.Settimeofday(&timeVal) -} diff --git a/common/settings/time_windows.go b/common/settings/time_windows.go deleted file mode 100644 index 5d95e3ba68..0000000000 --- a/common/settings/time_windows.go +++ /dev/null @@ -1,32 +0,0 @@ -package settings - -import ( - "time" - "unsafe" - - "golang.org/x/sys/windows" -) - -func SetSystemTime(nowTime time.Time) error { - var systemTime windows.Systemtime - systemTime.Year = uint16(nowTime.Year()) - systemTime.Month = uint16(nowTime.Month()) - systemTime.Day = uint16(nowTime.Day()) - systemTime.Hour = uint16(nowTime.Hour()) - systemTime.Minute = uint16(nowTime.Minute()) - systemTime.Second = uint16(nowTime.Second()) - systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000) - - dllKernel32 := windows.NewLazySystemDLL("kernel32.dll") - proc := dllKernel32.NewProc("SetSystemTime") - - _, _, err := proc.Call( - uintptr(unsafe.Pointer(&systemTime)), - ) - - if err != nil && err.Error() != "The operation completed successfully." { - return err - } - - return nil -} From e65926fd0835d903481fb0aa76172b26f319fd18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Nov 2024 18:59:19 +0800 Subject: [PATCH 0192/1272] Fix tests --- test/box_test.go | 10 +++- test/brutal_test.go | 58 +++++++++++---------- test/direct_test.go | 16 +++--- test/domain_inbound_test.go | 16 +++--- test/ech_test.go | 43 ++++++++-------- test/go.mod | 29 ++++++----- test/go.sum | 91 ++++++++++++++++----------------- test/http_test.go | 16 +++--- test/hysteria2_test.go | 72 ++++++++++++++++++-------- test/hysteria_test.go | 32 ++++++------ test/inbound_detour_test.go | 26 ++++++---- test/mux_cool_test.go | 32 ++++++------ test/mux_test.go | 30 ++++++----- test/naive_test.go | 20 ++++---- test/shadowsocks_legacy_test.go | 12 +++-- test/shadowsocks_test.go | 60 +++++++++++----------- test/shadowtls_test.go | 71 +++++++++++++------------ test/ss_plugin_test.go | 12 +++-- test/tfo_test.go | 16 +++--- test/tls_test.go | 16 +++--- test/trojan_test.go | 40 ++++++++------- test/tuic_test.go | 32 ++++++------ test/v2ray_api_test.go | 12 +++-- test/v2ray_grpc_test.go | 18 ++++--- test/v2ray_transport_test.go | 58 +++++++++++---------- test/v2ray_ws_test.go | 18 ++++--- test/vmess_test.go | 32 ++++++------ test/wireguard_test.go | 51 ------------------ test/wrapper_test.go | 10 ++-- 29 files changed, 492 insertions(+), 457 deletions(-) delete mode 100644 test/wireguard_test.go diff --git a/test/box_test.go b/test/box_test.go index 621c00dafb..08b50b64c2 100644 --- a/test/box_test.go +++ b/test/box_test.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/quic-go/http3" "github.com/sagernet/sing-box" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/debug" @@ -28,6 +29,12 @@ func TestMain(m *testing.M) { goleak.VerifyTestMain(m) } +var globalCtx context.Context + +func init() { + globalCtx = box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()) +} + func startInstance(t *testing.T, options option.Options) *box.Box { if debug.Enabled { options.Log = &option.LogOptions{ @@ -38,8 +45,7 @@ func startInstance(t *testing.T, options option.Options) *box.Box { Level: "warning", } } - // ctx := context.Background() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(globalCtx) var instance *box.Box var err error for retry := 0; retry < 3; retry++ { diff --git a/test/brutal_test.go b/test/brutal_test.go index ce1d2c2a8b..d0841467b0 100644 --- a/test/brutal_test.go +++ b/test/brutal_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -15,22 +17,22 @@ func TestBrutalShadowsocks(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -46,14 +48,14 @@ func TestBrutalShadowsocks(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -100,22 +102,22 @@ func TestBrutalTrojan(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{{Password: password}}, @@ -138,14 +140,14 @@ func TestBrutalTrojan(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeTrojan, Tag: "ss-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -197,22 +199,22 @@ func TestBrutalTrojan(t *testing.T) { func TestBrutalVMess(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{{UUID: user.String()}}, @@ -227,14 +229,14 @@ func TestBrutalVMess(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeVMess, Tag: "ss-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -279,22 +281,22 @@ func TestBrutalVMess(t *testing.T) { func TestBrutalVLESS(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVLESS, - VLESSOptions: option.VLESSInboundOptions{ + Options: &option.VLESSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VLESSUser{{UUID: user.String()}}, @@ -326,14 +328,14 @@ func TestBrutalVLESS(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeVLESS, Tag: "ss-out", - VLESSOptions: option.VLESSOutboundOptions{ + Options: &option.VLESSOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/direct_test.go b/test/direct_test.go index c4fd8c5ea3..d33defeebd 100644 --- a/test/direct_test.go +++ b/test/direct_test.go @@ -6,41 +6,43 @@ import ( 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" ) // Since this is a feature one-off added by outsiders, I won't address these anymore. func _TestProxyProtocol(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeDirect, - DirectOptions: option.DirectInboundOptions{ + Options: &option.DirectInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, ProxyProtocol: true, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeDirect, Tag: "proxy-out", - DirectOptions: option.DirectOutboundOptions{ + Options: &option.DirectOutboundOptions{ OverrideAddress: "127.0.0.1", OverridePort: serverPort, ProxyProtocol: 2, diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index c82b0d2974..f39cd18725 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -14,22 +16,22 @@ import ( func TestTUICDomainUDP(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTUIC, - TUICOptions: option.TUICInboundOptions{ + Options: &option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, InboundOptions: option.InboundOptions{ DomainStrategy: option.DomainStrategy(dns.DomainStrategyUseIPv6), @@ -49,14 +51,14 @@ func TestTUICDomainUDP(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeTUIC, Tag: "tuic-out", - TUICOptions: option.TUICOutboundOptions{ + Options: &option.TUICOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/ech_test.go b/test/ech_test.go index eeac1acb72..865ffba003 100644 --- a/test/ech_test.go +++ b/test/ech_test.go @@ -8,6 +8,7 @@ import ( 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" ) @@ -16,22 +17,22 @@ func TestECH(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -55,14 +56,14 @@ func TestECH(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeTrojan, Tag: "trojan-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -109,22 +110,22 @@ func TestECHQUIC(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTUIC, - TUICOptions: option.TUICInboundOptions{ + Options: &option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TUICUser{{ @@ -145,14 +146,14 @@ func TestECHQUIC(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeTUIC, Tag: "tuic-out", - TUICOptions: option.TUICOutboundOptions{ + Options: &option.TUICOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -199,22 +200,22 @@ func TestECHHysteria2(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeHysteria2, - Hysteria2Options: option.Hysteria2InboundOptions{ + Options: &option.Hysteria2InboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.Hysteria2User{{ @@ -235,14 +236,14 @@ func TestECHHysteria2(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeHysteria2, Tag: "hy2-out", - Hysteria2Options: option.Hysteria2OutboundOptions{ + Options: &option.Hysteria2OutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/go.mod b/test/go.mod index 458ff8800b..68fddc34b1 100644 --- a/test/go.mod +++ b/test/go.mod @@ -13,9 +13,9 @@ require ( github.com/docker/go-connections v0.5.0 github.com/gofrs/uuid/v5 v5.3.0 github.com/sagernet/quic-go v0.48.2-beta.1 - github.com/sagernet/sing v0.6.0-beta.2 - github.com/sagernet/sing-dns v0.4.0-beta.1 - github.com/sagernet/sing-quic v0.4.0-alpha.4 + github.com/sagernet/sing v0.6.0-beta.12 + github.com/sagernet/sing-dns v0.4.0-beta.2 + github.com/sagernet/sing-quic v0.4.0-beta.4 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/spyzhov/ajson v0.9.4 @@ -25,8 +25,8 @@ require ( ) require ( - berty.tech/go-libtor v1.0.385 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/caddyserver/certmagic v0.20.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect @@ -38,6 +38,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-chi/chi/v5 v5.1.0 // indirect + github.com/go-chi/render v1.0.3 // 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 @@ -49,6 +50,7 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/hashicorp/yamux v0.1.2 // indirect + github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect @@ -65,16 +67,17 @@ require ( github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect - github.com/ooni/go-libtor v1.1.8 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect + github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // 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/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect @@ -82,12 +85,13 @@ require ( github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect - github.com/sagernet/sing-tun v0.6.0-beta.2 // indirect - github.com/sagernet/sing-vmess v0.1.12 // indirect + github.com/sagernet/sing-tun v0.6.0-beta.8 // indirect + github.com/sagernet/sing-vmess v0.2.0-beta.2 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/utls v1.6.7 // indirect - github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect + github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect + github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect @@ -99,14 +103,15 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.24.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect diff --git a/test/go.sum b/test/go.sum index 5fc5292c9c..5266d18cca 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1,9 +1,9 @@ -berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= -berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= @@ -14,7 +14,6 @@ github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vc github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= 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= @@ -34,6 +33,8 @@ 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/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= +github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.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= @@ -65,6 +66,9 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjw 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/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= +github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= +github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -107,14 +111,14 @@ github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= -github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= -github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= +github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= +github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -127,51 +131,45 @@ 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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= +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-20241021032506-a4324256e4a3 h1:RxEz7LhPNiF/gX/Hg+OXr5lqsM9iVAgmaK1L1vzlDRM= -github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= +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/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.48.0-beta.1 h1:86hQZrmuoARI3BpDRkQaP0iAVpywA4YsRrzJPYuPKWg= -github.com/sagernet/quic-go v0.48.0-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= +github.com/sagernet/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg= github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= 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.5.0-rc.4.0.20241022031908-cd17884118cb h1:3IhGq2UmcbQfAcuqyE8RYKFapqEEa3eItS/MrZr+5l8= -github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.6.0-beta.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce h1:OfpxE5qnXMyU/9LtNgX4M7bGP11lJx4s+KZ3Sijb0HE= -github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= -github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= -github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec h1:6Fd/VsEsw9qIjaGi1IBTZSb4b4v5JYtNcoiBtGsQC48= -github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec/go.mod h1:RSwqqHwbtTOX3vs6ms8vMtBGH/0ZNyLm/uwt6TlmR84= +github.com/sagernet/sing v0.6.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s= +github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY= +github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= +github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= -github.com/sagernet/sing-quic v0.3.0-rc.1 h1:SlzL1yfEAKJyRduub8vzOVtbyTLAX7RZEEBZxO5utts= -github.com/sagernet/sing-quic v0.3.0-rc.1/go.mod h1:uX+aUHA0fgIN6U3WViseDpSdTQriViZ7qz0Wbsf1mNQ= -github.com/sagernet/sing-quic v0.4.0-alpha.4/go.mod h1:h5RkKTmUhudJKzK7c87FPXD5w1bJjVyxMN9+opZcctA= +github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g= +github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM= 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.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= -github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= +github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= -github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d h1:zWcIQM3eAKJGzy7zhqkO7zm7ZI890OdR4vSwx2mevS0= -github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d/go.mod h1:Xhv+Mz2nE7HZTwResni8EtTa7AMJz/S6uQLT5lV23M8= -github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= -github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= -github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= +github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw= +github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs= +github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk= 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/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.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= -github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= +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/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= @@ -179,11 +177,12 @@ 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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= +github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -219,13 +218,11 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -238,37 +235,33 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= 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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-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-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -281,6 +274,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= diff --git a/test/http_test.go b/test/http_test.go index 7e72400534..0e9185ef7f 100644 --- a/test/http_test.go +++ b/test/http_test.go @@ -6,39 +6,41 @@ import ( 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" ) func TestHTTPSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeHTTP, Tag: "http-out", - HTTPOptions: option.HTTPOutboundOptions{ + Options: &option.HTTPOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/hysteria2_test.go b/test/hysteria2_test.go index 665da552c3..115af4a7b6 100644 --- a/test/hysteria2_test.go +++ b/test/hysteria2_test.go @@ -3,22 +3,36 @@ package main import ( "net/netip" "testing" + "time" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-quic/hysteria2" + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" ) func TestHysteria2Self(t *testing.T) { t.Run("self", func(t *testing.T) { - testHysteria2Self(t, "") + testHysteria2Self(t, "", false) }) t.Run("self-salamander", func(t *testing.T) { - testHysteria2Self(t, "password") + testHysteria2Self(t, "password", false) + }) + t.Run("self-hop", func(t *testing.T) { + testHysteria2Self(t, "", true) + }) + t.Run("self-hop-salamander", func(t *testing.T) { + testHysteria2Self(t, "password", true) }) } -func testHysteria2Self(t *testing.T, salamanderPassword string) { +func TestHysteria2Hop(t *testing.T) { + testHysteria2Self(t, "password", true) +} + +func testHysteria2Self(t *testing.T, salamanderPassword string, portHop bool) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") var obfs *option.Hysteria2Obfs if salamanderPassword != "" { @@ -27,23 +41,31 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { Password: salamanderPassword, } } + var ( + serverPorts []string + hopInterval time.Duration + ) + if portHop { + serverPorts = []string{F.ToString(serverPort, ":", serverPort)} + hopInterval = 5 * time.Second + } startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeHysteria2, - Hysteria2Options: option.Hysteria2InboundOptions{ + Options: &option.Hysteria2InboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, UpMbps: 100, @@ -63,22 +85,24 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeHysteria2, Tag: "hy2-out", - Hysteria2Options: option.Hysteria2OutboundOptions{ + Options: &option.Hysteria2OutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, }, - UpMbps: 100, - DownMbps: 100, - Obfs: obfs, - Password: "password", + ServerPorts: serverPorts, + HopInterval: badoption.Duration(hopInterval), + UpMbps: 100, + DownMbps: 100, + Obfs: obfs, + Password: "password", OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ TLS: &option.OutboundTLSOptions{ Enabled: true, @@ -110,17 +134,21 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { }, }) testSuitLargeUDP(t, clientPort, testPort) + if portHop { + time.Sleep(5 * time.Second) + testSuitLargeUDP(t, clientPort, testPort) + } } func TestHysteria2Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeHysteria2, - Hysteria2Options: option.Hysteria2InboundOptions{ + Options: &option.Hysteria2InboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Obfs: &option.Hysteria2Obfs{ @@ -167,21 +195,21 @@ func TestHysteria2Outbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeHysteria2, - Hysteria2Options: option.Hysteria2OutboundOptions{ + Options: &option.Hysteria2OutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/hysteria_test.go b/test/hysteria_test.go index dce00390a4..a7f73deadb 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -6,27 +6,29 @@ import ( 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" ) func TestHysteriaSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeHysteria, - HysteriaOptions: option.HysteriaInboundOptions{ + Options: &option.HysteriaInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, UpMbps: 100, @@ -46,14 +48,14 @@ func TestHysteriaSelf(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeHysteria, Tag: "hy-out", - HysteriaOptions: option.HysteriaOutboundOptions{ + Options: &option.HysteriaOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -98,12 +100,12 @@ func TestHysteriaSelf(t *testing.T) { func TestHysteriaInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeHysteria, - HysteriaOptions: option.HysteriaInboundOptions{ + Options: &option.HysteriaInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, UpMbps: 100, @@ -149,21 +151,21 @@ func TestHysteriaOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeHysteria, - HysteriaOptions: option.HysteriaOutboundOptions{ + Options: &option.HysteriaOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go index c26c81a799..93c283aac7 100644 --- a/test/inbound_detour_test.go +++ b/test/inbound_detour_test.go @@ -7,30 +7,34 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestChainedInbound(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - Detour: "detour", + InboundOptions: option.InboundOptions{ + Detour: "detour", + }, }, Method: method, Password: password, @@ -39,9 +43,9 @@ func TestChainedInbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "detour", - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Method: method, @@ -49,14 +53,14 @@ func TestChainedInbound(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ Method: method, Password: password, DialerOptions: option.DialerOptions{ @@ -67,7 +71,7 @@ func TestChainedInbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "detour-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/mux_cool_test.go b/test/mux_cool_test.go index e72f244fc5..ed42a05945 100644 --- a/test/mux_cool_test.go +++ b/test/mux_cool_test.go @@ -7,6 +7,8 @@ import ( 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/spyzhov/ajson" "github.com/stretchr/testify/require" @@ -37,12 +39,12 @@ func TestMuxCoolServer(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -81,21 +83,21 @@ func TestMuxCoolClient(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -112,22 +114,22 @@ func TestMuxCoolClient(t *testing.T) { func TestMuxCoolSelf(t *testing.T) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -139,14 +141,14 @@ func TestMuxCoolSelf(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/mux_test.go b/test/mux_test.go index 335def2e93..6454d19c7f 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -55,22 +57,22 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -81,14 +83,14 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -125,22 +127,22 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -154,14 +156,14 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/naive_test.go b/test/naive_test.go index fe3e7dce93..9dbe985588 100644 --- a/test/naive_test.go +++ b/test/naive_test.go @@ -6,19 +6,21 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/json/badoption" "github.com/sagernet/sing/common/network" ) func TestNaiveInboundWithNginx(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeNaive, - NaiveOptions: option.NaiveInboundOptions{ + Options: &option.NaiveInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Users: []auth.User{ @@ -59,12 +61,12 @@ func TestNaiveInboundWithNginx(t *testing.T) { func TestNaiveInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeNaive, - NaiveOptions: option.NaiveInboundOptions{ + Options: &option.NaiveInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []auth.User{ @@ -103,12 +105,12 @@ func TestNaiveInbound(t *testing.T) { func TestNaiveHTTP3Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeNaive, - NaiveOptions: option.NaiveInboundOptions{ + Options: &option.NaiveInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []auth.User{ diff --git a/test/shadowsocks_legacy_test.go b/test/shadowsocks_legacy_test.go index ae6f38e400..8182e6cb23 100644 --- a/test/shadowsocks_legacy_test.go +++ b/test/shadowsocks_legacy_test.go @@ -7,7 +7,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks2/shadowstream" + "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" ) func TestShadowsocksLegacy(t *testing.T) { @@ -24,21 +26,21 @@ func testShadowsocksLegacy(t *testing.T, method string) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index 0f7af7650d..3f526b106c 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -9,7 +9,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" ) @@ -99,12 +101,12 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass Cmd: []string{"-s", F.ToString("127.0.0.1:", serverPort), "-b", F.ToString("0.0.0.0:", clientPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -124,21 +126,21 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas Cmd: []string{"-s", F.ToString("0.0.0.0:", serverPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -154,22 +156,22 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas func testShadowsocksSelf(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -177,14 +179,14 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -221,22 +223,22 @@ func TestShadowsocksUoT(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -244,14 +246,14 @@ func TestShadowsocksUoT(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -289,22 +291,22 @@ func TestShadowsocksUoT(t *testing.T) { func testShadowsocks2022EIH(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -317,14 +319,14 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index 71e8d9fa2a..f64492ee61 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -10,7 +10,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" ) @@ -37,12 +39,12 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) method := shadowaead_2022.List[0] ssPassword := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -50,11 +52,14 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) { Type: C.TypeShadowTLS, Tag: "in", - ShadowTLSOptions: option.ShadowTLSInboundOptions{ + Options: &option.ShadowTLSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - Detour: "detour", + + InboundOptions: option.InboundOptions{ + Detour: "detour", + }, }, Handshake: option.ShadowTLSHandshakeOptions{ ServerOptions: option.ServerOptions{ @@ -70,9 +75,9 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) { Type: C.TypeShadowsocks, Tag: "detour", - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Method: method, @@ -80,10 +85,10 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ Method: method, Password: ssPassword, DialerOptions: option.DialerOptions{ @@ -94,7 +99,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) { Type: C.TypeShadowTLS, Tag: "detour", - ShadowTLSOptions: option.ShadowTLSOutboundOptions{ + Options: &option.ShadowTLSOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -142,12 +147,12 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) func TestShadowTLSFallback(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeShadowTLS, - ShadowTLSOptions: option.ShadowTLSInboundOptions{ + Options: &option.ShadowTLSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Handshake: option.ShadowTLSHandshakeOptions{ @@ -189,24 +194,26 @@ func TestShadowTLSInbound(t *testing.T) { Cmd: []string{"--v3", "--threads", "1", "client", "--listen", "0.0.0.0:" + F.ToString(otherPort), "--server", "127.0.0.1:" + F.ToString(serverPort), "--sni", "google.com", "--password", password}, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowTLS, - ShadowTLSOptions: option.ShadowTLSInboundOptions{ + Options: &option.ShadowTLSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - Detour: "detour", + InboundOptions: option.InboundOptions{ + Detour: "detour", + }, }, Handshake: option.ShadowTLSHandshakeOptions{ ServerOptions: option.ServerOptions{ @@ -223,23 +230,23 @@ func TestShadowTLSInbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "detour", - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), }, Method: method, Password: password, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeShadowsocks, Tag: "out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: otherPort, @@ -283,12 +290,12 @@ func TestShadowTLSOutbound(t *testing.T) { Env: []string{"RUST_LOG=trace"}, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -296,9 +303,9 @@ func TestShadowTLSOutbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "detour", - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Method: method, @@ -306,10 +313,10 @@ func TestShadowTLSOutbound(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ Method: method, Password: password, DialerOptions: option.DialerOptions{ @@ -320,7 +327,7 @@ func TestShadowTLSOutbound(t *testing.T) { { Type: C.TypeShadowTLS, Tag: "detour", - ShadowTLSOptions: option.ShadowTLSOutboundOptions{ + Options: &option.ShadowTLSOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/ss_plugin_test.go b/test/ss_plugin_test.go index 3f837b4e92..f5d1863986 100644 --- a/test/ss_plugin_test.go +++ b/test/ss_plugin_test.go @@ -6,6 +6,8 @@ import ( 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" ) func TestShadowsocksObfs(t *testing.T) { @@ -33,21 +35,21 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/tfo_test.go b/test/tfo_test.go index 458a936d6b..d74dbfb66c 100644 --- a/test/tfo_test.go +++ b/test/tfo_test.go @@ -7,28 +7,30 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestTCPSlowOpen(t *testing.T) { method := shadowaead.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, TCPFastOpen: true, }, @@ -37,14 +39,14 @@ func TestTCPSlowOpen(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/tls_test.go b/test/tls_test.go index b42d924f42..5aaf37c314 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -6,27 +6,29 @@ import ( 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" ) func TestUTLS(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -46,14 +48,14 @@ func TestUTLS(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeTrojan, Tag: "trojan-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/trojan_test.go b/test/trojan_test.go index 1a206c66a4..cceed4077c 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -6,6 +6,8 @@ import ( 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" ) func TestTrojanOutbound(t *testing.T) { @@ -20,21 +22,21 @@ func TestTrojanOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeTrojan, - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -57,22 +59,22 @@ func TestTrojanOutbound(t *testing.T) { func TestTrojanSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -92,14 +94,14 @@ func TestTrojanSelf(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeTrojan, Tag: "trojan-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -140,22 +142,22 @@ func TestTrojanSelf(t *testing.T) { func TestTrojanPlainSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -167,14 +169,14 @@ func TestTrojanPlainSelf(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeTrojan, Tag: "trojan-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/tuic_test.go b/test/tuic_test.go index 41fb759997..d5f13bece9 100644 --- a/test/tuic_test.go +++ b/test/tuic_test.go @@ -6,6 +6,8 @@ import ( 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" ) @@ -29,22 +31,22 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { udpRelayMode = "quic" } startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTUIC, - TUICOptions: option.TUICInboundOptions{ + Options: &option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TUICUser{{ @@ -62,14 +64,14 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeTUIC, Tag: "tuic-out", - TUICOptions: option.TUICOutboundOptions{ + Options: &option.TUICOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -113,12 +115,12 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { func TestTUICInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeTUIC, - TUICOptions: option.TUICInboundOptions{ + Options: &option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TUICUser{{ @@ -160,21 +162,21 @@ func TestTUICOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeTUIC, - TUICOptions: option.TUICOutboundOptions{ + Options: &option.TUICOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/v2ray_api_test.go b/test/v2ray_api_test.go index cd7ae2c445..2225703255 100644 --- a/test/v2ray_api_test.go +++ b/test/v2ray_api_test.go @@ -1,5 +1,6 @@ package main +/* import ( "context" "net/netip" @@ -8,25 +9,27 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/v2rayapi" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" ) func TestV2RayAPI(t *testing.T) { i := startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, Tag: "out", @@ -54,3 +57,4 @@ func TestV2RayAPI(t *testing.T) { require.Equal(t, count, stat.Value) } } +*/ diff --git a/test/v2ray_grpc_test.go b/test/v2ray_grpc_test.go index 5cf875435d..884cc42e0b 100644 --- a/test/v2ray_grpc_test.go +++ b/test/v2ray_grpc_test.go @@ -7,6 +7,8 @@ import ( 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" "github.com/spyzhov/ajson" @@ -27,12 +29,12 @@ func testV2RayGRPCInbound(t *testing.T, forceLite bool) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -126,23 +128,23 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/v2ray_transport_test.go b/test/v2ray_transport_test.go index 27074e78e1..b32a5cafe6 100644 --- a/test/v2ray_transport_test.go +++ b/test/v2ray_transport_test.go @@ -6,6 +6,8 @@ import ( 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" "github.com/stretchr/testify/require" @@ -44,22 +46,22 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -80,14 +82,14 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -133,22 +135,22 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -169,14 +171,14 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeTrojan, Tag: "vmess-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -224,22 +226,22 @@ func TestVMessQUICSelf(t *testing.T) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -260,14 +262,14 @@ func TestVMessQUICSelf(t *testing.T) { }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -312,22 +314,22 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO user, err := uuid.DefaultGenerator.NewV4() require.NoError(t, err) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -340,14 +342,14 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/v2ray_ws_test.go b/test/v2ray_ws_test.go index de8d4bdce6..4db4b372e3 100644 --- a/test/v2ray_ws_test.go +++ b/test/v2ray_ws_test.go @@ -7,6 +7,8 @@ import ( 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" "github.com/spyzhov/ajson" @@ -61,12 +63,12 @@ func testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeade require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -158,23 +160,23 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead }, }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/vmess_test.go b/test/vmess_test.go index 9f81d9a015..4da76c6e08 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -7,6 +7,8 @@ import ( 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" "github.com/spyzhov/ajson" @@ -181,12 +183,12 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authe }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -229,21 +231,21 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo }) startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -263,22 +265,22 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo func testVMessSelf(t *testing.T, security string, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ + Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -291,14 +293,14 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo }, }, }, - LegacyOutbounds: []option.LegacyOutbound{ + Outbounds: []option.Outbound{ { Type: C.TypeDirect, }, { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/wireguard_test.go b/test/wireguard_test.go deleted file mode 100644 index 70c0e5a558..0000000000 --- a/test/wireguard_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "net/netip" - "testing" - "time" - - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" -) - -func _TestWireGuard(t *testing.T) { - startDockerContainer(t, DockerOptions{ - Image: ImageBoringTun, - Cap: []string{"MKNOD", "NET_ADMIN", "NET_RAW"}, - Ports: []uint16{serverPort, testPort}, - Bind: map[string]string{ - "wireguard.conf": "/etc/wireguard/wg0.conf", - }, - Cmd: []string{"wg0"}, - }) - time.Sleep(5 * time.Second) - startInstance(t, option.Options{ - Inbounds: []option.LegacyInbound{ - { - Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ - ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), - ListenPort: clientPort, - }, - }, - }, - }, - LegacyOutbounds: []option.LegacyOutbound{ - { - Type: C.TypeWireGuard, - WireGuardOptions: option.WireGuardOutboundOptions{ - ServerOptions: option.ServerOptions{ - Server: "127.0.0.1", - ServerPort: serverPort, - }, - LocalAddress: []netip.Prefix{netip.MustParsePrefix("10.0.0.2/32")}, - PrivateKey: "qGnwlkZljMxeECW8fbwAWdvgntnbK7B8UmMFl3zM0mk=", - PeerPublicKey: "QsdcBm+oJw2oNv0cIFXLIq1E850lgTBonup4qnKEQBg=", - }, - }, - }, - }) - testSuitWg(t, clientPort, testPort) -} diff --git a/test/wrapper_test.go b/test/wrapper_test.go index a7c23f335e..9eb9ed7873 100644 --- a/test/wrapper_test.go +++ b/test/wrapper_test.go @@ -10,9 +10,9 @@ import ( ) func TestOptionsWrapper(t *testing.T) { - inbound := option.LegacyInbound{ + inbound := option.Inbound{ Type: C.TypeHTTP, - HTTPOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &option.InboundTLSOptions{ Enabled: true, @@ -20,13 +20,11 @@ func TestOptionsWrapper(t *testing.T) { }, }, } - rawOptions, err := inbound.RawOptions() - require.NoError(t, err) - tlsOptionsWrapper, loaded := rawOptions.(option.InboundTLSOptionsWrapper) + tlsOptionsWrapper, loaded := inbound.Options.(option.InboundTLSOptionsWrapper) require.True(t, loaded, "find inbound tls options") tlsOptions := tlsOptionsWrapper.TakeInboundTLSOptions() require.NotNil(t, tlsOptions, "find inbound tls options") tlsOptions.Enabled = false tlsOptionsWrapper.ReplaceInboundTLSOptions(tlsOptions) - require.False(t, inbound.HTTPOptions.TLS.Enabled, "replace tls enabled") + require.False(t, inbound.Options.(*option.HTTPMixedInboundOptions).TLS.Enabled, "replace tls enabled") } From 6b4cf67add1b36fef02b93054a5db2269e6b76c4 Mon Sep 17 00:00:00 2001 From: HystericalDragon Date: Fri, 17 Jan 2025 11:21:07 +0800 Subject: [PATCH 0193/1272] Fix endpoints not close --- box.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/box.go b/box.go index 1908f6dbfe..0cbf1b3b3d 100644 --- a/box.go +++ b/box.go @@ -399,7 +399,7 @@ func (s *Box) Close() error { close(s.done) } err := common.Close( - s.inbound, s.outbound, s.router, s.connection, s.network, + s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.network, ) for _, lifecycleService := range s.services { err = E.Append(err, lifecycleService.Close(), func(err error) error { From 08c1ec4b7e406293ab650112f3354410f8dbe53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 19 Jan 2025 09:39:11 +0800 Subject: [PATCH 0194/1272] Fix legacy routes --- route/route.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/route/route.go b/route/route.go index ca7474623b..bb29261c3c 100644 --- a/route/route.go +++ b/route/route.go @@ -33,7 +33,18 @@ import ( // Deprecated: use RouteConnectionEx instead. func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return r.routeConnection(ctx, conn, metadata, nil) + done := make(chan interface{}) + err := r.routeConnection(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) + if err != nil { + return err + } + select { + case <-done: + case <-r.ctx.Done(): + } + return nil } func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { @@ -141,7 +152,10 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad } func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - err := r.routePacketConnection(ctx, conn, metadata, nil) + done := make(chan interface{}) + err := r.routePacketConnection(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) if err != nil { conn.Close() if E.IsClosedOrCanceled(err) { @@ -150,6 +164,10 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m r.logger.ErrorContext(ctx, err) } } + select { + case <-done: + case <-r.ctx.Done(): + } return nil } From eeb37d89f181f323741b9dcfaeb287c593ef176d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 27 Jan 2025 09:24:55 +0800 Subject: [PATCH 0195/1272] Fix rule-set upgrade command --- cmd/sing-box/cmd_rule_set_upgrade.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/sing-box/cmd_rule_set_upgrade.go b/cmd/sing-box/cmd_rule_set_upgrade.go index e885d849e4..3d77a1d2d4 100644 --- a/cmd/sing-box/cmd_rule_set_upgrade.go +++ b/cmd/sing-box/cmd_rule_set_upgrade.go @@ -61,14 +61,15 @@ func upgradeRuleSet(sourcePath string) error { log.Info("already up-to-date") return nil } - plainRuleSet, err := plainRuleSetCompat.Upgrade() + plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade() if err != nil { return err } + plainRuleSetCompat.Version = C.RuleSetVersionCurrent buffer := new(bytes.Buffer) encoder := json.NewEncoder(buffer) encoder.SetIndent("", " ") - err = encoder.Encode(plainRuleSet) + err = encoder.Encode(plainRuleSetCompat) if err != nil { return E.Cause(err, "encode config") } From 4807e646092dfadb8eaae23a6eb729cd77e17bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 27 Jan 2025 10:03:59 +0800 Subject: [PATCH 0196/1272] documentation: Fix typo --- docs/configuration/outbound/wireguard.zh.md | 2 +- docs/configuration/route/rule_action.md | 78 ++++++++++----------- docs/configuration/route/rule_action.zh.md | 78 ++++++++++----------- 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/docs/configuration/outbound/wireguard.zh.md b/docs/configuration/outbound/wireguard.zh.md index c4e77c24f8..4597fd9d73 100644 --- a/docs/configuration/outbound/wireguard.zh.md +++ b/docs/configuration/outbound/wireguard.zh.md @@ -4,7 +4,7 @@ icon: material/delete-clock !!! failure "已在 sing-box 1.11.0 废弃" - WireGuard 出站已被启用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。 + WireGuard 出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。 !!! quote "sing-box 1.11.0 中的更改" diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index fae52e8540..31256e251e 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -31,6 +31,45 @@ Tag of target outbound. See `route-options` fields below. +### reject + +```json +{ + "action": "reject", + "method": "default", // default + "no_drop": false +} +``` + +`reject` reject connections + +The specified method is used for reject tun connections if `sniff` action has not been performed yet. + +For non-tun connections and already established connections, will just be closed. + +#### method + +- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets. +- `drop`: Drop packets. + +#### no_drop + +If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. + +Not available when `method` is set to drop. + +### hijack-dns + +```json +{ + "action": "hijack-dns" +} +``` + +`hijack-dns` hijack DNS requests to the sing-box DNS module. + +## Non-final actions + ### route-options ```json @@ -109,45 +148,6 @@ If no protocol is sniffed, the following ports will be recognized as protocols b | 443 | `quic` | | 3478 | `stun` | -### reject - -```json -{ - "action": "reject", - "method": "default", // default - "no_drop": false -} -``` - -`reject` reject connections - -The specified method is used for reject tun connections if `sniff` action has not been performed yet. - -For non-tun connections and already established connections, will just be closed. - -#### method - -- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets. -- `drop`: Drop packets. - -#### no_drop - -If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. - -Not available when `method` is set to drop. - -### hijack-dns - -```json -{ - "action": "hijack-dns" -} -``` - -`hijack-dns` hijack DNS requests to the sing-box DNS module. - -## Non-final actions - ### sniff ```json diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 2f558f4e46..544918d4fb 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -27,6 +27,45 @@ icon: material/new-box 参阅下方的 `route-options` 字段。 +### reject + +```json +{ + "action": "reject", + "method": "default", // 默认 + "no_drop": false +} +``` + +`reject` 拒绝连接。 + +如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。 + +对于非 tun 连接和已建立的连接,将直接关闭。 + +#### method + +- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。 +- `drop`: 丢弃数据包。 + +#### no_drop + +如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 + +当 `method` 设为 `drop` 时不可用。 + +### hijack-dns + +```json +{ + "action": "hijack-dns" +} +``` + +`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。 + +## 非最终动作 + ### route-options ```json @@ -107,45 +146,6 @@ UDP 连接超时时间。 | 443 | `quic` | | 3478 | `stun` | -### reject - -```json -{ - "action": "reject", - "method": "default", // 默认 - "no_drop": false -} -``` - -`reject` 拒绝连接。 - -如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。 - -对于非 tun 连接和已建立的连接,将直接关闭。 - -#### method - -- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。 -- `drop`: 丢弃数据包。 - -#### no_drop - -如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 - -当 `method` 设为 `drop` 时不可用。 - -### hijack-dns - -```json -{ - "action": "hijack-dns" -} -``` - -`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。 - -## 非最终动作 - ### sniff ```json From 3923b57abfa6c024c4d2691ed5126a8ffde38044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 27 Jan 2025 10:33:16 +0800 Subject: [PATCH 0197/1272] release: Update NDK to r28-beta3 --- .github/workflows/build.yml | 10 +++++----- .goreleaser.yaml | 2 +- cmd/internal/build_shared/sdk.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b813a66c11..000ac3e9ee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -144,7 +144,7 @@ jobs: ~/go/go1.20.14 key: go120 - name: Setup legacy Go - if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true' + if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true' run: |- wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz tar -xzf go1.20.14.linux-amd64.tar.gz @@ -159,7 +159,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: latest + version: 2.5.1 install-only: true - name: Extract signing key run: |- @@ -224,7 +224,7 @@ jobs: id: setup-ndk uses: nttld/setup-ndk@v1 with: - ndk-version: r28-beta2 + ndk-version: r28-beta3 - name: Setup OpenJDK run: |- sudo apt update && sudo apt install -y openjdk-17-jdk-headless @@ -299,7 +299,7 @@ jobs: id: setup-ndk uses: nttld/setup-ndk@v1 with: - ndk-version: r28-beta2 + ndk-version: r28-beta3 - name: Setup OpenJDK run: |- sudo apt update && sudo apt install -y openjdk-17-jdk-headless @@ -548,7 +548,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: latest + version: 2.5.1 install-only: true - name: Cache ghr uses: actions/cache@v4 diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 62136cfc96..9eb02c94f1 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -52,7 +52,7 @@ builds: env: - CGO_ENABLED=0 - GOROOT={{ .Env.GOPATH }}/go1.20.14 - gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go" + tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go" targets: - windows_amd64_v1 - windows_386 diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index 5dbefa4ad3..eab9521652 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -48,7 +48,7 @@ func FindSDK() { } func findNDK() bool { - const fixedVersion = "28.0.12674087" + const fixedVersion = "28.0.12916984" const versionFile = "source.properties" if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { androidNDKPath = fixedPath From 9b73222314e32fbe3f797aca4525fc9b0c6166dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Nov 2024 19:00:56 +0800 Subject: [PATCH 0198/1272] documentation: Bump version --- docs/changelog.md | 228 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 9a550912fc..c1ce319494 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,16 +2,242 @@ icon: material/alert-decagram --- +#### 1.11.0-rc.1 + +* Fixes and improvements + ### 1.10.7 * Fixes and improvements +#### 1.11.0-beta.20 + +* Hysteria2 `ignore_client_bandwidth` behavior update **1** +* Fixes and improvements + +**1**: + +When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC. + +See [Hysteria2](/configuration/inbound/hysteria2/#ignore_client_bandwidth). + +#### 1.11.0-beta.17 + +* Add port hopping support for Hysteria2 **1** +* Fixes and improvements + +**1**: + +See [Hysteria2](/configuration/outbound/hysteria2/). + +#### 1.11.0-beta.14 + +* Allow adding route (exclude) address sets to routes **1** +* Fixes and improvements + +**1**: + +When `auto_redirect` is not enabled, directly add `route[_exclude]_address_set` +to tun routes (equivalent to `route[_exclude]_address`). + +Note that it **doesn't work on the Android graphical client** due to +the Android VpnService not being able to handle a large number of routes (DeadSystemException), +but otherwise it works fine on all command line clients and Apple platforms. + +See [route_address_set](/configuration/inbound/tun/#route_address_set) and +[route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set). + +#### 1.11.0-beta.12 + +* Add `rule-set merge` command +* Fixes and improvements + +#### 1.11.0-beta.3 + +* Add more masquerade options for hysteria2 **1** +* Fixes and improvements + +**1**: + +See [Hysteria2](/configuration/inbound/hysteria2/#masquerade). + +#### 1.11.0-alpha.25 + +* Update quic-go to v0.48.2 +* Fixes and improvements + +#### 1.11.0-alpha.22 + +* Add UDP timeout route option **1** +* Fixes and improvements + +**1**: + +See [Rule Action](/configuration/route/rule_action/#udp_timeout). + +#### 1.11.0-alpha.20 + +* Add UDP GSO support for WireGuard +* Make GSO adaptive **1** + +**1**: + +For WireGuard outbound and endpoint, GSO will be automatically enabled when available, +see [WireGuard Outbound](/configuration/outbound/wireguard/#gso). + +For TUN, GSO has been removed, +see [Deprecated](/deprecated/#gso-option-in-tun). + +#### 1.11.0-alpha.19 + +* Upgrade WireGuard outbound to endpoint **1** +* Fixes and improvements + +**1**: + +The new WireGuard endpoint combines inbound and outbound capabilities, +and the old outbound will be removed in sing-box 1.13.0. + +See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/) +and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint). + ### 1.10.2 * Add deprecated warnings * Fix proxying websocket connections in HTTP/mixed inbounds * Fixes and improvements +#### 1.11.0-alpha.18 + +* Fixes and improvements + +#### 1.11.0-alpha.16 + +* Add `cache_capacity` DNS option **1** +* Add `override_address` and `override_port` route options **2** +* Fixes and improvements + +**1**: + +See [DNS](/configuration/dns/#cache_capacity). + +**2**: + +See [Rule Action](/configuration/route/#override_address) and +[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options). + +#### 1.11.0-alpha.15 + +* Improve multi network dialing **1** +* Fixes and improvements + +**1**: + +New options allow you to configure the network strategy flexibly. + +See [Dial Fields](/configuration/shared/dial/#network_strategy), +[Rule Action](/configuration/route/rule_action/#network_strategy) +and [Route](/configuration/route/#default_network_strategy). + +#### 1.11.0-alpha.14 + +* Add multi network dialing **1** +* Fixes and improvements + +**1**: + +Similar to Surge's strategy. + +New options allow you to connect using multiple network interfaces, +prefer or only use one type of interface, +and configure a timeout to fallback to other interfaces. + +See [Dial Fields](/configuration/shared/dial/#network_strategy), +[Rule Action](/configuration/route/rule_action/#network_strategy) +and [Route](/configuration/route/#default_network_strategy). + +#### 1.11.0-alpha.13 + +* Fixes and improvements + +#### 1.11.0-alpha.12 + +* Merge route options to route actions **1** +* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **2** +* Fixes and improvements + +**1**: + +Route options in DNS route actions will no longer be considered deprecated, +see [DNS Route Action](/configuration/dns/rule_action/). + +Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action, +see [Route Action](/configuration/route/rule_action/). + +**2**: + +When using in graphical clients, new routing rule items allow you to match on +network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled. + +See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/) +and [Headless Rule](/configuration/rule-set/headless-rule/). + +#### 1.11.0-alpha.9 + +* Improve tun compatibility **1** +* Fixes and improvements + +**1**: + +When `gvisor` tun stack is enabled, even if the request passes routing, +if the outbound connection establishment fails, +the connection still does not need to be established and a TCP RST is replied. + +#### 1.11.0-alpha.7 + +* Introducing rule actions **1** + +**1**: + +New rule actions replace legacy inbound fields and special outbound fields, +and can be used for pre-matching **2**. + +See [Rule](/configuration/route/rule/), +[Rule Action](/configuration/route/rule_action/), +[DNS Rule](/configuration/dns/rule/) and +[DNS Rule Action](/configuration/dns/rule_action/). + +For migration, see +[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions), +[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions) +and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions). + +**2**: + +Similar to Surge's pre-matching. + +Specifically, new rule actions allow you to reject connections with +TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets) +before connection established to improve tun's compatibility. + +See [Rule Action](/configuration/route/rule_action/). + +#### 1.11.0-alpha.6 + +* Update quic-go to v0.48.1 +* Set gateway for tun correctly +* Fixes and improvements + +#### 1.11.0-alpha.2 + +* Add warnings for usage of deprecated features +* Fixes and improvements + +#### 1.11.0-alpha.1 + +* Update quic-go to v0.48.0 +* Fixes and improvements + ### 1.10.1 * Fixes and improvements @@ -87,7 +313,7 @@ allows you to write headless rules directly without creating a rule-set file. **8**: -With the new access control options, not only can you allow Clash dashboards +With new access control options, not only can you allow Clash dashboards to access the Clash API on your local network, you can also manually limit the websites that can access the API instead of allowing everyone. From e64cf3b7df3a2ead890aabcab1860fd85f8b1924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 27 Jan 2025 13:40:26 +0800 Subject: [PATCH 0199/1272] Do not set address sets to routes on Apple platforms Network Extension was observed to stop for unknown reasons --- experimental/libbox/config.go | 4 --- experimental/libbox/platform.go | 1 - experimental/libbox/platform/interface.go | 1 - experimental/libbox/service.go | 14 --------- protocol/tun/inbound.go | 38 ++--------------------- 5 files changed, 2 insertions(+), 56 deletions(-) diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 6a85c963ad..159fd8f6a0 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -66,10 +66,6 @@ func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions op return nil, os.ErrInvalid } -func (s *platformInterfaceStub) UpdateRouteOptions(options *tun.Options, platformInterface option.TunPlatformOptions) error { - return os.ErrInvalid -} - func (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool { return true } diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 2503ea442b..d5951cd38f 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -9,7 +9,6 @@ type PlatformInterface interface { UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int32) error OpenTun(options TunOptions) (int32, error) - UpdateRouteOptions(options TunOptions) error WriteLog(message string) UseProcFS() bool FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index eda51b4888..ef37daea22 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -13,7 +13,6 @@ type Interface interface { UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int) error OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) - UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor Interfaces() ([]adapter.NetworkInterface, error) UnderNetworkExtension() bool diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 4906c57131..8d42d26e21 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -174,20 +174,6 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions return tun.New(*options) } -func (w *platformInterfaceWrapper) UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error { - if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 { - return E.New("android: unsupported uid options") - } - if len(options.IncludeAndroidUser) > 0 { - return E.New("android: unsupported android_user option") - } - routeRanges, err := options.BuildAutoRouteRanges(true) - if err != nil { - return err - } - return w.iif.UpdateRouteOptions(&tunOptions{options, routeRanges, platformOptions}) -} - func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 34a5db2b84..3bbe042116 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -305,7 +305,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error { if t.tunOptions.Name == "" { t.tunOptions.Name = tun.CalculateInterfaceName("") } - if t.platformInterface == nil || runtime.GOOS != "android" { + if t.platformInterface == nil { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) for _, routeRuleSet := range t.routeRuleSet { ipSets := routeRuleSet.ExtractIPSet() @@ -421,41 +421,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error { func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) - if t.autoRedirect != nil { - t.autoRedirect.UpdateRouteAddressSet() - } else { - tunOptions := t.tunOptions - for _, ipSet := range t.routeAddressSet { - for _, prefix := range ipSet.Prefixes() { - if prefix.Addr().Is4() { - tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix) - } else { - tunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix) - } - } - } - for _, ipSet := range t.routeExcludeAddressSet { - for _, prefix := range ipSet.Prefixes() { - if prefix.Addr().Is4() { - tunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix) - } else { - tunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix) - } - } - } - if t.platformInterface != nil { - err := t.platformInterface.UpdateRouteOptions(&tunOptions, t.platformOptions) - if err != nil { - t.logger.Error("update route addresses: ", err) - } - } else { - err := t.tunIf.UpdateRouteOptions(tunOptions) - if err != nil { - t.logger.Error("update route addresses: ", err) - } - } - t.logger.Info("updated route addresses") - } + t.autoRedirect.UpdateRouteAddressSet() t.routeAddressSet = nil t.routeExcludeAddressSet = nil } From d09d2fb665f1a4aa0cefb1ce0298539867ba487b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 27 Jan 2025 13:22:08 +0800 Subject: [PATCH 0200/1272] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 120 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/clients/android b/clients/android index b17fb6d857..599d8cecac 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit b17fb6d8570251ff1d0494d07d77b45f44006607 +Subproject commit 599d8cecacf2ef732b92b3153c08b1b45074661c diff --git a/clients/apple b/clients/apple index 64a4614aca..e43e3a3f64 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 64a4614aca01e1e9b8369ab9f542da737b3f7619 +Subproject commit e43e3a3f64886c5ebaa8e41348b4a4e048a0b5b1 diff --git a/docs/changelog.md b/docs/changelog.md index c1ce319494..c37bdf7167 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,9 +2,125 @@ icon: material/alert-decagram --- -#### 1.11.0-rc.1 +### 1.11.0 -* Fixes and improvements +Important changes since 1.10: + +* Introducing rule actions **1** +* Improve tun compatibility **3** +* Merge route options to route actions **4** +* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5** +* Add multi network dialing **6** +* Add `cache_capacity` DNS option **7** +* Add `override_address` and `override_port` route options **8** +* Upgrade WireGuard outbound to endpoint **9** +* Add UDP GSO support for WireGuard +* Make GSO adaptive **10** +* Add UDP timeout route option **11** +* Add more masquerade options for hysteria2 **12** +* Add `rule-set merge` command +* Add port hopping support for Hysteria2 **13** +* Hysteria2 `ignore_client_bandwidth` behavior update **14** + +**1**: + +New rule actions replace legacy inbound fields and special outbound fields, +and can be used for pre-matching **2**. + +See [Rule](/configuration/route/rule/), +[Rule Action](/configuration/route/rule_action/), +[DNS Rule](/configuration/dns/rule/) and +[DNS Rule Action](/configuration/dns/rule_action/). + +For migration, see +[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions), +[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions) +and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions). + +**2**: + +Similar to Surge's pre-matching. + +Specifically, new rule actions allow you to reject connections with +TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets) +before connection established to improve tun's compatibility. + +See [Rule Action](/configuration/route/rule_action/). + +**3**: + +When `gvisor` tun stack is enabled, even if the request passes routing, +if the outbound connection establishment fails, +the connection still does not need to be established and a TCP RST is replied. + +**4**: + +Route options in DNS route actions will no longer be considered deprecated, +see [DNS Route Action](/configuration/dns/rule_action/). + +Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action, +see [Route Action](/configuration/route/rule_action/). + +**5**: + +When using in graphical clients, new routing rule items allow you to match on +network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled. + +See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/) +and [Headless Rule](/configuration/rule-set/headless-rule/). + +**6**: + +Similar to Surge's strategy. + +New options allow you to connect using multiple network interfaces, +prefer or only use one type of interface, +and configure a timeout to fallback to other interfaces. + +See [Dial Fields](/configuration/shared/dial/#network_strategy), +[Rule Action](/configuration/route/rule_action/#network_strategy) +and [Route](/configuration/route/#default_network_strategy). + +**7**: + +See [DNS](/configuration/dns/#cache_capacity). + +**8**: + +See [Rule Action](/configuration/route/#override_address) and +[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options). + +**9**: + +The new WireGuard endpoint combines inbound and outbound capabilities, +and the old outbound will be removed in sing-box 1.13.0. + +See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/) +and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint). + +**10**: + +For WireGuard outbound and endpoint, GSO will be automatically enabled when available, +see [WireGuard Outbound](/configuration/outbound/wireguard/#gso). + +For TUN, GSO has been removed, +see [Deprecated](/deprecated/#gso-option-in-tun). + +**11**: + +See [Rule Action](/configuration/route/rule_action/#udp_timeout). + +**12**: + +See [Hysteria2](/configuration/inbound/hysteria2/#masquerade). + +**13**: + +See [Hysteria2](/configuration/outbound/hysteria2/). + +**14**: + +When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC. ### 1.10.7 From bab8dc0b820199ccc47f1bbcceb0037ee5a1fd28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 31 Jan 2025 12:57:35 +0800 Subject: [PATCH 0201/1272] Fix missing handshake for early conn --- route/conn.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/route/conn.go b/route/conn.go index e010c2cdb6..cbed3491d8 100644 --- a/route/conn.go +++ b/route/conn.go @@ -225,6 +225,17 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader } break } + if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destination); isEarlyConn && earlyConn.NeedHandshake() { + _, err := destination.Write(nil) + if err != nil { + if !direction { + m.logger.ErrorContext(ctx, "connection upload handshake: ", err) + } else { + m.logger.ErrorContext(ctx, "connection download handshake: ", err) + } + return + } + } _, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters) if err != nil { common.Close(originDestination) From 27c31eac5d8626251f61f9f6e89961285e2f3bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 1 Feb 2025 19:42:21 +0800 Subject: [PATCH 0202/1272] Fix local rule-set not updated --- adapter/experimental.go | 10 +++--- experimental/cachefile/cache.go | 6 ++-- route/rule/rule_set_local.go | 39 +++++++++++++++-------- route/rule/rule_set_remote.go | 56 ++++++++++++++++----------------- 4 files changed, 62 insertions(+), 49 deletions(-) diff --git a/adapter/experimental.go b/adapter/experimental.go index f22ff9b22a..648eb41880 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -39,17 +39,17 @@ type CacheFile interface { StoreSelected(group string, selected string) error LoadGroupExpand(group string) (isExpand bool, loaded bool) StoreGroupExpand(group string, expand bool) error - LoadRuleSet(tag string) *SavedRuleSet - SaveRuleSet(tag string, set *SavedRuleSet) error + LoadRuleSet(tag string) *SavedBinary + SaveRuleSet(tag string, set *SavedBinary) error } -type SavedRuleSet struct { +type SavedBinary struct { Content []byte LastUpdated time.Time LastEtag string } -func (s *SavedRuleSet) MarshalBinary() ([]byte, error) { +func (s *SavedBinary) MarshalBinary() ([]byte, error) { var buffer bytes.Buffer err := binary.Write(&buffer, binary.BigEndian, uint8(1)) if err != nil { @@ -70,7 +70,7 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) { return buffer.Bytes(), nil } -func (s *SavedRuleSet) UnmarshalBinary(data []byte) error { +func (s *SavedBinary) UnmarshalBinary(data []byte) error { reader := bytes.NewReader(data) var version uint8 err := binary.Read(reader, binary.BigEndian, &version) diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 498b9474fd..88cffdbe93 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -284,8 +284,8 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error { }) } -func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet { - var savedSet adapter.SavedRuleSet +func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary { + var savedSet adapter.SavedBinary err := c.DB.View(func(t *bbolt.Tx) error { bucket := c.bucket(t, bucketRuleSet) if bucket == nil { @@ -303,7 +303,7 @@ func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet { return &savedSet } -func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error { +func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error { return c.DB.Batch(func(t *bbolt.Tx) error { bucket, err := c.createBucket(t, bucketRuleSet) if err != nil { diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index efbc525ee6..2b44c50bbb 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strings" + "sync" "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" @@ -26,14 +27,16 @@ import ( var _ adapter.RuleSet = (*LocalRuleSet)(nil) type LocalRuleSet struct { - ctx context.Context - logger logger.Logger - tag string - rules []adapter.HeadlessRule - metadata adapter.RuleSetMetadata - fileFormat string - watcher *fswatch.Watcher - refs atomic.Int32 + ctx context.Context + logger logger.Logger + tag string + rules []adapter.HeadlessRule + metadata adapter.RuleSetMetadata + fileFormat string + watcher *fswatch.Watcher + callbackAccess sync.Mutex + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 } func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { @@ -52,13 +55,12 @@ func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.R return nil, err } } else { - err := ruleSet.reloadFile(filemanager.BasePath(ctx, options.LocalOptions.Path)) + filePath := filemanager.BasePath(ctx, options.LocalOptions.Path) + filePath, _ = filepath.Abs(options.LocalOptions.Path) + err := ruleSet.reloadFile(filePath) if err != nil { return nil, err } - } - if options.Type == C.RuleSetTypeLocal { - filePath, _ := filepath.Abs(options.LocalOptions.Path) watcher, err := fswatch.NewWatcher(fswatch.Options{ Path: []string{filePath}, Callback: func(path string) { @@ -141,6 +143,12 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error { metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule) s.rules = rules s.metadata = metadata + s.callbackAccess.Lock() + callbacks := s.callbacks.Array() + s.callbackAccess.Unlock() + for _, callback := range callbacks { + callback(s) + } return nil } @@ -173,10 +181,15 @@ func (s *LocalRuleSet) Cleanup() { } func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { - return nil + s.callbackAccess.Lock() + defer s.callbackAccess.Unlock() + return s.callbacks.PushBack(callback) } func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { + s.callbackAccess.Lock() + defer s.callbackAccess.Unlock() + s.callbacks.Remove(element) } func (s *LocalRuleSet) Close() error { diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 830e19f7cf..0c5d9bb3d6 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -33,23 +33,23 @@ import ( var _ adapter.RuleSet = (*RemoteRuleSet)(nil) type RemoteRuleSet struct { - ctx context.Context - cancel context.CancelFunc - outboundManager adapter.OutboundManager - logger logger.ContextLogger - options option.RuleSet - metadata adapter.RuleSetMetadata - updateInterval time.Duration - dialer N.Dialer - rules []adapter.HeadlessRule - lastUpdated time.Time - lastEtag string - updateTicker *time.Ticker - cacheFile adapter.CacheFile - pauseManager pause.Manager - callbackAccess sync.Mutex - callbacks list.List[adapter.RuleSetUpdateCallback] - refs atomic.Int32 + ctx context.Context + cancel context.CancelFunc + logger logger.ContextLogger + outbound adapter.OutboundManager + options option.RuleSet + metadata adapter.RuleSetMetadata + updateInterval time.Duration + dialer N.Dialer + rules []adapter.HeadlessRule + lastUpdated time.Time + lastEtag string + updateTicker *time.Ticker + cacheFile adapter.CacheFile + pauseManager pause.Manager + callbackAccess sync.Mutex + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 } func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { @@ -61,13 +61,13 @@ func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options updateInterval = 24 * time.Hour } return &RemoteRuleSet{ - ctx: ctx, - cancel: cancel, - outboundManager: service.FromContext[adapter.OutboundManager](ctx), - logger: logger, - options: options, - updateInterval: updateInterval, - pauseManager: service.FromContext[pause.Manager](ctx), + ctx: ctx, + cancel: cancel, + outbound: service.FromContext[adapter.OutboundManager](ctx), + logger: logger, + options: options, + updateInterval: updateInterval, + pauseManager: service.FromContext[pause.Manager](ctx), } } @@ -83,13 +83,13 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter. s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx) var dialer N.Dialer if s.options.RemoteOptions.DownloadDetour != "" { - outbound, loaded := s.outboundManager.Outbound(s.options.RemoteOptions.DownloadDetour) + outbound, loaded := s.outbound.Outbound(s.options.RemoteOptions.DownloadDetour) if !loaded { - return E.New("download_detour not found: ", s.options.RemoteOptions.DownloadDetour) + return E.New("download detour not found: ", s.options.RemoteOptions.DownloadDetour) } dialer = outbound } else { - dialer = s.outboundManager.Default() + dialer = s.outbound.Default() } s.dialer = dialer if s.cacheFile != nil { @@ -286,7 +286,7 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT } s.lastUpdated = time.Now() if s.cacheFile != nil { - err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{ + err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedBinary{ LastUpdated: s.lastUpdated, Content: content, LastEtag: s.lastEtag, From 9b4c11ba9597f223d21084e75eeb32ca4b7da930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 1 Feb 2025 19:42:32 +0800 Subject: [PATCH 0203/1272] Fix rule-set not closed --- route/router.go | 7 +++++++ route/rule/rule_set_local.go | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/route/router.go b/route/router.go index 642340d4ab..68f5dc3524 100644 --- a/route/router.go +++ b/route/router.go @@ -484,6 +484,13 @@ func (r *Router) Close() error { }) monitor.Finish() } + for i, ruleSet := range r.ruleSets { + monitor.Start("close rule-set[", i, "]") + err = E.Append(err, ruleSet.Close(), func(err error) error { + return E.Cause(err, "close rule-set[", i, "]") + }) + monitor.Finish() + } return err } diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index 2b44c50bbb..4f2fabcc9f 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -56,7 +56,7 @@ func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.R } } else { filePath := filemanager.BasePath(ctx, options.LocalOptions.Path) - filePath, _ = filepath.Abs(options.LocalOptions.Path) + filePath, _ = filepath.Abs(filePath) err := ruleSet.reloadFile(filePath) if err != nil { return nil, err From 7f79458b4f3dd4c2fed470e2a79e5619d6c70f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 1 Feb 2025 19:42:53 +0800 Subject: [PATCH 0204/1272] Minor updates --- cmd/sing-box/cmd_generate_tls.go | 2 +- common/tls/mkcert.go | 12 ++++++++---- common/tls/std_server.go | 2 +- experimental/locale/locale.go | 2 ++ experimental/locale/locale_zh_CN.go | 1 + 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cmd/sing-box/cmd_generate_tls.go b/cmd/sing-box/cmd_generate_tls.go index d871566fac..4cd4060d1d 100644 --- a/cmd/sing-box/cmd_generate_tls.go +++ b/cmd/sing-box/cmd_generate_tls.go @@ -30,7 +30,7 @@ func init() { } func generateTLSKeyPair(serverName string) error { - privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0)) + privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0)) if err != nil { return err } diff --git a/common/tls/mkcert.go b/common/tls/mkcert.go index 1e71a76315..12680c4834 100644 --- a/common/tls/mkcert.go +++ b/common/tls/mkcert.go @@ -11,8 +11,8 @@ import ( "time" ) -func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) { - privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour)) +func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) { + privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour)) if err != nil { return nil, err } @@ -23,7 +23,7 @@ func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Cer return &certificate, err } -func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) { +func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) { if timeFunc == nil { timeFunc = time.Now } @@ -47,7 +47,11 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.T }, DNSNames: []string{serverName}, } - publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) + if parent == nil { + parent = template + parentKey = key + } + publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey) if err != nil { return } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 8eab87da0f..1e01bc5003 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -222,7 +222,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound } if certificate == nil && key == nil && options.Insecure { tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { - return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName) + return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName) } } else { if certificate == nil { diff --git a/experimental/locale/locale.go b/experimental/locale/locale.go index b17367802a..e5575af4eb 100644 --- a/experimental/locale/locale.go +++ b/experimental/locale/locale.go @@ -7,11 +7,13 @@ var ( type Locale struct { // deprecated messages for graphical clients + Locale string DeprecatedMessage string DeprecatedMessageNoLink string } var defaultLocal = &Locale{ + Locale: "en_US", DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.", DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.", } diff --git a/experimental/locale/locale_zh_CN.go b/experimental/locale/locale_zh_CN.go index 5db2f27406..f5605d9d16 100644 --- a/experimental/locale/locale_zh_CN.go +++ b/experimental/locale/locale_zh_CN.go @@ -4,6 +4,7 @@ var warningMessageForEndUsers = "\n\n如果您不明白此消息意味着什么 func init() { localeRegistry["zh_CN"] = &Locale{ + Locale: "zh_CN", DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers, DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers, } From 0908627297d50cbe5259b86c563e1ead142727e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 5 Feb 2025 08:58:10 +0800 Subject: [PATCH 0205/1272] Fix crash on remote rule-set stop --- route/rule/rule_set_remote.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 0c5d9bb3d6..9e0c172927 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -301,8 +301,10 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT func (s *RemoteRuleSet) Close() error { s.rules = nil - s.updateTicker.Stop() s.cancel() + if s.updateTicker != nil { + s.updateTicker.Stop() + } return nil } From 92d245ad040cbda2f84b21c2a847a470e532c179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 5 Feb 2025 09:55:38 +0800 Subject: [PATCH 0206/1272] 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 599d8cecac..6df2f60c3a 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 599d8cecacf2ef732b92b3153c08b1b45074661c +Subproject commit 6df2f60c3ad555f6321ec722e1d4f8f609019825 diff --git a/clients/apple b/clients/apple index e43e3a3f64..17fed6b9fc 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit e43e3a3f64886c5ebaa8e41348b4a4e048a0b5b1 +Subproject commit 17fed6b9fcc51aad61638f9aea8827dd1a8f1f0d diff --git a/docs/changelog.md b/docs/changelog.md index c37bdf7167..be9e76c5a2 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +### 1.11.1 + +* Fixes and improvements + ### 1.11.0 Important changes since 1.10: From c9fb99b79950f47616ed0a04a471c14c5417b6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 6 Feb 2025 08:48:49 +0800 Subject: [PATCH 0207/1272] Fix missing ENOTCONN in IsClosed check --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1ad0c046d7..89b67b43e0 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.48.2-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.0-beta.12 + github.com/sagernet/sing v0.6.0 github.com/sagernet/sing-dns v0.4.0-beta.2 github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-quic v0.4.0-beta.4 diff --git a/go.sum b/go.sum index d20329135e..12afbc3e4c 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/ 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.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s= -github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.0 h1:jT55zAXrG7H3x+s/FlrC15xQy3LcmuZ2GGA9+8IJdt0= +github.com/sagernet/sing v0.6.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY= github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= From 0d443072d1c2cdb67fe6f503ce5fc85a8765a4dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 6 Feb 2025 08:49:25 +0800 Subject: [PATCH 0208/1272] Fix panic in auto-redirect initialize --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 89b67b43e0..70f32e31b1 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 - github.com/sagernet/sing-tun v0.6.0-beta.8 + github.com/sagernet/sing-tun v0.6.0 github.com/sagernet/sing-vmess v0.2.0-beta.2 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 12afbc3e4c..c752c78ee2 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= -github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw= -github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-tun v0.6.0 h1:VBUb4scl74g9ngBxydCMneEZQ07Oiwx4ZFYL9YYYv34= +github.com/sagernet/sing-tun v0.6.0/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs= github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= From a0d4421085b5163c2a05239e44e0c30c49250329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 6 Feb 2025 08:50:21 +0800 Subject: [PATCH 0209/1272] Update quic-go to v0.49.0 --- go.mod | 10 +++++----- go.sum | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 70f32e31b1..894641551d 100644 --- a/go.mod +++ b/go.mod @@ -24,12 +24,12 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.4 github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff - github.com/sagernet/quic-go v0.48.2-beta.1 + github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.0 github.com/sagernet/sing-dns v0.4.0-beta.2 github.com/sagernet/sing-mux v0.3.0-alpha.1 - github.com/sagernet/sing-quic v0.4.0-beta.4 + github.com/sagernet/sing-quic v0.4.0 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 @@ -43,11 +43,11 @@ require ( github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.31.0 + golang.org/x/crypto v0.32.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/mod v0.20.0 - golang.org/x/net v0.31.0 - golang.org/x/sys v0.28.0 + golang.org/x/net v0.34.0 + golang.org/x/sys v0.29.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 diff --git a/go.sum b/go.sum index c752c78ee2..dc6b48275e 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,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.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg= -github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= +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= @@ -125,8 +125,8 @@ github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250j github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= -github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g= -github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM= +github.com/sagernet/sing-quic v0.4.0 h1:E4geazHk/UrJTXMlT+CBCKmn8V86RhtNeczWtfeoEFc= +github.com/sagernet/sing-quic v0.4.0/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= @@ -172,16 +172,16 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -191,10 +191,10 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= From 17b502bb4bdc3c7f3966bbad49b18179b814843e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 6 Feb 2025 08:58:56 +0800 Subject: [PATCH 0210/1272] Update dependencies --- go.mod | 20 ++++++++++---------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 894641551d..2448e4ca6d 100644 --- a/go.mod +++ b/go.mod @@ -6,16 +6,16 @@ require ( github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 - github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.3.0 - github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 + github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 github.com/libdns/alidns v1.0.3 github.com/libdns/cloudflare v0.1.1 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa + github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 github.com/mholt/acmez v1.2.0 - github.com/miekg/dns v1.1.62 + github.com/miekg/dns v1.1.63 github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a @@ -27,27 +27,27 @@ require ( github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.0 - github.com/sagernet/sing-dns v0.4.0-beta.2 - github.com/sagernet/sing-mux v0.3.0-alpha.1 + github.com/sagernet/sing-dns v0.4.0 + github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.0 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 - github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 + github.com/sagernet/sing-shadowtls v0.2.0 github.com/sagernet/sing-tun v0.6.0 - github.com/sagernet/sing-vmess v0.2.0-beta.2 + github.com/sagernet/sing-vmess v0.2.0 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.1-beta.5 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.8.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.32.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/mod v0.20.0 golang.org/x/net v0.34.0 - golang.org/x/sys v0.29.0 + golang.org/x/sys v0.30.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 diff --git a/go.sum b/go.sum index dc6b48275e..7a36c17198 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +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/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -47,8 +47,8 @@ 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/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-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= +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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= @@ -74,12 +74,12 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4= -github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw= +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 v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= -github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= @@ -121,22 +121,22 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.6.0 h1:jT55zAXrG7H3x+s/FlrC15xQy3LcmuZ2GGA9+8IJdt0= github.com/sagernet/sing v0.6.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY= -github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= -github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= -github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= +github.com/sagernet/sing-dns v0.4.0 h1:+mNoOuR3nljjouCH+qMg4zHI1+R9T2ReblGFkZPEndc= +github.com/sagernet/sing-dns v0.4.0/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= +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.0 h1:E4geazHk/UrJTXMlT+CBCKmn8V86RhtNeczWtfeoEFc= github.com/sagernet/sing-quic v0.4.0/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.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= -github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= +github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= +github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= github.com/sagernet/sing-tun v0.6.0 h1:VBUb4scl74g9ngBxydCMneEZQ07Oiwx4ZFYL9YYYv34= github.com/sagernet/sing-tun v0.6.0/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= -github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs= -github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk= +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/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= @@ -152,8 +152,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -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/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= @@ -191,8 +191,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From a2d40eb8b8f53f9c5059223a0d415e03f0a0f0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 6 Feb 2025 11:20:18 +0800 Subject: [PATCH 0211/1272] Fix override UDP destination --- route/route.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/route/route.go b/route/route.go index bb29261c3c..bad7922ba8 100644 --- a/route/route.go +++ b/route/route.go @@ -489,12 +489,8 @@ match: break match } } - if !preMatch && inputPacketConn != nil && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() { - var timeout time.Duration - if metadata.InboundType == C.TypeSOCKS { - timeout = C.TCPTimeout - } - newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: timeout}, inputConn, inputPacketConn) + if !preMatch && inputPacketConn != nil && (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() { + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: C.TCPTimeout}, inputConn, inputPacketConn) if newErr != nil { fatalErr = newErr return @@ -590,7 +586,7 @@ func (r *Router) actionSniff( return } } else { - if !metadata.Destination.Addr.IsGlobalUnicast() { + if (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() { metadata.Destination = destination } if len(packetBuffers) > 0 { From 8d6c4f1289f31b28d955da05c59d9a297aed50bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 31 Jan 2025 11:59:35 +0800 Subject: [PATCH 0212/1272] release: Skip testflight when another build in review --- cmd/internal/app_store_connect/main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go index ef00673835..6eb1e7e087 100644 --- a/cmd/internal/app_store_connect/main.go +++ b/cmd/internal/app_store_connect/main.go @@ -5,6 +5,7 @@ import ( "net/http" "os" "strconv" + "strings" "time" "github.com/sagernet/asc-go/asc" @@ -194,6 +195,10 @@ func publishTestflight(ctx context.Context) error { 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 } } From abc38d1dab6640bd23e93f90069f4ea9398de971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 10 Feb 2025 15:11:26 +0800 Subject: [PATCH 0213/1272] Fix udpnat2 crash --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2448e4ca6d..511aea0b76 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.0 + github.com/sagernet/sing v0.6.1 github.com/sagernet/sing-dns v0.4.0 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.0 diff --git a/go.sum b/go.sum index 7a36c17198..42abdb9b15 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.0 h1:jT55zAXrG7H3x+s/FlrC15xQy3LcmuZ2GGA9+8IJdt0= -github.com/sagernet/sing v0.6.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.1 h1:mJ6e7Ir2wtCoGLbdnnXWBsNJu5YHtbXmv66inoE0zFA= +github.com/sagernet/sing v0.6.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0 h1:+mNoOuR3nljjouCH+qMg4zHI1+R9T2ReblGFkZPEndc= github.com/sagernet/sing-dns v0.4.0/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= From 4eed46ac5998945b604e4d0152907f61d3b1c50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 10 Feb 2025 15:12:03 +0800 Subject: [PATCH 0214/1272] Fix respond ICMP echo --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 511aea0b76..b75ceb3498 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0 - github.com/sagernet/sing-tun v0.6.0 + github.com/sagernet/sing-tun v0.6.1 github.com/sagernet/sing-vmess v0.2.0 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 42abdb9b15..9ffef2d4fc 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= -github.com/sagernet/sing-tun v0.6.0 h1:VBUb4scl74g9ngBxydCMneEZQ07Oiwx4ZFYL9YYYv34= -github.com/sagernet/sing-tun v0.6.0/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-tun v0.6.1 h1:4l0+gnEKcGjlWfUVTD+W0BRApqIny/lU2ZliurE+VMo= +github.com/sagernet/sing-tun v0.6.1/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= From 9db2d58545b4de9a3453b296125eed0fe823ad17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 2 Feb 2025 23:17:31 +0800 Subject: [PATCH 0215/1272] Fix override address --- route/conn.go | 6 ++++++ route/route.go | 2 +- route/rule/rule_action.go | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/route/conn.go b/route/conn.go index cbed3491d8..1c0d3c90e5 100644 --- a/route/conn.go +++ b/route/conn.go @@ -159,6 +159,12 @@ 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.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination { + if metadata.UDPDisableDomainUnmapping { + remotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination) + } else { + remotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination) + } } var udpTimeout time.Duration if metadata.UDPTimeout > 0 { diff --git a/route/route.go b/route/route.go index bad7922ba8..edf6063ea5 100644 --- a/route/route.go +++ b/route/route.go @@ -586,7 +586,7 @@ func (r *Router) actionSniff( return } } else { - if (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() { + if (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() && !metadata.RouteOriginalDestination.IsValid() { metadata.Destination = destination } if len(packetBuffers) > 0 { diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index f4f2299ae7..8989ff3c86 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -162,6 +162,24 @@ func (r *RuleActionRouteOptions) Type() string { func (r *RuleActionRouteOptions) String() string { var descriptions []string + if r.OverrideAddress.IsValid() { + descriptions = append(descriptions, F.ToString("override-address=", r.OverrideAddress.AddrString())) + } + if r.OverridePort > 0 { + descriptions = append(descriptions, F.ToString("override-port=", r.OverridePort)) + } + if r.NetworkStrategy != nil { + descriptions = append(descriptions, F.ToString("network-strategy=", r.NetworkStrategy)) + } + if r.NetworkType != nil { + descriptions = append(descriptions, F.ToString("network-type=", strings.Join(common.Map(r.NetworkType, C.InterfaceType.String), ","))) + } + if r.FallbackNetworkType != nil { + descriptions = append(descriptions, F.ToString("fallback-network-type="+strings.Join(common.Map(r.NetworkType, C.InterfaceType.String), ","))) + } + if r.FallbackDelay > 0 { + descriptions = append(descriptions, F.ToString("fallback-delay=", r.FallbackDelay.String())) + } if r.UDPDisableDomainUnmapping { descriptions = append(descriptions, "udp-disable-domain-unmapping") } From 986a410b3066a6129c3deb0175476977aafcf3aa Mon Sep 17 00:00:00 2001 From: printfer <16911871+printfer@users.noreply.github.com> Date: Mon, 10 Feb 2025 02:35:20 -0400 Subject: [PATCH 0216/1272] documentation: Fix migration links --- docs/migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migration.md b/docs/migration.md index c8b876f7cf..4943bb86fe 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -108,7 +108,7 @@ Inbound fields are deprecated and can be replaced by rule actions. !!! info "References" - [Listen Fields](/configuration/inbound/listen/) / + [Listen Fields](/configuration/shared/listen/) / [Rule](/configuration/route/rule/) / [Rule Action](/configuration/route/rule_action/) / [DNS Rule](/configuration/dns/rule/) / From badfdb62cd2030430a496325d5c6ad4152ebc3ab Mon Sep 17 00:00:00 2001 From: ReleTor <191429954+reletor@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:33:41 +0800 Subject: [PATCH 0217/1272] documentation: Fixes --- docs/configuration/index.zh.md | 2 ++ docs/configuration/outbound/block.md | 2 +- docs/configuration/rule-set/index.md | 2 +- docs/configuration/rule-set/index.zh.md | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/configuration/index.zh.md b/docs/configuration/index.zh.md index 6aeb4857f4..b639c72a28 100644 --- a/docs/configuration/index.zh.md +++ b/docs/configuration/index.zh.md @@ -8,6 +8,7 @@ sing-box 使用 JSON 作为配置文件格式。 { "log": {}, "dns": {}, + "ntp": {}, "endpoints": [], "inbounds": [], "outbounds": [], @@ -22,6 +23,7 @@ sing-box 使用 JSON 作为配置文件格式。 |----------------|------------------------| | `log` | [日志](./log/) | | `dns` | [DNS](./dns/) | +| `ntp` | [NTP](./ntp/) | | `endpoints` | [端点](./endpoint/) | | `inbounds` | [入站](./inbound/) | | `outbounds` | [出站](./outbound/) | diff --git a/docs/configuration/outbound/block.md b/docs/configuration/outbound/block.md index f29120ccf2..1be04dd5b6 100644 --- a/docs/configuration/outbound/block.md +++ b/docs/configuration/outbound/block.md @@ -8,7 +8,7 @@ icon: material/delete-clock ### Structure -```json F +```json { "type": "block", "tag": "block" diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index bed3fb5466..c905e110fb 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -74,7 +74,7 @@ Tag of rule-set. ==Required== -List of [Headless Rule](../headless-rule/). +List of [Headless Rule](./headless-rule/). ### Local or Remote Fields diff --git a/docs/configuration/rule-set/index.zh.md b/docs/configuration/rule-set/index.zh.md index 083c06bd3a..c3b86b6d84 100644 --- a/docs/configuration/rule-set/index.zh.md +++ b/docs/configuration/rule-set/index.zh.md @@ -74,7 +74,7 @@ icon: material/new-box ==必填== -一组 [无头规则](../headless-rule/). +一组 [无头规则](./headless-rule/). ### 本地或远程字段 From 07ac01dcb794119b5d943c6950d1402ea1f9f82d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 10 Feb 2025 15:44:49 +0800 Subject: [PATCH 0218/1272] platform: Update NDK to r28 --- .github/workflows/build.yml | 6 +++--- cmd/internal/build_shared/sdk.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 000ac3e9ee..61f14cfb9c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -153,7 +153,7 @@ jobs: if: matrix.goos == 'android' uses: nttld/setup-ndk@v1 with: - ndk-version: r28-beta2 + ndk-version: r28 local-cache: true - name: Setup Goreleaser uses: goreleaser/goreleaser-action@v6 @@ -224,7 +224,7 @@ jobs: id: setup-ndk uses: nttld/setup-ndk@v1 with: - ndk-version: r28-beta3 + ndk-version: r28 - name: Setup OpenJDK run: |- sudo apt update && sudo apt install -y openjdk-17-jdk-headless @@ -299,7 +299,7 @@ jobs: id: setup-ndk uses: nttld/setup-ndk@v1 with: - ndk-version: r28-beta3 + ndk-version: r28 - name: Setup OpenJDK run: |- sudo apt update && sudo apt install -y openjdk-17-jdk-headless diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index eab9521652..5061c32177 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -48,7 +48,7 @@ func FindSDK() { } func findNDK() bool { - const fixedVersion = "28.0.12916984" + const fixedVersion = "28.0.13004108" const versionFile = "source.properties" if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { androidNDKPath = fixedPath From fbe390268c7334928f304dc63e84643d3b44df0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 10 Feb 2025 19:11:26 +0800 Subject: [PATCH 0219/1272] 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 6df2f60c3a..601e5c1571 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 6df2f60c3ad555f6321ec722e1d4f8f609019825 +Subproject commit 601e5c1571261691c360d3a017a7aab0551f2b56 diff --git a/clients/apple b/clients/apple index 17fed6b9fc..ae8c4ceb6e 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 17fed6b9fcc51aad61638f9aea8827dd1a8f1f0d +Subproject commit ae8c4ceb6e74fc07780075530747d9eeec1888f1 diff --git a/docs/changelog.md b/docs/changelog.md index be9e76c5a2..8a8dd9e359 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +### 1.11.2 + +* Fixes and improvements + ### 1.11.1 * Fixes and improvements From ff31c469a061656f17aafb29b35d3fc297a66439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 11 Feb 2025 14:41:41 +0800 Subject: [PATCH 0220/1272] Override version --- clients/android | 2 +- clients/apple | 2 +- cmd/internal/update_android_version/main.go | 6 ++++-- docs/changelog.md | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/clients/android b/clients/android index 601e5c1571..3a2fc9c880 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 601e5c1571261691c360d3a017a7aab0551f2b56 +Subproject commit 3a2fc9c8802f0c40f0b1fd2d7acdcabb7aa0855f diff --git a/clients/apple b/clients/apple index ae8c4ceb6e..3d5d7343fb 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit ae8c4ceb6e74fc07780075530747d9eeec1888f1 +Subproject commit 3d5d7343fb473dce772633183791524629493213 diff --git a/cmd/internal/update_android_version/main.go b/cmd/internal/update_android_version/main.go index 0c6528c65d..7f27903bb1 100644 --- a/cmd/internal/update_android_version/main.go +++ b/cmd/internal/update_android_version/main.go @@ -46,21 +46,23 @@ func main() { switch propPair[0] { case "VERSION_NAME": if propPair[1] != newVersion { + log.Info("updated version from ", propPair[1], " to ", newVersion) versionUpdated = true propPair[1] = newVersion - log.Info("updated version to ", newVersion) } case "GO_VERSION": if propPair[1] != runtime.Version() { + log.Info("updated Go version from ", propPair[1], " to ", runtime.Version()) goVersionUpdated = true propPair[1] = runtime.Version() - log.Info("updated Go version to ", runtime.Version()) } } } if !(versionUpdated || goVersionUpdated) { log.Info("version not changed") return + } else if flagRunInCI { + log.Fatal("version changed, commit changes first.") } for _, propPair := range propsList { switch propPair[0] { diff --git a/docs/changelog.md b/docs/changelog.md index 8a8dd9e359..c1d7ba8975 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,10 +2,12 @@ icon: material/alert-decagram --- -### 1.11.2 +### 1.11.3 * Fixes and improvements +_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._ + ### 1.11.1 * Fixes and improvements From 093013687c0c66a323e22d10191d675a7e7da570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 18 Feb 2025 18:12:53 +0800 Subject: [PATCH 0221/1272] Fix sniff QUIC hidden in three or more packets --- common/sniff/quic_test.go | 20 ++++++++++++++++++++ route/route.go | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/common/sniff/quic_test.go b/common/sniff/quic_test.go index e75e86a5d2..1fbf60961c 100644 --- a/common/sniff/quic_test.go +++ b/common/sniff/quic_test.go @@ -12,6 +12,26 @@ import ( "github.com/stretchr/testify/require" ) +func TestSniffQUICChromeNew(t *testing.T) { + t.Parallel() + pkt, err := hex.DecodeString("ca0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489ad89c322f75f9a383c90d126a0b21104cb519c2bb32e6a134e86896452e942b26c519b8c7ac9e4c99fae5e1f65cf08fb98443b30e4567932e8fb0789820d8f33037b59ac8113530258c9467dfb52489396dae01f099d28b234efa107fa411f2a1ffa2abe74988e03d662d4296024e95ce0fe1671724937157f77b84990478a2d4060676cf0827b4e8c600654111750414dafa0cccb332f3020c2922a015f445df5edc9c7d2d1ceea9fddcc9ff821c9183aa39a70da20fcc057579e1051c1c899148d6cf9d08b4919822082d040d1ce03ca4f216be6cb7ef03db6df0993ef1ccce5c8c648980554f41704526e1809d2545739f5872e75ec797db1c99f5682e2eda9363cb32aa367b7b363c782ddbacf874183cc15c8a2db068dd4093eebdd096ad33832a7939deb0a872279744f5a56dc001ba62fac973bf680f3b362bdd336add4dd102f462b773bf70bfce1921070a802a92025273a177186d1a643081b42175eb789ccddadb71033ef4feacbf6fd282ab622cf61669d73cda559e411c6ccdd8f003443b6933b7729b7a357aa4aa2fba0f365f829a4d497afb5dc2648a53bc9f3e786d955069d0a4781088a5463747dfe9958ea19ea444eae947ec6a67640955f710f93640084f3fbb8ad259b68dbc0ee0b7fab2d81bffd83ed8a6d33522dbfef43bec0a0fb4bdf1cb712dc4ced0680c0687fa240fd157baa232b1c84e14adce6421cf9270f9b3972f98fc67b344b8a4f1fb551e26f7f76d484ed9f8197f231dc5d9a44cc0ddce73d7f810a620851f4e97eb5037ab5135d7c3be5b80cc32d19910b8387aca64c93c02dc3e35238b78e6aff470722078982e58802844932b6041446bfdcc97ba640cbb86721bcd0f40f27b77aa6287ce5674ec1720134b9302875482c3269787e004b9edb483d44f326eef38c0e83cb46af96488c2e696bc2524567fb29c1e8edcd5a73615496d172d46a9d29e0505c0018b7bbb00165eca0389e09c4b1d73b6cc4a2f735a720650134a2e98e8105e20695cf231b92586237dfe0f99c897414e51c21627496276535f07abb53fb2b554376fe520fa45a3e944fd91dfe7a72aead08842b6b63d8edf861fb911954c83bd9a896eb9da4af5eff646455069d747facd4e77c254096843bff7c3e9031dbdf8dc37ea45f1122922fcbc322ec1378f3c7c1af0da62e1052e6210f1b23073f93a82d90e14cb20bc4501d487a1c848674d57a7c269b13590b3a99d8b8b4f6d0dfbd1d2cbbe7a32c0d5c84ae7ec438b0b19f3862d8fabaa828d06c7e3c6967405cd56a1ae90f38633e2ee0e3ecfca3df399fe12f029e0860a1a30da010300d0c94f0bf56091d00011488c1429928b21c739ebf50ba8be91116315d3173f6d2c56735722478c4d74392ba84d1727036b3d64e8c2263b0f33cb8086be587ca6b3940259c06afa2683868856529303ae12e91d7ca874568be7f2bfaa0656dfab0ed31ed90eaea10fb7f3433ec59a334abe6211d547fa0c825ac45d3691e749d15432008de83e9f6d98f368359137ae803d9189b3386f800c7c0cf4b615d1983cf82d9981a8105b60a80fe66c9b0d439b5ba153dd19e9e7483a01cf3b02b4597540b38e658d4eb8455e030b2bf2690bdd78c23f16fe5") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Equal(t, metadata.Client, C.ClientChromium) + require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) + pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894") + require.NoError(t, err) + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) + pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f") + require.NoError(t, err) + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, "www.google.com", metadata.Domain) +} + func TestSniffQUICChromium(t *testing.T) { t.Parallel() pkt, err := hex.DecodeString("c30000000108f40d654cc09b27f5000044d08a94548e57e43cc5483f129986187c432d58d46674830442988f869566a6e31e2ae37c9f7acbf61cc81621594fab0b3dfdc1635460b32389563dc8e74006315661cd22694114612973c1c45910621713a48b375854f095e8a77ccf3afa64e972f0f7f7002f50e0b014b1b146ea47c07fb20b73ad5587872b51a0b3fafdf1c4cf4fe6f8b112142392efa25d993abe2f42582be145148bdfe12edcd96c3655b65a4781b093e5594ba8e3ae5320f12e8314fc3ca374128cc43381046c322b964681ed4395c813b28534505118201459665a44b8f0abead877de322e9040631d20b05f15b81fa7ff785d4041aecc37c7e2ccdc5d1532787ce566517e8985fd5c200dbfd1e67bc255efaba94cfc07bb52fea4a90887413b134f2715b5643542aa897c6116486f428d82da64d2a2c1e1bdd40bd592558901a554b003d6966ac5a7b8b9413eddbf6ef21f28386c74981e3ce1d724c341e95494907626659692720c81114ca4acea35a14c402cfa3dc2228446e78dc1b81fa4325cf7e314a9cad6a6bdff33b3351dcba74eb15fae67f1227283aa4cdd64bcadf8f19358333f8549b596f4350297b5c65274565869d497398339947b9d3d064e5b06d39d34b436d8a41c1a3880de10bd26c3b1c5b4e2a49b0d4d07b8d90cd9e92bc611564d19ea8ec33099e92033caf21f5307dbeaa4708b99eb313bff99e2081ac25fd12d6a72e8335e0724f6718fe023cd0ad0d6e6a6309f09c9c391eec2bc08e9c3210a043c08e1759f354c121f6517fff4d6e20711a871e41285d48d930352fddffb92c96ba57df045ce99f8bfdfa8edc0969ce68a51e9fbb4f54b956d9df74a9e4af27ed2b27839bce1cffeca8333c0aaee81a570217442f9029ba8fedb84a2cf4be4d910982d891ea00e816c7fb98e8020e896a9c6fdd9106611da0a99dde18df1b7a8f6327acb1eed9ad93314451e48cb0dfb9571728521ca3db2ac0968159d5622556a55d51a422d11995b650949aaefc5d24c16080446dfc4fbc10353f9f93ce161ab513367bb89ab83988e0630b689e174e27bcfcc31996ee7b0bca909e251b82d69a28fee5a5d662e127508cd19dbbe5097b7d5b62a49203d66764197a527e472e2627e44a93d44177dace9d60e7d0e03305ddf4cfe47cdf2362e14de79ef46a6763ce696cd7854a48d9419a0817507a4713ffd4977b906d4f2b5fb6dbe1bd15bc505d5fea582190bf531a45d5ee026da8918547fd5105f15e5d061c7b0cf80a34990366ed8e91e13c2f0d85e5dad537298808d193cf54b7eaac33f10051f74cb6b75e52f81618c36f03d86aef613ba237a1a793ba1539938a38f62ccaf7bd5f6c5e0ce53cde4012fcf2b758214a0422d2faaa798e86e19d7481b42df2b36a73d287ff28c20cce01ce598771fec16a8f1f00305c06010126013a6c1de9f589b4e79d693717cd88ad1c42a2d99fa96617ba0bc6365b68e21a70ebc447904aa27979e1514433cfd83bfec09f137c747d47582cb63eb28f873fb94cf7a59ff764ddfbb687d79a58bb10f85949269f7f72c611a5e0fbb52adfa298ff060ec2eb7216fd7302ea8fb07798cbb3be25cb53ac8161aac2b5bbcfbcfb01c113d28bd1cb0333fb89ac82a95930f7abded0a2f5a623cc6a1f62bf3f38ef1b81c1e50a634f657dbb6770e4af45879e2fb1e00c742e7b52205c8015b5c0f5b1e40186ff9aa7288ab3e01a51fb87761f9bc6837082af109b39cc9f620") diff --git a/route/route.go b/route/route.go index edf6063ea5..bb484efd31 100644 --- a/route/route.go +++ b/route/route.go @@ -622,7 +622,7 @@ func (r *Router) actionSniff( Destination: destination, } packetBuffers = append(packetBuffers, packetBuffer) - if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(packetBuffers) == 0 { + if E.IsMulti(err, sniff.ErrClientHelloFragmented) { r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") continue } From 6cad142cfeddfc7c936fce343f6055d1584f4bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 12 Feb 2025 08:43:11 +0800 Subject: [PATCH 0222/1272] Bump Go to go1.24 --- .github/workflows/build.yml | 10 +++++----- .github/workflows/lint.yml | 2 +- .github/workflows/linux.yml | 2 +- Dockerfile | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 61f14cfb9c..217bfcfbce 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.23 + go-version: ^1.24 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -134,7 +134,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.23 + go-version: ^1.24 - name: Cache legacy Go if: matrix.require_legacy_go id: cache-legacy-go @@ -219,7 +219,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.23 + go-version: ^1.24 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -294,7 +294,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.23 + go-version: ^1.24 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -392,7 +392,7 @@ jobs: if: matrix.if uses: actions/setup-go@v5 with: - go-version: ^1.23 + go-version: ^1.24 - name: Setup Xcode stable if: matrix.if && github.ref == 'refs/heads/main-next' run: |- diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ba6a544dd9..3b428020b9 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.23 + go-version: ^1.24 - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 613c057a27..0fec75945e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.23 + go-version: ^1.24 - name: Extract signing key run: |- mkdir -p $HOME/.gnupg diff --git a/Dockerfile b/Dockerfile index e82b81cee9..6c3cfd396c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder +FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder LABEL maintainer="nekohasekai " COPY . /go/src/github.com/sagernet/sing-box WORKDIR /go/src/github.com/sagernet/sing-box From 270740e8599cb8d31ddc6707761660d98b75e755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 14 Feb 2025 13:36:52 +0800 Subject: [PATCH 0223/1272] Fix crash on route address set update --- protocol/tun/inbound.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 3bbe042116..00cc0561e4 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -312,9 +312,11 @@ func (t *Inbound) Start(stage adapter.StartStage) error { if len(ipSets) == 0 { t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name()) } - t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) routeRuleSet.DecRef() t.routeAddressSet = append(t.routeAddressSet, ipSets...) + if t.autoRedirect != nil { + t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + } } t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { @@ -322,9 +324,11 @@ func (t *Inbound) Start(stage adapter.StartStage) error { if len(ipSets) == 0 { t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name()) } - t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) routeExcludeRuleSet.DecRef() t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...) + if t.autoRedirect != nil { + t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + } } } var ( From 4000e1e66d448d6a765c5e169442e30700378598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 14 Feb 2025 19:13:49 +0800 Subject: [PATCH 0224/1272] release: Fix update android version --- .github/workflows/build.yml | 9 ++++++++- cmd/internal/update_android_version/main.go | 8 ++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 217bfcfbce..98316ddd00 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -256,9 +256,16 @@ jobs: with: path: ~/.gradle key: gradle-${{ hashFiles('**/*.gradle') }} - - name: Build + - name: Update version + if: github.event_name == 'workflow_dispatch' run: |- go run -v ./cmd/internal/update_android_version --ci + - name: Update nightly version + if: github.event_name != 'workflow_dispatch' + run: |- + go run -v ./cmd/internal/update_android_version --ci --nightly + - name: Build + run: |- mkdir clients/android/app/libs cp libbox.aar clients/android/app/libs cd clients/android diff --git a/cmd/internal/update_android_version/main.go b/cmd/internal/update_android_version/main.go index 7f27903bb1..4850fce037 100644 --- a/cmd/internal/update_android_version/main.go +++ b/cmd/internal/update_android_version/main.go @@ -13,10 +13,14 @@ import ( "github.com/sagernet/sing/common" ) -var flagRunInCI bool +var ( + flagRunInCI bool + flagRunNightly bool +) func init() { flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI") + flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly") } func main() { @@ -61,7 +65,7 @@ func main() { if !(versionUpdated || goVersionUpdated) { log.Info("version not changed") return - } else if flagRunInCI { + } else if flagRunInCI && !flagRunNightly { log.Fatal("version changed, commit changes first.") } for _, propPair := range propsList { From 97ce666e43d8748b936bbac061844e8265820812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 18 Feb 2025 21:52:38 +0800 Subject: [PATCH 0225/1272] Fix http.FileServer short write --- experimental/clashapi/server.go | 2 +- experimental/clashapi/server_fs.go | 18 ++++++++++++++++++ experimental/clashapi/server_resources.go | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 experimental/clashapi/server_fs.go diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 2b4da4a4a1..efe2ce3690 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -129,7 +129,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) chiRouter.Group(func(r chi.Router) { r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP) - r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.externalUI)))) + r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(Dir(s.externalUI)))) }) } return s, nil diff --git a/experimental/clashapi/server_fs.go b/experimental/clashapi/server_fs.go new file mode 100644 index 0000000000..afb4cca5e2 --- /dev/null +++ b/experimental/clashapi/server_fs.go @@ -0,0 +1,18 @@ +package clashapi + +import "net/http" + +type Dir http.Dir + +func (d Dir) Open(name string) (http.File, error) { + file, err := http.Dir(d).Open(name) + if err != nil { + return nil, err + } + return &fileWrapper{file}, nil +} + +// workaround for #2345 #2596 +type fileWrapper struct { + http.File +} diff --git a/experimental/clashapi/server_resources.go b/experimental/clashapi/server_resources.go index 2e73121f1a..ec3b2a0f0e 100644 --- a/experimental/clashapi/server_resources.go +++ b/experimental/clashapi/server_resources.go @@ -41,7 +41,6 @@ func (s *Server) downloadExternalUI() error { } else { downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip" } - s.logger.Info("downloading external ui") var detour adapter.Outbound if s.externalUIDownloadDetour != "" { outbound, loaded := s.outbound.Outbound(s.externalUIDownloadDetour) @@ -53,6 +52,7 @@ func (s *Server) downloadExternalUI() error { outbound := s.outbound.Default() detour = outbound } + s.logger.Info("downloading external ui using outbound/", detour.Type(), "[", detour.Tag(), "]") httpClient := &http.Client{ Transport: &http.Transport{ ForceAttemptHTTP2: true, From 93b68312cfa1f298f32aa2db06f732e59bcc160b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 20 Feb 2025 14:42:09 +0800 Subject: [PATCH 0226/1272] platform: Add update WIFI state func --- adapter/network.go | 1 + experimental/libbox/command_power.go | 29 ++-------------------------- experimental/libbox/service_pause.go | 4 ++++ route/network.go | 22 ++++++++++++--------- 4 files changed, 20 insertions(+), 36 deletions(-) diff --git a/adapter/network.go b/adapter/network.go index 00ef54b8c0..3adfaaeb9d 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -25,6 +25,7 @@ type NetworkManager interface { PackageManager() tun.PackageManager WIFIState() WIFIState ResetNetwork() + UpdateWIFIState() } type NetworkOptions struct { diff --git a/experimental/libbox/command_power.go b/experimental/libbox/command_power.go index 5ed7b01428..0090649000 100644 --- a/experimental/libbox/command_power.go +++ b/experimental/libbox/command_power.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "net" - E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/varbin" ) @@ -18,19 +17,7 @@ func (c *CommandClient) ServiceReload() error { if err != nil { return err } - var hasError bool - err = binary.Read(conn, binary.BigEndian, &hasError) - if err != nil { - return err - } - if hasError { - errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - return E.New(errorMessage) - } - return nil + return readError(conn) } func (s *CommandServer) handleServiceReload(conn net.Conn) error { @@ -55,19 +42,7 @@ func (c *CommandClient) ServiceClose() error { if err != nil { return err } - var hasError bool - err = binary.Read(conn, binary.BigEndian, &hasError) - if err != nil { - return nil - } - if hasError { - errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return nil - } - return E.New(errorMessage) - } - return nil + return readError(conn) } func (s *CommandServer) handleServiceClose(conn net.Conn) error { diff --git a/experimental/libbox/service_pause.go b/experimental/libbox/service_pause.go index a7599a767a..0fa9541f19 100644 --- a/experimental/libbox/service_pause.go +++ b/experimental/libbox/service_pause.go @@ -31,3 +31,7 @@ func (s *BoxService) Wake() { func (s *BoxService) ResetNetwork() { s.instance.Router().ResetNetwork() } + +func (s *BoxService) UpdateWIFIState() { + s.instance.Network().UpdateWIFIState() +} diff --git a/route/network.go b/route/network.go index 97d165f119..ab1be76c18 100644 --- a/route/network.go +++ b/route/network.go @@ -354,6 +354,18 @@ func (r *NetworkManager) WIFIState() adapter.WIFIState { 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) ResetNetwork() { conntrack.Close() @@ -414,15 +426,7 @@ func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interfa } } r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) - 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) - } - } - } + r.UpdateWIFIState() if !r.started { return From 7eb353509495c8691690cbd9ee81144871090a8e Mon Sep 17 00:00:00 2001 From: Gavin Luo Date: Thu, 20 Feb 2025 17:57:09 +0800 Subject: [PATCH 0227/1272] release: Fix systemd permissions --- release/config/sing-box.service | 4 ++-- release/config/sing-box@.service | 4 ++-- release/local/sing-box.service | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/release/config/sing-box.service b/release/config/sing-box.service index 7b7a13a855..388d725008 100644 --- a/release/config/sing-box.service +++ b/release/config/sing-box.service @@ -4,8 +4,8 @@ Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target network-online.target [Service] -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH ExecStart=/usr/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure diff --git a/release/config/sing-box@.service b/release/config/sing-box@.service index 578ebd1cb5..38866457bf 100644 --- a/release/config/sing-box@.service +++ b/release/config/sing-box@.service @@ -4,8 +4,8 @@ Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target network-online.target [Service] -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH ExecStart=/usr/bin/sing-box -D /var/lib/sing-box-%i -c /etc/sing-box/%i.json run ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure diff --git a/release/local/sing-box.service b/release/local/sing-box.service index 7dfd6f7950..9a152ade8a 100644 --- a/release/local/sing-box.service +++ b/release/local/sing-box.service @@ -4,8 +4,8 @@ Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target network-online.target [Service] -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH ExecStart=/usr/local/bin/sing-box -D /var/lib/sing-box -C /usr/local/etc/sing-box run ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure From eb07c7a79eeca943370eafea601e87da76c0e57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 23 Feb 2025 08:36:57 +0800 Subject: [PATCH 0228/1272] 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 3a2fc9c880..0576fd75a6 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 3a2fc9c8802f0c40f0b1fd2d7acdcabb7aa0855f +Subproject commit 0576fd75a67e56048c29d00ef539b4f8f05aec2a diff --git a/clients/apple b/clients/apple index 3d5d7343fb..a828bb3a93 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 3d5d7343fb473dce772633183791524629493213 +Subproject commit a828bb3a93b57d0c1b13d74246f0675c5244466d diff --git a/docs/changelog.md b/docs/changelog.md index c1d7ba8975..cf40c0ed16 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +### 1.11.4 + +* Fixes and improvements + ### 1.11.3 * Fixes and improvements From 619fa671d7c5f8adce939a77acf1e9668afb6b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 26 Feb 2025 07:25:35 +0800 Subject: [PATCH 0229/1272] Skip binding to the default interface as it will fail on some Android devices --- common/dialer/default_parallel_interface.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go index 92e3535e81..ca374b2e44 100644 --- a/common/dialer/default_parallel_interface.go +++ b/common/dialer/default_parallel_interface.go @@ -18,6 +18,7 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, false, E.New("no available network interface") } + defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface() if fallbackDelay == 0 { fallbackDelay = N.DefaultFallbackDelay } @@ -31,7 +32,9 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di results := make(chan dialResult) // unbuffered startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) { perNetDialer := dialer - perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index)) + if defaultInterface == nil || iif.Index != defaultInterface.Index { + perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index)) + } conn, err := perNetDialer.DialContext(ctx, network, addr) if err != nil { select { @@ -89,6 +92,7 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, false, E.New("no available network interface") } + defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface() if fallbackDelay == 0 { fallbackDelay = N.DefaultFallbackDelay } @@ -103,7 +107,9 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d results := make(chan dialResult) // unbuffered startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) { perNetDialer := dialer - perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index)) + if defaultInterface == nil || iif.Index != defaultInterface.Index { + perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index)) + } conn, err := perNetDialer.DialContext(ctx, network, addr) if err != nil { select { @@ -149,10 +155,13 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, E.New("no available network interface") } + defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface() var errors []error for _, primaryInterface := range primaryInterfaces { perNetListener := listener - perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index)) + if defaultInterface == nil || primaryInterface.Index != defaultInterface.Index { + perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index)) + } conn, err := perNetListener.ListenPacket(ctx, network, addr) if err == nil { return conn, nil @@ -161,7 +170,9 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene } for _, fallbackInterface := range fallbackInterfaces { perNetListener := listener - perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index)) + if defaultInterface == nil || fallbackInterface.Index != defaultInterface.Index { + perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index)) + } conn, err := perNetListener.ListenPacket(ctx, network, addr) if err == nil { return conn, nil From ce36835fabcd3068ce08bd0fd0677f9a5783c86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 9 Mar 2025 15:20:55 +0800 Subject: [PATCH 0230/1272] Fix override destination --- go.mod | 2 +- go.sum | 4 ++-- protocol/direct/inbound.go | 9 ++++++--- route/conn.go | 14 +++++++------- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index b75ceb3498..532c6be6ce 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.1 + github.com/sagernet/sing v0.6.3 github.com/sagernet/sing-dns v0.4.0 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.0 diff --git a/go.sum b/go.sum index 9ffef2d4fc..41842fa31a 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.1 h1:mJ6e7Ir2wtCoGLbdnnXWBsNJu5YHtbXmv66inoE0zFA= -github.com/sagernet/sing v0.6.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.3 h1:J1spMc6LMlqUvRjWjvNMAcbvACDneqxB9zxfLuS0UTE= +github.com/sagernet/sing v0.6.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0 h1:+mNoOuR3nljjouCH+qMg4zHI1+R9T2ReblGFkZPEndc= github.com/sagernet/sing-dns v0.4.0/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go index 93994761e1..a8af341782 100644 --- a/protocol/direct/inbound.go +++ b/protocol/direct/inbound.go @@ -12,6 +12,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/udpnat2" @@ -80,7 +81,7 @@ func (i *Inbound) Close() error { } func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.Socksaddr{}, nil) + i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, i.listener.UDPAddr(), nil) } func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { @@ -104,7 +105,6 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { i.logger.InfoContext(ctx, "inbound packet connection from ", source) - i.logger.InfoContext(ctx, "inbound packet connection to ", destination) var metadata adapter.InboundContext metadata.Inbound = i.Tag() metadata.InboundType = i.Type() @@ -123,8 +123,11 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, destination.Port = i.overrideDestination.Port default: } + i.logger.InfoContext(ctx, "inbound packet connection to ", destination) metadata.Destination = destination - metadata.OriginDestination = i.listener.UDPAddr() + if i.overrideOption != 0 { + conn = bufio.NewDestinationNATPacketConn(bufio.NewNetPacketConn(conn), i.listener.UDPAddr(), destination) + } i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } diff --git a/route/conn.go b/route/conn.go index 1c0d3c90e5..75c67a3880 100644 --- a/route/conn.go +++ b/route/conn.go @@ -160,11 +160,7 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial natConn.UpdateDestination(destinationAddress) } } else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination { - if metadata.UDPDisableDomainUnmapping { - remotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination) - } else { - remotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination) - } + remotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination) } var udpTimeout time.Duration if metadata.UDPTimeout > 0 { @@ -279,13 +275,17 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { _, err := bufio.CopyPacket(destination, source) if !direction { - if E.IsClosedOrCanceled(err) { + if err == nil { + m.logger.DebugContext(ctx, "packet upload finished") + } else if E.IsClosedOrCanceled(err) { m.logger.TraceContext(ctx, "packet upload closed") } else { m.logger.DebugContext(ctx, "packet upload closed: ", err) } } else { - if E.IsClosedOrCanceled(err) { + if err == nil { + m.logger.DebugContext(ctx, "packet download finished") + } else if E.IsClosedOrCanceled(err) { m.logger.TraceContext(ctx, "packet download closed") } else { m.logger.DebugContext(ctx, "packet download closed: ", err) From 373f158fe0429539ae0b3fb664efc6e029787eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 6 Mar 2025 14:03:41 +0800 Subject: [PATCH 0231/1272] Fix download external ui with query params --- experimental/clashapi/server_resources.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/experimental/clashapi/server_resources.go b/experimental/clashapi/server_resources.go index ec3b2a0f0e..7cf0000b89 100644 --- a/experimental/clashapi/server_resources.go +++ b/experimental/clashapi/server_resources.go @@ -71,15 +71,15 @@ func (s *Server) downloadExternalUI() error { if response.StatusCode != http.StatusOK { return E.New("download external ui failed: ", response.Status) } - err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI) + err = s.downloadZIP(response.Body, s.externalUI) if err != nil { removeAllInDirectory(s.externalUI) } return err } -func (s *Server) downloadZIP(name string, body io.Reader, output string) error { - tempFile, err := filemanager.CreateTemp(s.ctx, name) +func (s *Server) downloadZIP(body io.Reader, output string) error { + tempFile, err := filemanager.CreateTemp(s.ctx, "external-ui.zip") if err != nil { return err } From d3132645fb21d06dcededb8c8f210912e092325f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 27 Feb 2025 18:07:24 +0800 Subject: [PATCH 0232/1272] documentation: Fix description of the UoT protocol --- docs/configuration/shared/udp-over-tcp.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/configuration/shared/udp-over-tcp.md b/docs/configuration/shared/udp-over-tcp.md index 9c2688b94b..71a934db8b 100644 --- a/docs/configuration/shared/udp-over-tcp.md +++ b/docs/configuration/shared/udp-over-tcp.md @@ -31,12 +31,11 @@ The protocol version, `1` or `2`. ### Application support -| Project | UoT v1 | UoT v2 | -|--------------|----------------------|-------------------------------------------------------------------------------------------------------------------| -| sing-box | v0 (2022/08/11) | v1.2-beta9 | -| Xray-core | v1.5.7 (2022/06/05) | [f57ec13](https://github.com/XTLS/Xray-core/commit/f57ec1388084df041a2289bacab14e446bf1b357) (Not released) | -| Clash.Meta | v1.12.0 (2022/07/02) | [8cb67b6](https://github.com/MetaCubeX/Clash.Meta/commit/8cb67b6480649edfa45dcc9ac89ce0789651e8b3) (Not released) | -| Shadowrocket | v2.2.12 (2022/08/13) | / | +| Project | 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) | / | ### Protocol details @@ -50,7 +49,13 @@ The client requests the magic address to the upper layer proxy protocol to indic |------|----------|-------|--------|----------| | u8 | variable | u16be | u16be | variable | -**ATYP / address / port**: Uses the SOCKS address format. +**ATYP / address / port**: Uses the SOCKS address format, but with different address types: + +| ATYP | Address type | +|--------|--------------| +| `0x00` | IPv4 Address | +| `0x01` | IPv6 Address | +| `0x02` | Domain Name | #### Protocol version 2 From 8946a6d2d03ff5daf93a1246dbd1dbc162c4ba92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 26 Feb 2025 09:58:04 +0800 Subject: [PATCH 0233/1272] release: Use latest goreleaser --- .github/workflows/build.yml | 2 +- .goreleaser.yaml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 98316ddd00..7bca4cfe71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,7 +159,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: 2.5.1 + version: '~> v2' install-only: true - name: Extract signing key run: |- diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9eb02c94f1..3ada737739 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -95,10 +95,12 @@ archives: builds: - main - android - format: tar.gz + formats: + - tar.gz format_overrides: - goos: windows - format: zip + formats: + - zip wrap_in_directory: true files: - LICENSE From 63e6c85f6f93d2e11b5857429404a4aeb2653c60 Mon Sep 17 00:00:00 2001 From: Mahdi <80265960+Mahdi-zarei@users.noreply.github.com> Date: Mon, 10 Mar 2025 09:30:14 +0330 Subject: [PATCH 0234/1272] Fix shadowsocks UoT --- protocol/shadowsocks/inbound.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/shadowsocks/inbound.go b/protocol/shadowsocks/inbound.go index 89af24ea43..d921bfa6ca 100644 --- a/protocol/shadowsocks/inbound.go +++ b/protocol/shadowsocks/inbound.go @@ -61,7 +61,7 @@ func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLo logger: logger, } var err error - inbound.router, err = mux.NewRouterWithOptions(router, logger, common.PtrValueOrDefault(options.Multiplex)) + inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) if err != nil { return nil, err } From 2d73ef511d9289068997498687dd20c5c3d0373b Mon Sep 17 00:00:00 2001 From: Tal Rasha Date: Mon, 10 Mar 2025 14:05:31 +0800 Subject: [PATCH 0235/1272] Fix grpclite memory leak Co-authored-by: talrasha007 --- transport/v2raygrpclite/conn.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/transport/v2raygrpclite/conn.go b/transport/v2raygrpclite/conn.go index 5ab02569fd..5feafbb68f 100644 --- a/transport/v2raygrpclite/conn.go +++ b/transport/v2raygrpclite/conn.go @@ -21,6 +21,7 @@ import ( var _ net.Conn = (*GunConn)(nil) type GunConn struct { + rawReader io.Reader reader *std_bufio.Reader writer io.Writer flusher http.Flusher @@ -31,9 +32,10 @@ type GunConn struct { func newGunConn(reader io.Reader, writer io.Writer, flusher http.Flusher) *GunConn { return &GunConn{ - reader: std_bufio.NewReader(reader), - writer: writer, - flusher: flusher, + rawReader: reader, + reader: std_bufio.NewReader(reader), + writer: writer, + flusher: flusher, } } @@ -46,6 +48,7 @@ func newLateGunConn(writer io.Writer) *GunConn { func (c *GunConn) setup(reader io.Reader, err error) { if reader != nil { + c.rawReader = reader c.reader = std_bufio.NewReader(reader) } c.err = err @@ -138,7 +141,7 @@ func (c *GunConn) FrontHeadroom() int { } func (c *GunConn) Close() error { - return common.Close(c.reader, c.writer) + return common.Close(c.rawReader, c.writer) } func (c *GunConn) LocalAddr() net.Addr { From 0b5490d5a3b50412a84b95895e5f6c09c9a4b96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 11 Mar 2025 12:01:31 +0800 Subject: [PATCH 0236/1272] Fix resolve domain for WireGuard --- box.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/box.go b/box.go index 0cbf1b3b3d..8eb8f2f3ed 100644 --- a/box.go +++ b/box.go @@ -165,7 +165,15 @@ func New(options Options) (*Box, error) { } else { tag = F.ToString(i) } - err = endpointManager.Create(ctx, + endpointCtx := ctx + if tag != "" { + // TODO: remove this + endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{ + Outbound: tag, + }) + } + err = endpointManager.Create( + endpointCtx, router, logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")), tag, @@ -183,7 +191,8 @@ func New(options Options) (*Box, error) { } else { tag = F.ToString(i) } - err = inboundManager.Create(ctx, + err = inboundManager.Create( + ctx, router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), tag, From ed46438359b13a24d117eb1191ef13a11e7891f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 11 Mar 2025 13:29:02 +0800 Subject: [PATCH 0237/1272] release: Use nightly goreleaser to fix rpm bug --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7bca4cfe71..445afe9ef7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,7 +159,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: '~> v2' + version: nightly install-only: true - name: Extract signing key run: |- From 32e52ce1eda7ddaa24e107a2f01e7a13a7552bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 11 Mar 2025 14:12:59 +0800 Subject: [PATCH 0238/1272] Fix udp nat for fakeip --- route/conn.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/route/conn.go b/route/conn.go index 75c67a3880..7b9f4a63a5 100644 --- a/route/conn.go +++ b/route/conn.go @@ -149,16 +149,15 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial } else { originDestination = metadata.Destination } - if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) { + 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) } } - if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { - natConn.UpdateDestination(destinationAddress) - } } else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination { remotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination) } From c24c40dfee76f413a82919578534f4a4b0f0a903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 11 Mar 2025 20:18:24 +0800 Subject: [PATCH 0239/1272] platform: Fix android start --- experimental/libbox/monitor.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index eac0026d24..05973ec6ab 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -56,7 +56,12 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) { if sFixAndroidStack { - go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained) + done := make(chan struct{}) + go func() { + m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained) + close(done) + }() + <-done } else { m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained) } From 8e2baf40f11973c7eb7d34fa741f282a5cb275f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 10 Mar 2025 14:48:56 +0800 Subject: [PATCH 0240/1272] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 6 ++++++ docs/clients/apple/index.md | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/clients/android b/clients/android index 0576fd75a6..aefe3c0290 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 0576fd75a67e56048c29d00ef539b4f8f05aec2a +Subproject commit aefe3c029096ddac5189a20a8203a68858152f0a diff --git a/clients/apple b/clients/apple index a828bb3a93..ae5818ee5a 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit a828bb3a93b57d0c1b13d74246f0675c5244466d +Subproject commit ae5818ee5a24af965dc91f80bffa16e1e6c109c1 diff --git a/docs/changelog.md b/docs/changelog.md index cf40c0ed16..8a1d9dd181 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,12 @@ icon: material/alert-decagram --- +### 1.11.5 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ + ### 1.11.4 * Fixes and improvements diff --git a/docs/clients/apple/index.md b/docs/clients/apple/index.md index dc7be7ecda..a872417ad3 100644 --- a/docs/clients/apple/index.md +++ b/docs/clients/apple/index.md @@ -7,6 +7,10 @@ icon: material/apple SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides platform-specific function implementation, such as TUN transparent proxy implementation. +!!! failure "" + + We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected). + ## :material-graph: Requirements * iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+ From 5da2d1d4703b908329ad085b6bbd74d17164ad85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 12 Mar 2025 16:15:40 +0800 Subject: [PATCH 0241/1272] release: Fix goreleaser version --- .github/workflows/build.yml | 2 +- .github/workflows/linux.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 445afe9ef7..034764e613 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -555,7 +555,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: 2.5.1 + version: nightly install-only: true - name: Cache ghr uses: actions/cache@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0fec75945e..5fc3116d85 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -28,7 +28,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: latest + version: nightly args: release -f .goreleaser.fury.yaml --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 3ae036e99727a599869f9db504a2013f30ca3bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 13 Mar 2025 18:53:19 +0800 Subject: [PATCH 0242/1272] Downgrade goreleaser to stable since nfpm fixed --- .github/workflows/build.yml | 4 ++-- .github/workflows/linux.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 034764e613..a26e254357 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,7 +159,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: nightly + version: '~> v2' install-only: true - name: Extract signing key run: |- @@ -555,7 +555,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: nightly + version: '~> v2' install-only: true - name: Cache ghr uses: actions/cache@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 5fc3116d85..fe64ae1f95 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -28,7 +28,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: nightly + version: '~> v2' args: release -f .goreleaser.fury.yaml --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 68ce9577c63ec98ea0c8520800f3749ed3758734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 14 Mar 2025 17:07:17 +0800 Subject: [PATCH 0243/1272] Fix context in v2ray http transports --- log/id.go | 6 +++++- transport/v2rayhttp/conn.go | 10 ++++++++++ transport/v2rayhttp/server.go | 2 +- transport/v2rayhttpupgrade/server.go | 3 ++- transport/v2raywebsocket/server.go | 3 ++- 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/log/id.go b/log/id.go index 19e19d5599..7cac29d2a4 100644 --- a/log/id.go +++ b/log/id.go @@ -20,12 +20,16 @@ type ID struct { } func ContextWithNewID(ctx context.Context) context.Context { - return context.WithValue(ctx, (*idKey)(nil), ID{ + return ContextWithID(ctx, ID{ ID: rand.Uint32(), CreatedAt: time.Now(), }) } +func ContextWithID(ctx context.Context, id ID) context.Context { + return context.WithValue(ctx, (*idKey)(nil), id) +} + func IDFromContext(ctx context.Context) (ID, bool) { id, loaded := ctx.Value((*idKey)(nil)).(ID) return id, loaded diff --git a/transport/v2rayhttp/conn.go b/transport/v2rayhttp/conn.go index f7f93f1b58..e066503d7c 100644 --- a/transport/v2rayhttp/conn.go +++ b/transport/v2rayhttp/conn.go @@ -2,6 +2,7 @@ package v2rayhttp import ( std_bufio "bufio" + "context" "io" "net" "net/http" @@ -10,6 +11,7 @@ import ( "sync" "time" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/baderror" "github.com/sagernet/sing/common/buf" @@ -255,3 +257,11 @@ func (w *HTTP2ConnWrapper) Close() error { func (w *HTTP2ConnWrapper) Upstream() any { return w.ExtendedConn } + +func DupContext(ctx context.Context) context.Context { + id, loaded := log.IDFromContext(ctx) + if !loaded { + return context.Background() + } + return log.ContextWithID(context.Background(), id) +} diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index e0ee42a77e..dd2bc9a2f7 100644 --- a/transport/v2rayhttp/server.go +++ b/transport/v2rayhttp/server.go @@ -132,7 +132,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { if requestBody != nil { conn = bufio.NewCachedConn(conn, requestBody) } - s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil) + s.handler.NewConnectionEx(DupContext(request.Context()), conn, source, M.Socksaddr{}, nil) } else { writer.WriteHeader(http.StatusOK) done := make(chan struct{}) diff --git a/transport/v2rayhttpupgrade/server.go b/transport/v2rayhttpupgrade/server.go index 6a42912e92..3d02547791 100644 --- a/transport/v2rayhttpupgrade/server.go +++ b/transport/v2rayhttpupgrade/server.go @@ -12,6 +12,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-box/transport/v2rayhttp" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -110,7 +111,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed")) return } - s.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil) + s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil) } func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { diff --git a/transport/v2raywebsocket/server.go b/transport/v2raywebsocket/server.go index ccabf086ad..b54d760a05 100644 --- a/transport/v2raywebsocket/server.go +++ b/transport/v2raywebsocket/server.go @@ -13,6 +13,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-box/transport/v2rayhttp" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -114,7 +115,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { if len(earlyData) > 0 { conn = bufio.NewCachedConn(conn, buf.As(earlyData)) } - s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil) + s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, source, M.Socksaddr{}, nil) } func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { From 96eb98c00a718d2caf399eb4b8ab4ec34c16e50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 14 Mar 2025 14:51:35 +0800 Subject: [PATCH 0244/1272] Fix httpupgrade crash --- transport/v2rayhttpupgrade/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/transport/v2rayhttpupgrade/server.go b/transport/v2rayhttpupgrade/server.go index 3d02547791..338b7248a4 100644 --- a/transport/v2rayhttpupgrade/server.go +++ b/transport/v2rayhttpupgrade/server.go @@ -38,6 +38,7 @@ type Server struct { func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, + logger: logger, tlsConfig: tlsConfig, handler: handler, host: options.Host, From 4f3ee611042b66e55f8e1c910bd920547c8aaa4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 15 Mar 2025 08:09:04 +0800 Subject: [PATCH 0245/1272] Fix copy early conn --- route/conn.go | 58 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/route/conn.go b/route/conn.go index 7b9f4a63a5..2824ffb19f 100644 --- a/route/conn.go +++ b/route/conn.go @@ -5,6 +5,7 @@ import ( "io" "net" "net/netip" + "os" "sync" "sync/atomic" "time" @@ -13,6 +14,7 @@ import ( "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/canceler" E "github.com/sagernet/sing/common/exceptions" @@ -190,14 +192,16 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose) } -func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader, destination io.Writer, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { - originSource := source - originDestination := destination +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 { - source, readCounters = N.UnwrapCountReader(source, readCounters) - destination, writeCounters = N.UnwrapCountWriter(destination, writeCounters) - if cachedSrc, isCached := source.(N.CachedReader); isCached { + 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() @@ -207,7 +211,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader if done.Swap(true) { onClose(err) } - common.Close(originSource, originDestination) + common.Close(source, destination) if !direction { m.logger.ErrorContext(ctx, "connection upload payload: ", err) } else { @@ -226,9 +230,13 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader } break } - if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destination); isEarlyConn && earlyConn.NeedHandshake() { - _, err := destination.Write(nil) + 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 { @@ -237,20 +245,20 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader return } } - _, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters) + _, err := bufio.CopyWithCounters(destination, sourceReader, source, readCounters, writeCounters) if err != nil { - common.Close(originDestination) + common.Close(source, destination) } else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex { err = duplexDst.CloseWrite() if err != nil { - common.Close(originSource, originDestination) + common.Close(source, destination) } } else { - common.Close(originDestination) + destination.Close() } if done.Swap(true) { onClose(err) - common.Close(originSource, originDestination) + common.Close(source, destination) } if !direction { if err == nil { @@ -271,6 +279,28 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader } } +func (m *ConnectionManager) connectionCopyEarly(source net.Conn, destination io.Writer) 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)) + } + return err + } + _, err = payload.ReadOnceFrom(source) + if err != nil && !E.IsTimeout(err) { + return E.Cause(err, "read payload") + } + _ = source.SetReadDeadline(time.Time{}) + _, err = destination.Write(payload.Bytes()) + if err != nil { + return E.Cause(err, "write payload") + } + return nil +} + func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { _, err := bufio.CopyPacket(destination, source) if !direction { From d55d5009c29d594cc5a07ec29f9f44d4a5b20605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 16 Mar 2025 09:21:54 +0800 Subject: [PATCH 0246/1272] Fix processing multiple sniffs --- adapter/inbound.go | 9 +++++---- common/sniff/sniff.go | 8 ++++++-- route/route.go | 25 ++++++++++++++++++------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index 93d2ec60c5..173dd0ee92 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -53,10 +53,11 @@ type InboundContext struct { // sniffer - Protocol string - Domain string - Client string - SniffContext any + Protocol string + Domain string + Client string + SniffContext any + PacketSniffError error // cache diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 81fc0a2794..ecb0488b5c 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" ) @@ -34,7 +35,7 @@ func Skip(metadata *adapter.InboundContext) bool { return false } -func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error { +func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffers []*buf.Buffer, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error { if timeout == 0 { timeout = C.ReadPayloadTimeout } @@ -55,7 +56,10 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net. } errors = nil for _, sniffer := range sniffers { - err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes())) + reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader { + return bytes.NewReader(it.Bytes()) + })...) + err = sniffer(ctx, metadata, reader) if err == nil { return nil } diff --git a/route/route.go b/route/route.go index bb484efd31..dab750a226 100644 --- a/route/route.go +++ b/route/route.go @@ -358,7 +358,7 @@ func (r *Router) matchRule( newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), - }, inputConn, inputPacketConn) + }, inputConn, inputPacketConn, nil) if newErr != nil { fatalErr = newErr return @@ -458,7 +458,7 @@ match: switch action := currentRule.Action().(type) { case *rule.RuleActionSniff: if !preMatch { - newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers) if newErr != nil { fatalErr = newErr return @@ -490,7 +490,7 @@ match: } } if !preMatch && inputPacketConn != nil && (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() { - newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: C.TCPTimeout}, inputConn, inputPacketConn) + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: C.TCPTimeout}, inputConn, inputPacketConn, buffers) if newErr != nil { fatalErr = newErr return @@ -506,11 +506,16 @@ match: func (r *Router) actionSniff( ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff, - inputConn net.Conn, inputPacketConn N.PacketConn, + inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, ) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) { if sniff.Skip(metadata) { + r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first") + return + } else if metadata.Protocol != "" { + r.logger.DebugContext(ctx, "duplicate sniff skipped") return - } else if inputConn != nil { + } + if inputConn != nil { sniffBuffer := buf.NewPacket() var streamSniffers []sniff.StreamSniffer if len(action.StreamSniffers) > 0 { @@ -529,6 +534,7 @@ func (r *Router) actionSniff( ctx, metadata, inputConn, + inputBuffers, sniffBuffer, action.Timeout, streamSniffers..., @@ -555,6 +561,10 @@ func (r *Router) actionSniff( sniffBuffer.Release() } } else if inputPacketConn != nil { + if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrClientHelloFragmented) { + r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError) + return + } for { var ( sniffBuffer = buf.NewPacket() @@ -589,7 +599,7 @@ func (r *Router) actionSniff( if (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() && !metadata.RouteOriginalDestination.IsValid() { metadata.Destination = destination } - if len(packetBuffers) > 0 { + if len(packetBuffers) > 0 || metadata.PacketSniffError != nil { err = sniff.PeekPacket( ctx, metadata, @@ -622,7 +632,8 @@ func (r *Router) actionSniff( Destination: destination, } packetBuffers = append(packetBuffers, packetBuffer) - if E.IsMulti(err, sniff.ErrClientHelloFragmented) { + metadata.PacketSniffError = err + if errors.Is(err, sniff.ErrClientHelloFragmented) { r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") continue } From 9aca54d039fb720de1d7144f3c23b0677b519cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 16 Mar 2025 10:24:38 +0800 Subject: [PATCH 0247/1272] Fix socks5 UDP --- go.mod | 2 +- go.sum | 4 ++-- route/route.go | 15 --------------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 532c6be6ce..9a5fac6cf7 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.3 + github.com/sagernet/sing v0.6.4-0.20250316022329-ce1b4851a451 github.com/sagernet/sing-dns v0.4.0 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.0 diff --git a/go.sum b/go.sum index 41842fa31a..7f3d2626dd 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.3 h1:J1spMc6LMlqUvRjWjvNMAcbvACDneqxB9zxfLuS0UTE= -github.com/sagernet/sing v0.6.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.4-0.20250316022329-ce1b4851a451 h1:0yHxm7hyDkZdZlI70WBDtz1+7h+wBu5t9BuZiffEK5g= +github.com/sagernet/sing v0.6.4-0.20250316022329-ce1b4851a451/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0 h1:+mNoOuR3nljjouCH+qMg4zHI1+R9T2ReblGFkZPEndc= github.com/sagernet/sing-dns v0.4.0/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= diff --git a/route/route.go b/route/route.go index dab750a226..834d3425b7 100644 --- a/route/route.go +++ b/route/route.go @@ -489,18 +489,6 @@ match: break match } } - if !preMatch && inputPacketConn != nil && (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() { - newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: C.TCPTimeout}, inputConn, inputPacketConn, buffers) - if newErr != nil { - fatalErr = newErr - return - } - if newBuffer != nil { - buffers = append(buffers, newBuffer) - } else if len(newPacketBuffers) > 0 { - packetBuffers = append(packetBuffers, newPacketBuffers...) - } - } return } @@ -596,9 +584,6 @@ func (r *Router) actionSniff( return } } else { - if (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() && !metadata.RouteOriginalDestination.IsValid() { - metadata.Destination = destination - } if len(packetBuffers) > 0 || metadata.PacketSniffError != nil { err = sniff.PeekPacket( ctx, From db5ec3cdfc5b1b0e4c2d7551a82d480a57ae0b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 20 Mar 2025 20:22:47 +0800 Subject: [PATCH 0248/1272] Fix connectionCopyEarly --- route/conn.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/route/conn.go b/route/conn.go index 2824ffb19f..1721838754 100644 --- a/route/conn.go +++ b/route/conn.go @@ -2,6 +2,7 @@ package route import ( "context" + "errors" "io" "net" "net/netip" @@ -290,7 +291,7 @@ func (m *ConnectionManager) connectionCopyEarly(source net.Conn, destination io. return err } _, err = payload.ReadOnceFrom(source) - if err != nil && !E.IsTimeout(err) { + if err != nil && !(E.IsTimeout(err) || errors.Is(err, io.EOF)) { return E.Cause(err, "read payload") } _ = source.SetReadDeadline(time.Time{}) From 30d785f1ee7a320546a53cfba14aad05b675846c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 21 Mar 2025 20:30:52 +0800 Subject: [PATCH 0249/1272] release: Use fake goreleaser key --- .github/workflows/build.yml | 30 ++++++++++++++++++++---------- .github/workflows/linux.yml | 18 +++++++++++++----- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a26e254357..22302af47d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -135,6 +135,17 @@ jobs: uses: actions/setup-go@v5 with: go-version: ^1.24 + - name: Setup Goreleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser-pro + version: 2.8.1 + install-only: true + - name: Setup MITM + run: |- + git checkout dev-test-mitm + .github/goreleaser/configure.sh + git checkout ${{ github.ref }} - name: Cache legacy Go if: matrix.require_legacy_go id: cache-legacy-go @@ -155,12 +166,6 @@ jobs: with: ndk-version: r28 local-cache: true - - name: Setup Goreleaser - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser-pro - version: '~> v2' - install-only: true - name: Extract signing key run: |- mkdir -p $HOME/.gnupg @@ -182,7 +187,7 @@ jobs: GOPATH: ${{ env.HOME }}/go GOARM: ${{ matrix.goarm }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + GORELEASER_KEY: fake-key NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Build Android @@ -195,7 +200,7 @@ jobs: BUILD_GOARCH: ${{ matrix.goarch }} GOARM: ${{ matrix.goarm }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + GORELEASER_KEY: fake-key NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Upload artifact @@ -555,8 +560,13 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser-pro - version: '~> v2' + version: 2.8.1 install-only: true + - name: Setup MITM + run: |- + git checkout dev-test-mitm + .github/goreleaser/configure.sh + git checkout ${{ github.ref }} - name: Cache ghr uses: actions/cache@v4 id: cache-ghr @@ -589,7 +599,7 @@ jobs: mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + GORELEASER_KEY: fake-key - name: Upload builds if: ${{ env.PUBLISHED == 'false' }} run: |- diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index fe64ae1f95..fffd1dcc82 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -17,6 +17,17 @@ jobs: uses: actions/setup-go@v5 with: go-version: ^1.24 + - name: Setup Goreleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser-pro + version: 2.8.1 + install-only: true + - name: Setup MITM + run: |- + git checkout dev-test-mitm + .github/goreleaser/configure.sh + git checkout ${{ github.ref }} - name: Extract signing key run: |- mkdir -p $HOME/.gnupg @@ -25,11 +36,8 @@ jobs: EOF echo "HOME=$HOME" >> "$GITHUB_ENV" - name: Publish release - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser-pro - version: '~> v2' - args: release -f .goreleaser.fury.yaml --clean + run: |- + goreleaser release -f .goreleaser.fury.yaml --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} From d6d94b689f93bbbb6978b0980845daf1b0cdff9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 23 Mar 2025 11:36:48 +0800 Subject: [PATCH 0250/1272] release: Replace goreleaser build with scripts --- .fpm | 19 +++ .github/workflows/build.yml | 284 ++++++++++++++++++------------------ .github/workflows/linux.yml | 178 +++++++++++++++++++--- 3 files changed, 318 insertions(+), 163 deletions(-) create mode 100644 .fpm diff --git a/.fpm b/.fpm new file mode 100644 index 0000000000..718244b284 --- /dev/null +++ b/.fpm @@ -0,0 +1,19 @@ +-s dir +--name sing-box +--category net +--license GPLv3-or-later +--description "The universal proxy platform." +--url "https://sing-box.sagernet.org/" +--maintainer "nekohasekai " +--deb-field "Bug: https://github.com/SagerNet/sing-box/issues" + +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/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 22302af47d..f34ef4a262 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- - echo "version=${{ inputs.version }}" + echo "version=${{ inputs.version }}" echo "version=${{ inputs.version }}" >> "$GITHUB_ENV" - name: Calculate version if: github.event_name != 'workflow_dispatch' @@ -68,147 +68,171 @@ jobs: - calculate_version strategy: matrix: + os: [ linux, windows, darwin, android ] + arch: [ "386", amd64, arm64 ] + legacy_go: [ false ] include: - - name: linux_386 - goos: linux - goarch: 386 - - name: linux_amd64 - goos: linux - goarch: amd64 - - name: linux_arm64 - goos: linux - goarch: arm64 - - name: linux_arm - goos: linux - goarch: arm - goarm: 6 - - name: linux_arm_v7 - goos: linux - goarch: arm - goarm: 7 - - name: linux_s390x - goos: linux - goarch: s390x - - name: linux_riscv64 - goos: linux - goarch: riscv64 - - name: linux_mips64le - goos: linux - goarch: mips64le - - name: windows_amd64 - goos: windows - goarch: amd64 - require_legacy_go: true - - name: windows_386 - goos: windows - goarch: 386 - require_legacy_go: true - - name: windows_arm64 - goos: windows - goarch: arm64 - - name: darwin_arm64 - goos: darwin - goarch: arm64 - - name: darwin_amd64 - goos: darwin - goarch: amd64 - require_legacy_go: true - - name: android_arm64 - goos: android - goarch: arm64 - - name: android_arm - goos: android - goarch: arm - goarm: 7 - - name: android_amd64 - goos: android - goarch: amd64 - - name: android_386 - goos: android - goarch: 386 + - { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 } + - { os: linux, arch: "386", debian: i386, rpm: i386 } + - { 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 } + - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le } + - { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 } + - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 } + + - { os: windows, arch: "386", legacy_go: true } + - { os: windows, arch: amd64, legacy_go: true } + - { os: darwin, arch: amd64, legacy_go: true } + + - { os: android, arch: "386", ndk: "i686-linux-android21" } + - { os: android, arch: amd64, ndk: "x86_64-linux-android21" } + - { os: android, arch: arm64, ndk: "aarch64-linux-android21" } + - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" } + exclude: + - { os: darwin, arch: "386" } steps: - name: Checkout uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - name: Setup Go + if: matrix.legacy_go uses: actions/setup-go@v5 with: - go-version: ^1.24 - - name: Setup Goreleaser - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser-pro - version: 2.8.1 - install-only: true - - name: Setup MITM - run: |- - git checkout dev-test-mitm - .github/goreleaser/configure.sh - git checkout ${{ github.ref }} - - name: Cache legacy Go - if: matrix.require_legacy_go - id: cache-legacy-go - uses: actions/cache@v4 + go-version: ~1.20 + - name: Setup Go + if: ${{ ! matrix.legacy_go }} + uses: actions/setup-go@v5 with: - path: | - ~/go/go1.20.14 - key: go120 - - name: Setup legacy Go - if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true' - run: |- - wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz - tar -xzf go1.20.14.linux-amd64.tar.gz - mv go $HOME/go/go1.20.14 + go-version: ^1.24 - name: Setup Android NDK - if: matrix.goos == 'android' + if: matrix.os == 'android' uses: nttld/setup-ndk@v1 with: ndk-version: r28 local-cache: true - - name: Extract signing key - run: |- - mkdir -p $HOME/.gnupg - cat > $HOME/.gnupg/sagernet.key <> "$GITHUB_ENV" - 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_reality_server,with_acme,with_clash_api' + if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then + TAGS="${TAGS},with_ech" + fi + echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build - if: matrix.goos != 'android' - run: |- - goreleaser release --clean --split + if: matrix.os != 'android' + 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 }}' \ + ./cmd/sing-box env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - GOPATH: ${{ env.HOME }}/go + CGO_ENABLED: "0" + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} GOARM: ${{ matrix.goarm }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_KEY: fake-key - NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key - NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Build Android - if: matrix.goos == 'android' - run: |- + if: matrix.os == 'android' + run: | + set -xeuo pipefail go install -v ./cmd/internal/build - GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split + export CC='${{ matrix.ndk }}-clang' + 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 }}' \ + ./cmd/sing-box env: - BUILD_GOOS: ${{ matrix.goos }} - BUILD_GOARCH: ${{ matrix.goarch }} - GOARM: ${{ matrix.goarm }} + CGO_ENABLED: "1" + BUILD_GOOS: ${{ matrix.os }} + BUILD_GOARCH: ${{ matrix.arch }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_KEY: fake-key - NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key - NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: Set name + run: |- + ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true) + LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true) + DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}" + PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}" + echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" + echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}" + - name: Package DEB + if: matrix.debian != '' + run: | + set -xeuo pipefail + sudo gem install fpm + sudo apt-get install -y debsigs + fpm -t deb \ + -v "${{ needs.calculate_version.outputs.version }}" \ + -p "dist/${PKG_NAME}.deb" \ + --architecture ${{ matrix.debian }} \ + dist/sing-box=/usr/bin/sing-box + curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff' + sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff' + rm -rf $HOME/.gnupg + gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import < $HOME/.rpmmacros <> "$GITHUB_ENV" echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV" echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV" @@ -532,7 +554,7 @@ jobs: cd "${{ matrix.archive }}" zip -r SFM.dSYMs.zip dSYMs popd - + mkdir -p dist/release cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg" cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip" @@ -556,17 +578,6 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - - name: Setup Goreleaser - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser-pro - version: 2.8.1 - install-only: true - - name: Setup MITM - run: |- - git checkout dev-test-mitm - .github/goreleaser/configure.sh - git checkout ${{ github.ref }} - name: Cache ghr uses: actions/cache@v4 id: cache-ghr @@ -591,26 +602,17 @@ jobs: with: path: dist merge-multiple: true - - name: Merge builds - if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' - run: |- - goreleaser continue --merge --skip publish - mkdir -p dist/release - mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_KEY: fake-key - name: Upload builds if: ${{ env.PUBLISHED == 'false' }} run: |- export PATH="$PATH:$HOME/go/bin" - ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release + ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Replace builds if: ${{ env.PUBLISHED != 'false' }} run: |- export PATH="$PATH:$HOME/go/bin" - ghr --replace -p 5 "v${VERSION}" dist/release + ghr --replace -p 5 "v${VERSION}" dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index fffd1dcc82..87b0738f19 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,13 +1,63 @@ -name: Release to Linux repository +name: Build Linux Packages on: + workflow_dispatch: + inputs: + version: + description: "Version name" + required: true + type: string release: types: - published jobs: + calculate_version: + name: Calculate version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.outputs.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + with: + fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ^1.24 + - name: Check input version + if: github.event_name == 'workflow_dispatch' + run: |- + echo "version=${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> "$GITHUB_ENV" + - name: Calculate version + if: github.event_name != 'workflow_dispatch' + run: |- + go run -v ./cmd/internal/read_tag --nightly + - name: Set outputs + id: outputs + run: |- + echo "version=$version" >> "$GITHUB_OUTPUT" build: + name: Build binary runs-on: ubuntu-latest + needs: + - calculate_version + strategy: + matrix: + include: + - { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 } + - { os: linux, arch: "386", debian: i386, rpm: i386 } + - { 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 } + - { 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@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 @@ -17,30 +67,114 @@ jobs: uses: actions/setup-go@v5 with: go-version: ^1.24 - - name: Setup Goreleaser - uses: goreleaser/goreleaser-action@v6 + - name: Setup Android NDK + if: matrix.os == 'android' + uses: nttld/setup-ndk@v1 with: - distribution: goreleaser-pro - version: 2.8.1 - install-only: true - - name: Setup MITM + ndk-version: r28 + local-cache: true + - 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_reality_server,with_acme,with_clash_api' + if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then + TAGS="${TAGS},with_ech" + fi + 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 }}' \ + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + GOARM: ${{ matrix.goarm }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set mtime run: |- - git checkout dev-test-mitm - .github/goreleaser/configure.sh - git checkout ${{ github.ref }} - - name: Extract signing key + TZ=UTC touch -t '197001010000' dist/sing-box + - name: Set name + if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }} run: |- - mkdir -p $HOME/.gnupg - cat > $HOME/.gnupg/sagernet.key <> "$GITHUB_ENV" + - name: Set beta name + if: contains(needs.calculate_version.outputs.version, '-') + run: |- + echo "NAME=sing-box-beta" >> "$GITHUB_ENV" + - name: Package DEB + if: matrix.debian != '' + run: | + set -xeuo pipefail + sudo gem install fpm + sudo apt-get install -y debsigs + fpm -t deb \ + -v "${{ needs.calculate_version.outputs.version }}" \ + -p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \ + --architecture ${{ matrix.debian }} \ + dist/sing-box=/usr/bin/${NAME} + curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff' + sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff' + rm -rf $HOME/.gnupg + gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <> "$GITHUB_ENV" - - name: Publish release + debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb + - name: Package RPM + if: matrix.rpm != '' run: |- - goreleaser release -f .goreleaser.fury.yaml --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} - FURY_TOKEN: ${{ secrets.FURY_TOKEN }} - NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key - NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + set -xeuo pipefail + sudo gem install fpm + fpm -t rpm \ + -v "${{ needs.calculate_version.outputs.version }}" \ + -p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \ + --architecture ${{ matrix.rpm }} \ + dist/sing-box=/usr/bin/${NAME} + cat > $HOME/.rpmmacros <> "$GITHUB_ENV" + git tag v${{ needs.calculate_version.outputs.version }} -f + echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" + - name: Download builds + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - name: Publish packages + run: |- + wget -O fury-cli.deb https://github.com/gemfury/cli/releases/download/v0.23.0/fury-cli_0.23.0_linux_amd64.deb + sudo dpkg -i fury-cli.deb + fury migrate dist --as=sagernet --api-token ${{ secrets.FURY_TOKEN }} From ae8ce75e4180b985bc59877436ee09e84b02d461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 24 Mar 2025 17:43:56 +0800 Subject: [PATCH 0251/1272] Fix websocket crash --- transport/v2raywebsocket/conn.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/transport/v2raywebsocket/conn.go b/transport/v2raywebsocket/conn.go index 6ed0b0f33d..f909976457 100644 --- a/transport/v2raywebsocket/conn.go +++ b/transport/v2raywebsocket/conn.go @@ -74,6 +74,10 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) { return } if header.OpCode.IsControl() { + if header.Length > 128 { + err = wsutil.ErrFrameTooLarge + return + } err = c.controlHandler(header, c.reader) if err != nil { return From 273a11d5504581543a206a0d486fc6a07bc1285f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 24 Mar 2025 18:14:24 +0800 Subject: [PATCH 0252/1272] Fix crash on udpnat2 handler --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9a5fac6cf7..9138441eb3 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.4-0.20250316022329-ce1b4851a451 + github.com/sagernet/sing v0.6.4 github.com/sagernet/sing-dns v0.4.0 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.0 diff --git a/go.sum b/go.sum index 7f3d2626dd..da7ced311c 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.20250316022329-ce1b4851a451 h1:0yHxm7hyDkZdZlI70WBDtz1+7h+wBu5t9BuZiffEK5g= -github.com/sagernet/sing v0.6.4-0.20250316022329-ce1b4851a451/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.4 h1:UdvcJXcwHjsdftIz1ct7IyhTlztd24DIQZAq2rMaJww= +github.com/sagernet/sing v0.6.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0 h1:+mNoOuR3nljjouCH+qMg4zHI1+R9T2ReblGFkZPEndc= github.com/sagernet/sing-dns v0.4.0/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= From f56131f38eaa7f3610a44090dfa7a6297177cc41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 24 Mar 2025 20:38:18 +0800 Subject: [PATCH 0253/1272] Make linter happy --- .github/workflows/lint.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3b428020b9..c7e5d6e663 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,4 +34,5 @@ jobs: with: version: latest args: --timeout=30m - install-mode: binary \ No newline at end of file + install-mode: binary + verify: false From 6aae834493681dbfb449fa93ea0b52d81ee08b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 26 Mar 2025 12:44:35 +0800 Subject: [PATCH 0254/1272] release: Fix workflow --- .github/workflows/build.yml | 27 ++++++++++++++----------- .github/workflows/linux.yml | 21 +++++++++++-------- cmd/internal/read_tag/main.go | 38 +++++++++++++++++++++++------------ 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f34ef4a262..211743fa32 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,7 +55,7 @@ jobs: - name: Calculate version if: github.event_name != 'workflow_dispatch' run: |- - go run -v ./cmd/internal/read_tag --nightly + go run -v ./cmd/internal/read_tag --ci --nightly - name: Set outputs id: outputs run: |- @@ -165,6 +165,9 @@ jobs: PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}" echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}" + PKG_VERSION="${{ needs.calculate_version.outputs.version }}" + PKG_VERSION="${PKG_VERSION//-/\~}" + echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}" - name: Package DEB if: matrix.debian != '' run: | @@ -172,7 +175,7 @@ jobs: sudo gem install fpm sudo apt-get install -y debsigs fpm -t deb \ - -v "${{ needs.calculate_version.outputs.version }}" \ + -v "$PKG_VERSION" \ -p "dist/${PKG_NAME}.deb" \ --architecture ${{ matrix.debian }} \ dist/sing-box=/usr/bin/sing-box @@ -189,7 +192,7 @@ jobs: set -xeuo pipefail sudo gem install fpm fpm -t rpm \ - -v "${{ needs.calculate_version.outputs.version }}" \ + -v "$PKG_VERSION" \ -p "dist/${PKG_NAME}.rpm" \ --architecture ${{ matrix.rpm }} \ dist/sing-box=/usr/bin/sing-box @@ -208,7 +211,7 @@ jobs: sudo gem install fpm sudo apt-get install -y libarchive-tools fpm -t pacman \ - -v "${{ needs.calculate_version.outputs.version }}" \ + -v "$PKG_VERSION" \ -p "dist/${PKG_NAME}.pkg.tar.zst" \ --architecture ${{ matrix.pacman }} \ dist/sing-box=/usr/bin/sing-box @@ -218,8 +221,8 @@ jobs: cd dist mkdir -p "${DIR_NAME}" cp ../LICENSE "${DIR_NAME}" - if [ '${{ matrix.os }}' = 'windoes' ]; then - cp sing-box.exe "${DIR_NAME}" + if [ '${{ matrix.os }}' = 'windows' ]; then + cp sing-box "${DIR_NAME}/sing-box.exe" zip -r "${DIR_NAME}.zip" "${DIR_NAME}" else cp sing-box "${DIR_NAME}" @@ -305,9 +308,9 @@ jobs: LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} - name: Prepare upload run: |- - mkdir -p dist/release - cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release - cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release + 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 - name: Upload artifact uses: actions/upload-artifact@v4 with: @@ -555,9 +558,9 @@ jobs: zip -r SFM.dSYMs.zip dSYMs popd - mkdir -p dist/release - cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg" - cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip" + mkdir -p dist + cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg" + cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip" - name: Upload image if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 87b0738f19..965929df90 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -34,7 +34,7 @@ jobs: - name: Calculate version if: github.event_name != 'workflow_dispatch' run: |- - go run -v ./cmd/internal/read_tag --nightly + go run -v ./cmd/internal/read_tag --ci --nightly - name: Set outputs id: outputs run: |- @@ -109,6 +109,11 @@ jobs: if: contains(needs.calculate_version.outputs.version, '-') run: |- echo "NAME=sing-box-beta" >> "$GITHUB_ENV" + - name: Set version + run: |- + PKG_VERSION="${{ needs.calculate_version.outputs.version }}" + PKG_VERSION="${PKG_VERSION//-/\~}" + echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}" - name: Package DEB if: matrix.debian != '' run: | @@ -116,10 +121,11 @@ jobs: sudo gem install fpm sudo apt-get install -y debsigs fpm -t deb \ - -v "${{ needs.calculate_version.outputs.version }}" \ + --name "${NAME}" \ + -v "$PKG_VERSION" \ -p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \ --architecture ${{ matrix.debian }} \ - dist/sing-box=/usr/bin/${NAME} + dist/sing-box=/usr/bin/sing-box curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff' sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff' rm -rf $HOME/.gnupg @@ -133,10 +139,11 @@ jobs: set -xeuo pipefail sudo gem install fpm fpm -t rpm \ - -v "${{ needs.calculate_version.outputs.version }}" \ + --name "${NAME}" \ + -v "$PKG_VERSION" \ -p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \ --architecture ${{ matrix.rpm }} \ - dist/sing-box=/usr/bin/${NAME} + dist/sing-box=/usr/bin/sing-box cat > $HOME/.rpmmacros < Date: Wed, 26 Mar 2025 13:13:59 +0800 Subject: [PATCH 0255/1272] Fix udpnat2 handler again --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9138441eb3..52167f1bfa 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.4 + github.com/sagernet/sing v0.6.5 github.com/sagernet/sing-dns v0.4.0 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.0 diff --git a/go.sum b/go.sum index da7ced311c..ca9f34dd77 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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 h1:UdvcJXcwHjsdftIz1ct7IyhTlztd24DIQZAq2rMaJww= -github.com/sagernet/sing v0.6.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.5 h1:TBKTK6Ms0/MNTZm+cTC2hhKunE42XrNIdsxcYtWqeUU= +github.com/sagernet/sing v0.6.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0 h1:+mNoOuR3nljjouCH+qMg4zHI1+R9T2ReblGFkZPEndc= github.com/sagernet/sing-dns v0.4.0/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= From a530e424e9efcf03cd29eca95b7a4d4acf258903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 18 Mar 2025 15:57:06 +0800 Subject: [PATCH 0256/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index aefe3c0290..5659088bb3 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit aefe3c029096ddac5189a20a8203a68858152f0a +Subproject commit 5659088bb3fe18b7095e4b9f868c181e27739617 diff --git a/docs/changelog.md b/docs/changelog.md index 8a1d9dd181..62933f5ed3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,12 @@ icon: material/alert-decagram --- +### 1.11.6 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ + ### 1.11.5 * Fixes and improvements From 2e4a6de4e78f81a02913ec0a5d2a20e4ab6a1257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 27 Mar 2025 20:30:57 +0800 Subject: [PATCH 0257/1272] release: Fix read tag --- cmd/internal/read_tag/main.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/internal/read_tag/main.go b/cmd/internal/read_tag/main.go index 0f426332ff..c4da1de594 100644 --- a/cmd/internal/read_tag/main.go +++ b/cmd/internal/read_tag/main.go @@ -27,11 +27,8 @@ func main() { ) if flagRunNightly { var version badversion.Version - version, err = build_shared.ReadTagVersionRev() + version, err = build_shared.ReadTagVersion() if err == nil { - if version.PreReleaseIdentifier == "" { - version.Patch++ - } versionStr = version.String() } } else { From 9774a659b0328095c601057e9999502b40602af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 29 Mar 2025 17:41:22 +0800 Subject: [PATCH 0258/1272] Fix DoQ / truncate DNS message --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 52167f1bfa..b3e970d3e4 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.5 - github.com/sagernet/sing-dns v0.4.0 + github.com/sagernet/sing-dns v0.4.1 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.0 github.com/sagernet/sing-shadowsocks v0.2.7 diff --git a/go.sum b/go.sum index ca9f34dd77..9e22fefc02 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.6.5 h1:TBKTK6Ms0/MNTZm+cTC2hhKunE42XrNIdsxcYtWqeUU= github.com/sagernet/sing v0.6.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.4.0 h1:+mNoOuR3nljjouCH+qMg4zHI1+R9T2ReblGFkZPEndc= -github.com/sagernet/sing-dns v0.4.0/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= +github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk= +github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= 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.0 h1:E4geazHk/UrJTXMlT+CBCKmn8V86RhtNeczWtfeoEFc= From 47fc3ebda4b370bd7ff9fdfc6b461bf5bb6ffbd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 29 Mar 2025 19:51:21 +0800 Subject: [PATCH 0259/1272] Add duplicate tag check --- option/options.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/option/options.go b/option/options.go index 94c9771928..85ff81eb97 100644 --- a/option/options.go +++ b/option/options.go @@ -4,6 +4,7 @@ import ( "bytes" "context" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" ) @@ -30,7 +31,7 @@ func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) erro return err } o.RawMessage = content - return nil + return checkOptions(o) } type LogOptions struct { @@ -42,3 +43,52 @@ type LogOptions struct { } type StubOptions struct{} + +func checkOptions(options *Options) error { + err := checkInbounds(options.Inbounds) + if err != nil { + return err + } + err = checkOutbounds(options.Outbounds, options.Endpoints) + if err != nil { + return err + } + return nil +} + +func checkInbounds(inbounds []Inbound) error { + seen := make(map[string]bool) + for _, inbound := range inbounds { + if inbound.Tag == "" { + continue + } + if seen[inbound.Tag] { + return E.New("duplicate inbound tag: ", inbound.Tag) + } + seen[inbound.Tag] = true + } + return nil +} + +func checkOutbounds(outbounds []Outbound, endpoints []Endpoint) error { + seen := make(map[string]bool) + for _, outbound := range outbounds { + if outbound.Tag == "" { + continue + } + if seen[outbound.Tag] { + return E.New("duplicate outbound/endpoint tag: ", outbound.Tag) + } + seen[outbound.Tag] = true + } + for _, endpoint := range endpoints { + if endpoint.Tag == "" { + continue + } + if seen[endpoint.Tag] { + return E.New("duplicate outbound/endpoint tag: ", endpoint.Tag) + } + seen[endpoint.Tag] = true + } + return nil +} From f4c29840c35041810d745de0c4bee5c47ba98204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 31 Mar 2025 20:44:46 +0800 Subject: [PATCH 0260/1272] Fix DNS sniffer --- common/sniff/dns.go | 4 ---- common/sniff/dns_test.go | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 common/sniff/dns_test.go diff --git a/common/sniff/dns.go b/common/sniff/dns.go index 96670eca9c..4eb1990c35 100644 --- a/common/sniff/dns.go +++ b/common/sniff/dns.go @@ -11,7 +11,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" - M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/task" mDNS "github.com/miekg/dns" @@ -47,9 +46,6 @@ func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, pack if err != nil { return err } - if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) { - return os.ErrInvalid - } metadata.Protocol = C.ProtocolDNS return nil } diff --git a/common/sniff/dns_test.go b/common/sniff/dns_test.go new file mode 100644 index 0000000000..eaf4dd1acb --- /dev/null +++ b/common/sniff/dns_test.go @@ -0,0 +1,23 @@ +package sniff_test + +import ( + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("740701000001000000000000012a06676f6f676c6503636f6d0000010001") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.DomainNameQuery(context.TODO(), &metadata, query) + require.NoError(t, err) + require.Equal(t, C.ProtocolDNS, metadata.Protocol) +} From 5eeef6b28e81e4374c83378c8fe21d1553209354 Mon Sep 17 00:00:00 2001 From: xchacha20-poly1305 Date: Thu, 3 Apr 2025 20:01:08 +0800 Subject: [PATCH 0261/1272] Fix multiple trackers --- adapter/router.go | 2 +- box.go | 4 ++-- route/route.go | 8 ++++---- route/router.go | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/adapter/router.go b/adapter/router.go index a637e5068d..687943cbfe 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -38,7 +38,7 @@ type Router interface { ClearDNSCache() Rules() []Rule - SetTracker(tracker ConnectionTracker) + AppendTracker(tracker ConnectionTracker) ResetNetwork() } diff --git a/box.go b/box.go index 8eb8f2f3ed..b9f04c8782 100644 --- a/box.go +++ b/box.go @@ -257,7 +257,7 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "create clash-server") } - router.SetTracker(clashServer) + router.AppendTracker(clashServer) service.MustRegister[adapter.ClashServer](ctx, clashServer) services = append(services, clashServer) } @@ -267,7 +267,7 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "create v2ray-server") } if v2rayServer.StatsService() != nil { - router.SetTracker(v2rayServer.StatsService()) + router.AppendTracker(v2rayServer.StatsService()) services = append(services, v2rayServer) service.MustRegister[adapter.V2RayServer](ctx, v2rayServer) } diff --git a/route/route.go b/route/route.go index 834d3425b7..6ab4cc97f5 100644 --- a/route/route.go +++ b/route/route.go @@ -140,8 +140,8 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad for _, buffer := range buffers { conn = bufio.NewCachedConn(conn, buffer) } - if r.tracker != nil { - conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound) + for _, tracker := range r.trackers { + conn = tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound) } if outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandlerEx); isHandler { outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose) @@ -258,8 +258,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) N.PutPacketBuffer(buffer) } - if r.tracker != nil { - conn = r.tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound) + for _, tracker := range r.trackers { + conn = tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound) } if metadata.FakeIP { conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) diff --git a/route/router.go b/route/router.go index 68f5dc3524..b74af8b930 100644 --- a/route/router.go +++ b/route/router.go @@ -64,7 +64,7 @@ type Router struct { fakeIPStore adapter.FakeIPStore processSearcher process.Searcher pauseManager pause.Manager - tracker adapter.ConnectionTracker + trackers []adapter.ConnectionTracker platformInterface platform.Interface needWIFIState bool started bool @@ -511,8 +511,8 @@ func (r *Router) Rules() []adapter.Rule { return r.rules } -func (r *Router) SetTracker(tracker adapter.ConnectionTracker) { - r.tracker = tracker +func (r *Router) AppendTracker(tracker adapter.ConnectionTracker) { + r.trackers = append(r.trackers, tracker) } func (r *Router) ResetNetwork() { From 3adc10a7976eabb4db8a1aee885ae41ad93b8f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 4 Apr 2025 21:54:01 +0800 Subject: [PATCH 0262/1272] Fix hysteria2 close --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b3e970d3e4..d21192ce70 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/render v1.0.3 - github.com/gofrs/uuid/v5 v5.3.0 + github.com/gofrs/uuid/v5 v5.3.2 github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 github.com/libdns/alidns v1.0.3 github.com/libdns/cloudflare v0.1.1 @@ -29,7 +29,7 @@ require ( github.com/sagernet/sing v0.6.5 github.com/sagernet/sing-dns v0.4.1 github.com/sagernet/sing-mux v0.3.1 - github.com/sagernet/sing-quic v0.4.0 + github.com/sagernet/sing-quic v0.4.1 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0 diff --git a/go.sum b/go.sum index 9e22fefc02..5d0ac95595 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,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/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= -github.com/gofrs/uuid/v5 v5.3.0/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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= @@ -125,8 +125,8 @@ github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYi github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= 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.0 h1:E4geazHk/UrJTXMlT+CBCKmn8V86RhtNeczWtfeoEFc= -github.com/sagernet/sing-quic v0.4.0/go.mod h1:c+CytOEyeN20KCTFIP8YQUkNDVFLSzjrEPqP7Hlnxys= +github.com/sagernet/sing-quic v0.4.1 h1:pxlMa4efZu/M07RgGagNNDDyl6ZUwpmNUjRTpgHOWK4= +github.com/sagernet/sing-quic v0.4.1/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= 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= From af17eaa537524a330ecc497a16c70ec213b76ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 4 Apr 2025 16:21:50 +0800 Subject: [PATCH 0263/1272] Improve sniffer --- common/sniff/bittorrent.go | 5 +++-- common/sniff/dns.go | 16 ++++------------ common/sniff/http.go | 8 +++++++- common/sniff/quic.go | 4 +--- common/sniff/quic_test.go | 6 +++--- common/sniff/rdp.go | 19 ++++++++++--------- common/sniff/sniff.go | 20 +++++++++++++------- common/sniff/ssh.go | 18 +++++++++++------- common/sniff/tls.go | 8 +++++++- go.mod | 2 +- go.sum | 4 ++-- route/route.go | 5 +++-- 12 files changed, 65 insertions(+), 50 deletions(-) diff --git a/common/sniff/bittorrent.go b/common/sniff/bittorrent.go index 9e123c41e9..9fa7d8b84a 100644 --- a/common/sniff/bittorrent.go +++ b/common/sniff/bittorrent.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" ) const ( @@ -23,7 +24,7 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R var first byte err := binary.Read(reader, binary.BigEndian, &first) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } if first != 19 { @@ -33,7 +34,7 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R var protocol [19]byte _, err = reader.Read(protocol[:]) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } if string(protocol[:]) != "BitTorrent protocol" { return os.ErrInvalid diff --git a/common/sniff/dns.go b/common/sniff/dns.go index 4eb1990c35..2e22f3d7f5 100644 --- a/common/sniff/dns.go +++ b/common/sniff/dns.go @@ -5,13 +5,11 @@ import ( "encoding/binary" "io" "os" - "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/task" + E "github.com/sagernet/sing/common/exceptions" mDNS "github.com/miekg/dns" ) @@ -20,22 +18,16 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon var length uint16 err := binary.Read(reader, binary.BigEndian, &length) if err != nil { - return os.ErrInvalid + return E.Cause1(ErrNeedMoreData, err) } if length == 0 { return os.ErrInvalid } buffer := buf.NewSize(int(length)) defer buffer.Release() - readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100) - var readTask task.Group - readTask.Append0(func(ctx context.Context) error { - return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen())) - }) - err = readTask.Run(readCtx) - cancel() + _, err = buffer.ReadFullFrom(reader, buffer.FreeLen()) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } return DomainNameQuery(readCtx, metadata, buffer.Bytes()) } diff --git a/common/sniff/http.go b/common/sniff/http.go index 0e6ab406c6..012f2c99df 100644 --- a/common/sniff/http.go +++ b/common/sniff/http.go @@ -3,10 +3,12 @@ package sniff import ( std_bufio "bufio" "context" + "errors" "io" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/protocol/http" ) @@ -14,7 +16,11 @@ import ( func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { request, err := http.ReadRequest(std_bufio.NewReader(reader)) if err != nil { - return err + if errors.Is(err, io.ErrUnexpectedEOF) { + return E.Cause1(ErrNeedMoreData, err) + } else { + return err + } } metadata.Protocol = C.ProtocolHTTP metadata.Domain = M.ParseSocksaddr(request.Host).AddrString() diff --git a/common/sniff/quic.go b/common/sniff/quic.go index adec7008b1..4c2e667cb4 100644 --- a/common/sniff/quic.go +++ b/common/sniff/quic.go @@ -20,8 +20,6 @@ import ( "golang.org/x/crypto/hkdf" ) -var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection") - func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { reader := bytes.NewReader(packet) typeByte, err := reader.ReadByte() @@ -308,7 +306,7 @@ find: metadata.Protocol = C.ProtocolQUIC metadata.Client = C.ClientChromium metadata.SniffContext = fragments - return ErrClientHelloFragmented + return E.Cause1(ErrNeedMoreData, err) } metadata.Domain = fingerprint.ServerName for metadata.Client == "" { diff --git a/common/sniff/quic_test.go b/common/sniff/quic_test.go index 1fbf60961c..1149f68e39 100644 --- a/common/sniff/quic_test.go +++ b/common/sniff/quic_test.go @@ -20,11 +20,11 @@ func TestSniffQUICChromeNew(t *testing.T) { err = sniff.QUICClientHello(context.Background(), &metadata, pkt) require.Equal(t, metadata.Protocol, C.ProtocolQUIC) require.Equal(t, metadata.Client, C.ClientChromium) - require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894") require.NoError(t, err) err = sniff.QUICClientHello(context.Background(), &metadata, pkt) - require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f") require.NoError(t, err) err = sniff.QUICClientHello(context.Background(), &metadata, pkt) @@ -40,7 +40,7 @@ func TestSniffQUICChromium(t *testing.T) { err = sniff.QUICClientHello(context.Background(), &metadata, pkt) require.Equal(t, metadata.Protocol, C.ProtocolQUIC) require.Equal(t, metadata.Client, C.ClientChromium) - require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28") require.NoError(t, err) err = sniff.QUICClientHello(context.Background(), &metadata, pkt) diff --git a/common/sniff/rdp.go b/common/sniff/rdp.go index 391ebd2695..37551fef24 100644 --- a/common/sniff/rdp.go +++ b/common/sniff/rdp.go @@ -8,6 +8,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/rw" ) @@ -15,7 +16,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) var tpktVersion uint8 err := binary.Read(reader, binary.BigEndian, &tpktVersion) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } if tpktVersion != 0x03 { return os.ErrInvalid @@ -24,7 +25,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) var tpktReserved uint8 err = binary.Read(reader, binary.BigEndian, &tpktReserved) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } if tpktReserved != 0x00 { return os.ErrInvalid @@ -33,7 +34,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) var tpktLength uint16 err = binary.Read(reader, binary.BigEndian, &tpktLength) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } if tpktLength != 19 { @@ -43,7 +44,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) var cotpLength uint8 err = binary.Read(reader, binary.BigEndian, &cotpLength) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } if cotpLength != 14 { @@ -53,7 +54,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) var cotpTpduType uint8 err = binary.Read(reader, binary.BigEndian, &cotpTpduType) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } if cotpTpduType != 0xE0 { return os.ErrInvalid @@ -61,13 +62,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) err = rw.SkipN(reader, 5) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } var rdpType uint8 err = binary.Read(reader, binary.BigEndian, &rdpType) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } if rdpType != 0x01 { return os.ErrInvalid @@ -75,12 +76,12 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) var rdpFlags uint8 err = binary.Read(reader, binary.BigEndian, &rdpFlags) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } var rdpLength uint8 err = binary.Read(reader, binary.BigEndian, &rdpLength) if err != nil { - return err + return E.Cause1(ErrNeedMoreData, err) } if rdpLength != 8 { return os.ErrInvalid diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index ecb0488b5c..59e81aaacf 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -3,6 +3,7 @@ package sniff import ( "bytes" "context" + "errors" "io" "net" "time" @@ -19,6 +20,8 @@ type ( PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error ) +var ErrNeedMoreData = E.New("need more data") + func Skip(metadata *adapter.InboundContext) bool { // skip server first protocols switch metadata.Destination.Port { @@ -40,7 +43,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net. timeout = C.ReadPayloadTimeout } deadline := time.Now().Add(timeout) - var errors []error + var sniffError error for i := 0; ; i++ { err := conn.SetReadDeadline(deadline) if err != nil { @@ -54,7 +57,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net. } return E.Cause(err, "read payload") } - errors = nil + sniffError = nil for _, sniffer := range sniffers { reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader { return bytes.NewReader(it.Bytes()) @@ -63,20 +66,23 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net. if err == nil { return nil } - errors = append(errors, err) + sniffError = E.Errors(sniffError, err) + } + if !errors.Is(err, ErrNeedMoreData) { + break } } - return E.Errors(errors...) + return sniffError } func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error { - var errors []error + var sniffError []error for _, sniffer := range sniffers { err := sniffer(ctx, metadata, packet) if err == nil { return nil } - errors = append(errors, err) + sniffError = append(sniffError, err) } - return E.Errors(errors...) + return E.Errors(sniffError...) } diff --git a/common/sniff/ssh.go b/common/sniff/ssh.go index 194d0bda8c..d373d292cd 100644 --- a/common/sniff/ssh.go +++ b/common/sniff/ssh.go @@ -5,22 +5,26 @@ import ( "context" "io" "os" - "strings" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" ) func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { - scanner := bufio.NewScanner(reader) - if !scanner.Scan() { + const sshPrefix = "SSH-2.0-" + bReader := bufio.NewReader(reader) + prefix, err := bReader.Peek(len(sshPrefix)) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } else if string(prefix) != sshPrefix { return os.ErrInvalid } - fistLine := scanner.Text() - if !strings.HasPrefix(fistLine, "SSH-2.0-") { - return os.ErrInvalid + fistLine, _, err := bReader.ReadLine() + if err != nil { + return err } metadata.Protocol = C.ProtocolSSH - metadata.Client = fistLine[8:] + metadata.Client = string(fistLine)[8:] return nil } diff --git a/common/sniff/tls.go b/common/sniff/tls.go index 6fe430e2d8..613086e810 100644 --- a/common/sniff/tls.go +++ b/common/sniff/tls.go @@ -3,11 +3,13 @@ package sniff import ( "context" "crypto/tls" + "errors" "io" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" ) func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error { @@ -23,5 +25,9 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade metadata.Domain = clientHello.ServerName return nil } - return err + if errors.Is(err, io.ErrUnexpectedEOF) { + return E.Cause1(ErrNeedMoreData, err) + } else { + return err + } } diff --git a/go.mod b/go.mod index d21192ce70..c42ecf7ca5 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.5 + github.com/sagernet/sing v0.6.6-0.20250406082302-d3673bff4af8 github.com/sagernet/sing-dns v0.4.1 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.1 diff --git a/go.sum b/go.sum index 5d0ac95595..921cfe3ddf 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.5 h1:TBKTK6Ms0/MNTZm+cTC2hhKunE42XrNIdsxcYtWqeUU= -github.com/sagernet/sing v0.6.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.6-0.20250406082302-d3673bff4af8 h1:1jHChanwnGF5DJZ5pR/RkVf69VyjQxfDVfOMJx7bPyI= +github.com/sagernet/sing v0.6.6-0.20250406082302-d3673bff4af8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk= github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= diff --git a/route/route.go b/route/route.go index 6ab4cc97f5..dde91db816 100644 --- a/route/route.go +++ b/route/route.go @@ -549,7 +549,7 @@ func (r *Router) actionSniff( sniffBuffer.Release() } } else if inputPacketConn != nil { - if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrClientHelloFragmented) { + if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) { r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError) return } @@ -618,7 +618,8 @@ func (r *Router) actionSniff( } packetBuffers = append(packetBuffers, packetBuffer) metadata.PacketSniffError = err - if errors.Is(err, sniff.ErrClientHelloFragmented) { + if errors.Is(err, sniff.ErrNeedMoreData) { + // TODO: replace with generic message when there are more multi-packet protocols r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") continue } From 24af0766ac2f4dc36acf90c4298ca31ab6cd7af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 6 Apr 2025 16:19:46 +0800 Subject: [PATCH 0264/1272] Fix uTP sniffer --- common/sniff/bittorrent.go | 4 +++- common/sniff/bittorrent_test.go | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/common/sniff/bittorrent.go b/common/sniff/bittorrent.go index 9fa7d8b84a..39c1959812 100644 --- a/common/sniff/bittorrent.go +++ b/common/sniff/bittorrent.go @@ -68,7 +68,9 @@ func UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) err if err != nil { return err } - + if extension > 0x04 { + return os.ErrInvalid + } var length byte err = binary.Read(reader, binary.BigEndian, &length) if err != nil { diff --git a/common/sniff/bittorrent_test.go b/common/sniff/bittorrent_test.go index 65f095bdac..f4762e3233 100644 --- a/common/sniff/bittorrent_test.go +++ b/common/sniff/bittorrent_test.go @@ -71,3 +71,19 @@ func TestSniffUDPTracker(t *testing.T) { require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) } } + +func TestSniffNotUTP(t *testing.T) { + t.Parallel() + + packets := []string{ + "0102736470696e674958d580121500000000000079aaed6717a39c27b07c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + } + for _, pkt := range packets { + pkt, err := hex.DecodeString(pkt) + require.NoError(t, err) + + var metadata adapter.InboundContext + err = sniff.UTP(context.TODO(), &metadata, pkt) + require.Error(t, err) + } +} From 97d41ffde8339eee50fcdb6245690f179f5c2297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 6 Apr 2025 16:35:18 +0800 Subject: [PATCH 0265/1272] Improve pause management --- experimental/libbox/service.go | 2 +- experimental/libbox/service_pause.go | 30 +++++++++--------- protocol/group/urltest.go | 47 +++++++++++++--------------- protocol/wireguard/endpoint.go | 1 - protocol/wireguard/outbound.go | 1 - route/route.go | 7 ----- route/rule/rule_set_remote.go | 23 ++++++++------ transport/wireguard/endpoint.go | 14 ++++----- 8 files changed, 59 insertions(+), 66 deletions(-) diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 8d42d26e21..16c04a1f5e 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -40,7 +40,7 @@ type BoxService struct { clashServer adapter.ClashServer pauseManager pause.Manager - servicePauseFields + iOSPauseFields } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { diff --git a/experimental/libbox/service_pause.go b/experimental/libbox/service_pause.go index 0fa9541f19..791684ecd4 100644 --- a/experimental/libbox/service_pause.go +++ b/experimental/libbox/service_pause.go @@ -1,31 +1,33 @@ package libbox import ( - "sync" "time" + + C "github.com/sagernet/sing-box/constant" ) -type servicePauseFields struct { - pauseAccess sync.Mutex - pauseTimer *time.Timer +type iOSPauseFields struct { + endPauseTimer *time.Timer } func (s *BoxService) Pause() { - s.pauseAccess.Lock() - defer s.pauseAccess.Unlock() - if s.pauseTimer != nil { - s.pauseTimer.Stop() + s.pauseManager.DevicePause() + if !C.IsIos { + s.instance.Router().ResetNetwork() + } else { + if s.endPauseTimer == nil { + s.endPauseTimer = time.AfterFunc(time.Minute, s.pauseManager.DeviceWake) + } else { + s.endPauseTimer.Reset(time.Minute) + } } - s.pauseTimer = time.AfterFunc(3*time.Second, s.ResetNetwork) } func (s *BoxService) Wake() { - s.pauseAccess.Lock() - defer s.pauseAccess.Unlock() - if s.pauseTimer != nil { - s.pauseTimer.Stop() + if !C.IsIos { + s.pauseManager.DeviceWake() + s.instance.Router().ResetNetwork() } - s.pauseTimer = time.AfterFunc(3*time.Minute, s.ResetNetwork) } func (s *BoxService) ResetNetwork() { diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index 564c2373ea..e52ec90693 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -19,6 +19,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/x/list" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) @@ -27,10 +28,7 @@ func RegisterURLTest(registry *outbound.Registry) { outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest) } -var ( - _ adapter.OutboundGroup = (*URLTest)(nil) - _ adapter.InterfaceUpdateListener = (*URLTest)(nil) -) +var _ adapter.OutboundGroup = (*URLTest)(nil) type URLTest struct { outbound.Adapter @@ -172,15 +170,12 @@ func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose) } -func (s *URLTest) InterfaceUpdated() { - go s.group.CheckOutbounds(true) - return -} - type URLTestGroup struct { ctx context.Context router adapter.Router - outboundManager adapter.OutboundManager + outbound adapter.OutboundManager + pause pause.Manager + pauseCallback *list.Element[pause.Callback] logger log.Logger outbounds []adapter.Outbound link string @@ -189,17 +184,15 @@ type URLTestGroup struct { idleTimeout time.Duration history *urltest.HistoryStorage checking atomic.Bool - pauseManager pause.Manager selectedOutboundTCP adapter.Outbound selectedOutboundUDP adapter.Outbound interruptGroup *interrupt.Group interruptExternalConnections bool - - access sync.Mutex - ticker *time.Ticker - close chan struct{} - started bool - lastActive atomic.TypedValue[time.Time] + access sync.Mutex + ticker *time.Ticker + close chan struct{} + started bool + lastActive atomic.TypedValue[time.Time] } func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) { @@ -224,7 +217,7 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage } return &URLTestGroup{ ctx: ctx, - outboundManager: outboundManager, + outbound: outboundManager, logger: logger, outbounds: outbounds, link: link, @@ -233,13 +226,15 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage idleTimeout: idleTimeout, history: history, close: make(chan struct{}), - pauseManager: service.FromContext[pause.Manager](ctx), + pause: service.FromContext[pause.Manager](ctx), interruptGroup: interrupt.NewGroup(), interruptExternalConnections: interruptExternalConnections, }, nil } func (g *URLTestGroup) PostStart() { + g.access.Lock() + defer g.access.Unlock() g.started = true g.lastActive.Store(time.Now()) go g.CheckOutbounds(false) @@ -249,24 +244,25 @@ func (g *URLTestGroup) Touch() { if !g.started { return } - if g.ticker != nil { - g.lastActive.Store(time.Now()) - return - } g.access.Lock() defer g.access.Unlock() if g.ticker != nil { + g.lastActive.Store(time.Now()) return } g.ticker = time.NewTicker(g.interval) go g.loopCheck() + g.pauseCallback = pause.RegisterTicker(g.pause, g.ticker, g.interval, nil) } func (g *URLTestGroup) Close() error { + g.access.Lock() + defer g.access.Unlock() if g.ticker == nil { return nil } g.ticker.Stop() + g.pause.UnregisterCallback(g.pauseCallback) close(g.close) return nil } @@ -330,10 +326,11 @@ func (g *URLTestGroup) loopCheck() { g.access.Lock() g.ticker.Stop() g.ticker = nil + g.pause.UnregisterCallback(g.pauseCallback) + g.pauseCallback = nil g.access.Unlock() return } - g.pauseManager.WaitActive() g.CheckOutbounds(false) } } @@ -366,7 +363,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint continue } checked[realTag] = true - p, loaded := g.outboundManager.Outbound(realTag) + p, loaded := g.outbound.Outbound(realTag) if !loaded { continue } diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 21d72bd982..c9751c3629 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -120,7 +120,6 @@ func (w *Endpoint) Close() error { func (w *Endpoint) InterfaceUpdated() { w.endpoint.BindUpdate() - return } func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 3e29970535..7a516598c0 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -126,7 +126,6 @@ func (o *Outbound) Close() error { func (o *Outbound) InterfaceUpdated() { o.endpoint.BindUpdate() - return } func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/route/route.go b/route/route.go index dde91db816..d6611b4fa3 100644 --- a/route/route.go +++ b/route/route.go @@ -60,10 +60,6 @@ func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata } func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - if r.pauseManager.IsDevicePaused() { - return E.New("reject connection to ", metadata.Destination, " while device paused") - } - //nolint:staticcheck if metadata.InboundDetour != "" { if metadata.LastInbound == metadata.InboundDetour { @@ -186,9 +182,6 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, } func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - if r.pauseManager.IsDevicePaused() { - return E.New("reject packet connection to ", metadata.Destination, " while device paused") - } //nolint:staticcheck if metadata.InboundDetour != "" { if metadata.LastInbound == metadata.InboundDetour { diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 9e0c172927..c29d6616ee 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -103,7 +103,7 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter. } } if s.lastUpdated.IsZero() { - err := s.fetchOnce(ctx, startContext) + err := s.fetch(ctx, startContext) if err != nil { return E.Cause(err, "initial rule-set: ", s.options.Tag) } @@ -198,7 +198,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { func (s *RemoteRuleSet) loopUpdate() { if time.Since(s.lastUpdated) > s.updateInterval { - err := s.fetchOnce(s.ctx, nil) + err := s.fetch(s.ctx, nil) if err != nil { s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err) } else if s.refs.Load() == 0 { @@ -211,18 +211,21 @@ func (s *RemoteRuleSet) loopUpdate() { case <-s.ctx.Done(): return case <-s.updateTicker.C: - s.pauseManager.WaitActive() - err := s.fetchOnce(s.ctx, nil) - if err != nil { - s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err) - } else if s.refs.Load() == 0 { - s.rules = nil - } + s.updateOnce() } } } -func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTTPStartContext) error { +func (s *RemoteRuleSet) updateOnce() { + err := s.fetch(s.ctx, nil) + if err != nil { + s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err) + } else if s.refs.Load() == 0 { + s.rules = nil + } +} + +func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPStartContext) error { s.logger.Debug("updating rule-set ", s.options.Tag, " from URL: ", s.options.RemoteOptions.URL) var httpClient *http.Client if startContext != nil { diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 69ce917020..17a58a6ce3 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -30,7 +30,7 @@ type Endpoint struct { allowedAddress []netip.Prefix tunDevice Device device *device.Device - pauseManager pause.Manager + pause pause.Manager pauseCallback *list.Element[pause.Callback] } @@ -187,9 +187,9 @@ func (e *Endpoint) Start(resolve bool) error { return E.Cause(err, "setup wireguard: \n", ipcConf) } e.device = wgDevice - e.pauseManager = service.FromContext[pause.Manager](e.options.Context) - if e.pauseManager != nil { - e.pauseCallback = e.pauseManager.RegisterCallback(e.onPauseUpdated) + e.pause = service.FromContext[pause.Manager](e.options.Context) + if e.pause != nil { + e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated) } return nil } @@ -217,16 +217,16 @@ func (e *Endpoint) Close() error { e.device.Close() } if e.pauseCallback != nil { - e.pauseManager.UnregisterCallback(e.pauseCallback) + e.pause.UnregisterCallback(e.pauseCallback) } return nil } func (e *Endpoint) onPauseUpdated(event int) { switch event { - case pause.EventDevicePaused: + case pause.EventDevicePaused, pause.EventNetworkPause: e.device.Down() - case pause.EventDeviceWake: + case pause.EventDeviceWake, pause.EventNetworkWake: e.device.Up() } } From 991e7557898e8fc297cc541f0b8ed50487117bf6 Mon Sep 17 00:00:00 2001 From: Mahdi <80265960+Mahdi-zarei@users.noreply.github.com> Date: Thu, 3 Apr 2025 11:40:43 +0330 Subject: [PATCH 0266/1272] Fix conn copy --- route/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/route/conn.go b/route/conn.go index 1721838754..319e463c35 100644 --- a/route/conn.go +++ b/route/conn.go @@ -246,7 +246,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, return } } - _, err := bufio.CopyWithCounters(destination, sourceReader, source, readCounters, writeCounters) + _, err := bufio.CopyWithCounters(destinationWriter, sourceReader, source, readCounters, writeCounters) if err != nil { common.Close(source, destination) } else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex { From a15b5a2463d9e52945ff71908c2daa97e84a7011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 6 Apr 2025 19:51:24 +0800 Subject: [PATCH 0267/1272] Fix `no_drop` not work --- route/rule/rule_action.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 8989ff3c86..d4c6625d29 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -279,6 +279,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error { default: panic(F.ToString("unknown reject method: ", r.Method)) } + if r.NoDrop { + return returnErr + } r.dropAccess.Lock() defer r.dropAccess.Unlock() timeNow := time.Now() From 594ee480a24da63652666ba6a5bddccb19405b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 6 Apr 2025 20:21:55 +0800 Subject: [PATCH 0268/1272] option: Fix listable --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c42ecf7ca5..3c1ab6448f 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.6-0.20250406082302-d3673bff4af8 + github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7 github.com/sagernet/sing-dns v0.4.1 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.1 diff --git a/go.sum b/go.sum index 921cfe3ddf..7aa82f747f 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.6-0.20250406082302-d3673bff4af8 h1:1jHChanwnGF5DJZ5pR/RkVf69VyjQxfDVfOMJx7bPyI= -github.com/sagernet/sing v0.6.6-0.20250406082302-d3673bff4af8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7 h1:ZJauxLmH12Gzv3nucfjsSBQw9UA8t7Sxu8pYHBSP2TU= +github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk= github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= From ae9bc7acf175cdeb9e7be9508e0538673df70e12 Mon Sep 17 00:00:00 2001 From: testing <58134720+testing765@users.noreply.github.com> Date: Sun, 6 Apr 2025 15:35:59 +0300 Subject: [PATCH 0269/1272] documentation: Fix typo Signed-off-by: testing <58134720+testing765@users.noreply.github.com> --- docs/configuration/route/sniff.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index 40de038cb2..3880f79025 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -29,7 +29,7 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c | QUIC Client | Type | |:------------------------:|:----------:| -| Chromium/Cronet | `chrimium` | +| Chromium/Cronet | `chromium` | | Safari/Apple Network API | `safari` | | Firefox / uquic firefox | `firefox` | -| quic-go / uquic chrome | `quic-go` | \ No newline at end of file +| quic-go / uquic chrome | `quic-go` | From 9668ea69b87e04c204e094593200d9a1129deb5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 7 Apr 2025 15:05:15 +0800 Subject: [PATCH 0270/1272] Fix windows process searcher --- common/process/searcher_windows.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/common/process/searcher_windows.go b/common/process/searcher_windows.go index 5b3d59b5ab..6ea7c709bc 100644 --- a/common/process/searcher_windows.go +++ b/common/process/searcher_windows.go @@ -124,14 +124,6 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) for i := 0; i < n; i++ { row := b[4+itemSize*i : 4+itemSize*(i+1)] - if s.tcpState >= 0 { - tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4]) - // MIB_TCP_STATE_ESTAB, only check established connections for TCP - if tcpState != 5 { - continue - } - } - // according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian. // this field can be illustrated as follows depends on different machine endianess: // little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB) @@ -144,7 +136,7 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize]) // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto - if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) { + if ip != srcIP && (!srcIP.IsUnspecified()) { continue } From 5adaf1ac75df426651a9079ca3f69dff5da7f6ef Mon Sep 17 00:00:00 2001 From: Fei1Yang Date: Tue, 8 Apr 2025 14:20:31 +0800 Subject: [PATCH 0271/1272] Mark config file as noreplace for rpm --- .fpm | 1 + .goreleaser.fury.yaml | 2 +- .goreleaser.yaml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.fpm b/.fpm index 718244b284..82e786bcd4 100644 --- a/.fpm +++ b/.fpm @@ -6,6 +6,7 @@ --url "https://sing-box.sagernet.org/" --maintainer "nekohasekai " --deb-field "Bug: https://github.com/SagerNet/sing-box/issues" +--config-files /etc/sing-box/config.json release/config/config.json=/etc/sing-box/config.json diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml index fbd1ae427d..3212027ab2 100644 --- a/.goreleaser.fury.yaml +++ b/.goreleaser.fury.yaml @@ -48,7 +48,7 @@ nfpms: contents: - src: release/config/config.json dst: /etc/sing-box/config.json - type: config + type: "config|noreplace" - src: release/config/sing-box.service dst: /usr/lib/systemd/system/sing-box.service diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 3ada737739..87a2f45806 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -130,7 +130,7 @@ nfpms: contents: - src: release/config/config.json dst: /etc/sing-box/config.json - type: config + type: "config|noreplace" - src: release/config/sing-box.service dst: /usr/lib/systemd/system/sing-box.service From 10874d2dc4eba1dcff67cd53cf0619ec25bd582a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 2 Apr 2025 16:46:30 +0800 Subject: [PATCH 0272/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 5659088bb3..8354b78e5d 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 5659088bb3fe18b7095e4b9f868c181e27739617 +Subproject commit 8354b78e5d002d636827cfeed6ed5df8ea057452 diff --git a/docs/changelog.md b/docs/changelog.md index 62933f5ed3..4e563a81e2 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,12 @@ icon: material/alert-decagram --- +### 1.11.7 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ + ### 1.11.6 * Fixes and improvements From d4fa0ed3491e58d417be5044163b00687f669781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 2 Apr 2025 13:44:39 +0800 Subject: [PATCH 0273/1272] Improve auto redirect --- docs/configuration/inbound/tun.md | 16 ++++++++++++---- docs/configuration/inbound/tun.zh.md | 16 +++++++++++----- go.mod | 2 +- go.sum | 4 ++-- protocol/tun/inbound.go | 2 +- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index b6bf5c75f3..fbad1d0ac8 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -211,6 +211,10 @@ Set the default route to the Tun. By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`. +!!! note "Also enable `auto_redirect`" + + `auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts with Docker bridge networks. + #### iproute2_table_index !!! question "Since sing-box 1.10.0" @@ -237,6 +241,10 @@ Linux iproute2 rule start index generated by `auto_route`. Automatically configure iptables/nftables to redirect connections. +Auto redirect is always recommended on Linux, it provides better routing, +higher performance (better than tproxy), +and avoids conflicts with Docker bridge networks. + *In Android*: Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater, @@ -246,11 +254,13 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot). `auto_route` with `auto_redirect` works as expected on routers **without intervention**. +Conflict with `route.default_mark` and `[dialOptions].routing_mark`. + #### auto_redirect_input_mark !!! question "Since sing-box 1.10.0" -Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`. +Connection input mark used by `auto_redirect`. `0x2023` is used by default. @@ -258,7 +268,7 @@ Connection input mark used by `route[_exclude]_address_set` with `auto_redirect` !!! question "Since sing-box 1.10.0" -Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`. +Connection output mark used by `auto_redirect`. `0x2024` is used by default. @@ -367,8 +377,6 @@ Exclude custom routes when `auto_route` is enabled. Add the destination IP CIDR rules in the specified rule-sets to the firewall. Matched traffic will bypass the sing-box routes. - - Conflict with `route.default_mark` and `[dialOptions].routing_mark`. === "Without `auto_redirect` enabled" diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index c9bd844d41..e0e90cb82e 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -215,6 +215,10 @@ tun 接口的 IPv6 前缀。 VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。 +!!! note "也启用 `auto_redirect`" + + 在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy), 并避免与 Docker 桥接网络冲突。 + #### iproute2_table_index !!! question "自 sing-box 1.10.0 起" @@ -241,19 +245,23 @@ tun 接口的 IPv6 前缀。 自动配置 iptables/nftables 以重定向连接。 +在 Linux 上始终推荐使用 auto redirect,它提供更好的路由, 更高的性能(优于 tproxy), 并避免与 Docker 桥接网络冲突。 + *在 Android 中*: 仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。 *在 Linux 中*: -带有 `auto_redirect `的 `auto_route` 可以在路由器上按预期工作,**无需干预**。 +带有 `auto_redirect` 的 `auto_route` 在路由器上**无需干预**即可按预期工作。 + +与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 #### auto_redirect_input_mark !!! question "自 sing-box 1.10.0 起" -`route_address_set` 和 `route_exclude_address_set` 使用的连接输入标记。 +`auto_redriect` 使用的连接输入标记。 默认使用 `0x2023`。 @@ -261,7 +269,7 @@ tun 接口的 IPv6 前缀。 !!! question "自 sing-box 1.10.0 起" -`route_address_set` 和 `route_exclude_address_set` 使用的连接输出标记。 +`auto_redriect` 使用的连接输出标记。 默认使用 `0x2024`。 @@ -341,8 +349,6 @@ tun 接口的 IPv6 前缀。 将指定规则集中的目标 IP CIDR 规则添加到防火墙。 不匹配的流量将绕过 sing-box 路由。 - - 与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 === "`auto_redirect` 未启用" diff --git a/go.mod b/go.mod index 3c1ab6448f..3fef31c3e7 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0 - github.com/sagernet/sing-tun v0.6.1 + github.com/sagernet/sing-tun v0.6.4 github.com/sagernet/sing-vmess v0.2.0 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 7aa82f747f..27d3a156f8 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= -github.com/sagernet/sing-tun v0.6.1 h1:4l0+gnEKcGjlWfUVTD+W0BRApqIny/lU2ZliurE+VMo= -github.com/sagernet/sing-tun v0.6.1/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-tun v0.6.4 h1:3Iew6UtAf1+mucVeHKNhAEQI5xmq3CUCbGptUbjebts= +github.com/sagernet/sing-tun v0.6.4/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= diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 00cc0561e4..70f78c0abe 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -245,7 +245,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if err != nil { return nil, E.Cause(err, "initialize auto-redirect") } - if !C.IsAndroid && (len(inbound.routeRuleSet) > 0 || len(inbound.routeExcludeRuleSet) > 0) { + if !C.IsAndroid { inbound.tunOptions.AutoRedirectMarkMode = true err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) if err != nil { From c6531cf184ea632c5d577d7d4844a1175bd3594d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 9 Apr 2025 11:25:52 +0800 Subject: [PATCH 0274/1272] Fix NTP service --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3fef31c3e7..4cc25b6e1a 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7 + github.com/sagernet/sing v0.6.6 github.com/sagernet/sing-dns v0.4.1 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.1 diff --git a/go.sum b/go.sum index 27d3a156f8..d639308feb 100644 --- a/go.sum +++ b/go.sum @@ -121,6 +121,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7 h1:ZJauxLmH12Gzv3nucfjsSBQw9UA8t7Sxu8pYHBSP2TU= github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE= +github.com/sagernet/sing v0.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk= github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= From 2a24b94b8d39cd038aef06ee9e3358d9031d89e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 11 Apr 2025 10:24:46 +0800 Subject: [PATCH 0275/1272] Minor fixes --- common/tls/reality_server.go | 22 +++++++++++++--------- common/tls/std_server.go | 7 ++++++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index cf429815ee..48a17e63bf 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -89,16 +89,20 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference) tlsConfig.ShortIds = make(map[[8]byte]bool) - for i, shortIDString := range options.Reality.ShortID { - var shortID [8]byte - decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString)) - if err != nil { - return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString) - } - if decodedLen > 8 { - return nil, E.New("invalid short_id[", i, "]: ", shortIDString) + if len(options.Reality.ShortID) == 0 { + tlsConfig.ShortIds[[8]byte{0}] = true + } else { + for i, shortIDString := range options.Reality.ShortID { + var shortID [8]byte + decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString)) + if err != nil { + return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString) + } + if decodedLen > 8 { + return nil, E.New("invalid short_id[", i, "]: ", shortIDString) + } + tlsConfig.ShortIds[shortID] = true } - tlsConfig.ShortIds[shortID] = true } handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions) diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 1e01bc5003..949521d7dc 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -6,6 +6,7 @@ import ( "net" "os" "strings" + "time" "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" @@ -221,8 +222,12 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound key = content } if certificate == nil && key == nil && options.Insecure { + timeFunc := ntp.TimeFuncFromContext(ctx) + if timeFunc == nil { + timeFunc = time.Now + } tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { - return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName) + return GenerateKeyPair(nil, nil, timeFunc, info.ServerName) } } else { if certificate == nil { From 023218e6e77d5ed67b350d991d6ffc051f5cc86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=B3=E8=A5=BF=E5=A6=B2=20=C2=B7=20Nahida?= Date: Fri, 11 Apr 2025 22:03:10 +0800 Subject: [PATCH 0276/1272] Fix build will fail when use space to split each tag --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 461233271f..ea63367378 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ GOHOSTARCH = $(shell go env GOHOSTARCH) VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag) PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid=" -MAIN_PARAMS = $(PARAMS) -tags $(TAGS) +MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)" MAIN = ./cmd/sing-box PREFIX ?= $(shell go env GOPATH) @@ -28,7 +28,7 @@ ci_build: go build $(MAIN_PARAMS) $(MAIN) generate_completions: - go run -v --tags $(TAGS),generate,generate_completions $(MAIN) + go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN) install: go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) @@ -247,4 +247,4 @@ clean: update: git fetch git reset FETCH_HEAD --hard - git clean -fdx \ No newline at end of file + git clean -fdx From cb68a40c43710e9562ce37ae8617bf89442b8857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 12 Apr 2025 12:27:05 +0800 Subject: [PATCH 0277/1272] documentation: Update actual behaviors of `auto_redirect` and `strict_route` --- docs/configuration/inbound/tun.md | 31 ++++++++++----------- docs/configuration/inbound/tun.zh.md | 41 ++++++++++++---------------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index fbad1d0ac8..d00d813ac0 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -213,7 +213,7 @@ Set the default route to the Tun. !!! note "Also enable `auto_redirect`" - `auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts with Docker bridge networks. + `auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts between TUN and Docker bridge networks. #### iproute2_table_index @@ -239,20 +239,21 @@ Linux iproute2 rule start index generated by `auto_route`. Only supported on Linux with `auto_route` enabled. -Automatically configure iptables/nftables to redirect connections. +Improve TUN routing and performance using nftables. -Auto redirect is always recommended on Linux, it provides better routing, +`auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), -and avoids conflicts with Docker bridge networks. +and avoids conflicts between TUN and Docker bridge networks. -*In Android*: - -Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater, +Note that `auto_redirect` also works on Android, +but due to the lack of `nftables` and `ip6tables`, +only simple IPv4 TCP forwarding is performed. +To share your VPN connection over hotspot or repeater on Android, use [VPNHotspot](https://github.com/Mygod/VPNHotspot). -*In Linux*: - -`auto_route` with `auto_redirect` works as expected on routers **without intervention**. +`auto_redirect` also automatically inserts compatibility rules +into the OpenWrt fw4 table, i.e. +it will work on routers without any extra configuration. Conflict with `route.default_mark` and `[dialOptions].routing_mark`. @@ -279,17 +280,15 @@ Enforce strict routing rules when `auto_route` is enabled: *In Linux*: * Let unsupported network unreachable -* Make ICMP traffic route to tun instead of upstream interfaces -* Route all connections to tun - -It prevents IP address leaks and makes DNS hijacking work on Android. +* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN. *In Windows*: -* Add firewall rules to prevent DNS leak caused by +* Let unsupported network unreachable +* prevent DNS leak caused by Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) -It may prevent some applications (such as VirtualBox) from working properly in certain situations. +It may prevent some Windows applications (such as VirtualBox) from working properly in certain situations. #### route_address diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index e0e90cb82e..b3b561f1aa 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -217,7 +217,7 @@ tun 接口的 IPv6 前缀。 !!! note "也启用 `auto_redirect`" - 在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy), 并避免与 Docker 桥接网络冲突。 + 在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy), 并避免 TUN 与 Docker 桥接网络冲突。 #### iproute2_table_index @@ -241,19 +241,16 @@ tun 接口的 IPv6 前缀。 !!! quote "" - 仅支持 Linux,且需要 `auto_route` 已启用。 + 仅支持 Linux,且需要 `auto_route` 已启用。 -自动配置 iptables/nftables 以重定向连接。 +通过使用 nftables 改善 TUN 路由和性能。 -在 Linux 上始终推荐使用 auto redirect,它提供更好的路由, 更高的性能(优于 tproxy), 并避免与 Docker 桥接网络冲突。 +在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由、更高的性能(优于 tproxy),并避免了 TUN 和 Docker 桥接网络之间的冲突。 -*在 Android 中*: +请注意,`auto_redirect` 也适用于 Android,但由于缺少 `nftables` 和 `ip6tables`,仅执行简单的 IPv4 TCP 转发。 +若要在 Android 上通过热点或中继器共享 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。 -仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。 - -*在 Linux 中*: - -带有 `auto_redirect` 的 `auto_route` 在路由器上**无需干预**即可按预期工作。 +`auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中,即无需额外配置即可在路由器上工作。 与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 @@ -261,7 +258,7 @@ tun 接口的 IPv6 前缀。 !!! question "自 sing-box 1.10.0 起" -`auto_redriect` 使用的连接输入标记。 +`auto_redirect` 使用的连接输入标记。 默认使用 `0x2023`。 @@ -269,29 +266,25 @@ tun 接口的 IPv6 前缀。 !!! question "自 sing-box 1.10.0 起" -`auto_redriect` 使用的连接输出标记。 +`auto_redirect` 使用的连接输出标记。 默认使用 `0x2024`。 #### strict_route -启用 `auto_route` 时执行严格的路由规则。 - -*在 Linux 中*: +当启用 `auto_route` 时,强制执行严格的路由规则: -* 让不支持的网络无法到达 -* 使 ICMP 流量路由到 tun 而不是上游接口 -* 将所有连接路由到 tun +*在 Linux 中*: -它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。 +* 使不支持的网络不可达。 +* 出于历史遗留原因,当未启用 `strict_route` 或 `auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。 -*在 Windows 中*: +*在 Windows 中*: -* 添加防火墙规则以阻止 Windows - 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) - 造成的 DNS 泄露 +* 使不支持的网络不可达。 +* 阻止 Windows 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) 造成的 DNS 泄露 -它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。 +它可能会使某些 Windows 应用程序(如 VirtualBox)在某些情况下无法正常工作。 #### route_address From 5fdc051a084401da54d33de69ccf25faf087a309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 16 Apr 2025 17:03:53 +0800 Subject: [PATCH 0278/1272] Fix `override_port` in direct inbound --- protocol/direct/inbound.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go index a8af341782..a96e8326ae 100644 --- a/protocol/direct/inbound.go +++ b/protocol/direct/inbound.go @@ -94,7 +94,7 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a case 2: destination.Addr = i.overrideDestination.Addr case 3: - destination.Port = metadata.Destination.Port + destination.Port = i.overrideDestination.Port } metadata.Destination = destination if i.overrideOption != 0 { From 4c9455b94432d879437cfaab637d31edd78e6724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 16 Apr 2025 17:30:41 +0800 Subject: [PATCH 0279/1272] Fix wireguard endpoint --- protocol/wireguard/endpoint.go | 9 --------- protocol/wireguard/outbound.go | 9 --------- transport/wireguard/endpoint.go | 6 +----- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index c9751c3629..2f6825fd5c 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -26,11 +26,6 @@ func RegisterEndpoint(registry *endpoint.Registry) { endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint) } -var ( - _ adapter.Endpoint = (*Endpoint)(nil) - _ adapter.InterfaceUpdateListener = (*Endpoint)(nil) -) - type Endpoint struct { endpoint.Adapter ctx context.Context @@ -118,10 +113,6 @@ func (w *Endpoint) Close() error { return w.endpoint.Close() } -func (w *Endpoint) InterfaceUpdated() { - w.endpoint.BindUpdate() -} - func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { return w.router.PreMatch(adapter.InboundContext{ Inbound: w.Tag(), diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 7a516598c0..808dade45c 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -25,11 +25,6 @@ func RegisterOutbound(registry *outbound.Registry) { outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) } -var ( - _ adapter.Endpoint = (*Endpoint)(nil) - _ adapter.InterfaceUpdateListener = (*Endpoint)(nil) -) - type Outbound struct { outbound.Adapter ctx context.Context @@ -124,10 +119,6 @@ func (o *Outbound) Close() error { return o.endpoint.Close() } -func (o *Outbound) InterfaceUpdated() { - o.endpoint.BindUpdate() -} - func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch network { case N.NetworkTCP: diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 17a58a6ce3..bddd2a124c 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -150,7 +150,7 @@ func (e *Endpoint) Start(resolve bool) error { connectAddr netip.AddrPort reserved [3]uint8 ) - if len(e.peers) == 1 { + if len(e.peers) == 1 && e.peers[0].endpoint.IsValid() { isConnect = true connectAddr = e.peers[0].endpoint reserved = e.peers[0].reserved @@ -208,10 +208,6 @@ func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return e.tunDevice.ListenPacket(ctx, destination) } -func (e *Endpoint) BindUpdate() error { - return e.device.BindUpdate() -} - func (e *Endpoint) Close() error { if e.device != nil { e.device.Close() From afb499344564558dcb9612f0324628c35b949ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 16 Apr 2025 18:48:03 +0800 Subject: [PATCH 0280/1272] Fix urltest outbound --- protocol/group/urltest.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index e52ec90693..c1a5c597e7 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -395,12 +395,16 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint func (g *URLTestGroup) performUpdateCheck() { var updated bool if outbound, exists := g.Select(N.NetworkTCP); outbound != nil && (g.selectedOutboundTCP == nil || (exists && outbound != g.selectedOutboundTCP)) { + if g.selectedOutboundTCP != nil { + updated = true + } g.selectedOutboundTCP = outbound - updated = true } if outbound, exists := g.Select(N.NetworkUDP); outbound != nil && (g.selectedOutboundUDP == nil || (exists && outbound != g.selectedOutboundUDP)) { + if g.selectedOutboundUDP != nil { + updated = true + } g.selectedOutboundUDP = outbound - updated = true } if updated { g.interruptGroup.Interrupt(g.interruptExternalConnections) From 144a890c715537c48735a89d22e895d3f84b2f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 14 Apr 2025 22:21:11 +0800 Subject: [PATCH 0281/1272] release: Add openwrt packages --- .fpm_openwrt | 30 ++++++++ .fpm => .fpm_systemd | 3 +- .github/deb2ipk.sh | 28 +++++++ .github/workflows/build.yml | 87 +++++++++++++++------- docs/installation/package-manager.md | 82 ++++++++++++--------- docs/installation/package-manager.zh.md | 39 ++++++---- docs/installation/tools/install.sh | 97 +++++++++++++++++++++++++ release/config/openwrt.init | 13 ++-- release/config/openwrt.keep | 1 + release/config/openwrt.prerm | 4 + 10 files changed, 302 insertions(+), 82 deletions(-) create mode 100644 .fpm_openwrt rename .fpm => .fpm_systemd (93%) create mode 100755 .github/deb2ipk.sh create mode 100755 docs/installation/tools/install.sh mode change 100644 => 100755 release/config/openwrt.init create mode 100644 release/config/openwrt.keep create mode 100755 release/config/openwrt.prerm diff --git a/.fpm_openwrt b/.fpm_openwrt new file mode 100644 index 0000000000..ab8b6db64b --- /dev/null +++ b/.fpm_openwrt @@ -0,0 +1,30 @@ +-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 " +--no-deb-generate-changes + +--config-files /etc/config/sing-box +--config-files /etc/sing-box/config.json + +--depends ca-bundle +--depends kmod-inet-diag +--depends kmod-tun +--depends firewall4 + +--before-remove release/config/openwrt.prerm + +release/config/config.json=/etc/sing-box/config.json + +release/config/openwrt.conf=/etc/config/sing-box +release/config/openwrt.init=/etc/init.d/sing-box +release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box + +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/.fpm b/.fpm_systemd similarity index 93% rename from .fpm rename to .fpm_systemd index 82e786bcd4..62d42588ea 100644 --- a/.fpm +++ b/.fpm_systemd @@ -1,11 +1,12 @@ -s dir --name sing-box --category net ---license GPLv3-or-later +--license GPL-3.0-or-later --description "The universal proxy platform." --url "https://sing-box.sagernet.org/" --maintainer "nekohasekai " --deb-field "Bug: https://github.com/SagerNet/sing-box/issues" +--no-deb-generate-changes --config-files /etc/sing-box/config.json release/config/config.json=/etc/sing-box/config.json diff --git a/.github/deb2ipk.sh b/.github/deb2ipk.sh new file mode 100755 index 0000000000..0b820533c6 --- /dev/null +++ b/.github/deb2ipk.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683 + +set -e -o pipefail + +PROJECT=$(dirname "$0")/../.. +TMP_PATH=`mktemp -d` +cp $2 $TMP_PATH +pushd $TMP_PATH + +DEB_NAME=`ls *.deb` +ar x $DEB_NAME + +mkdir control +pushd control +tar xf ../control.tar.gz +rm md5sums +sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i +cat control +tar czf ../control.tar.gz ./* +popd + +DEB_NAME=${DEB_NAME%.deb} +tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary +popd + +cp $TMP_PATH/$DEB_NAME.ipk $3 +rm -r $TMP_PATH diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 211743fa32..4d8232bb94 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,32 +68,39 @@ jobs: - calculate_version strategy: matrix: - os: [ linux, windows, darwin, android ] - arch: [ "386", amd64, arm64 ] - legacy_go: [ false ] include: - - { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 } - - { os: linux, arch: "386", debian: i386, rpm: i386 } - - { 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: 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: "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" } + - { 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: 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 } - - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 } + - { 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: "386", legacy_go: true } + - { os: windows, arch: amd64 } - { os: windows, arch: amd64, legacy_go: true } + - { os: windows, arch: "386" } + - { os: windows, arch: "386", legacy_go: true } + - { os: windows, arch: arm64 } + + - { os: darwin, arch: amd64 } - { os: darwin, arch: amd64, legacy_go: true } + - { os: darwin, arch: arm64 } - - { os: android, arch: "386", ndk: "i686-linux-android21" } - - { os: android, arch: amd64, ndk: "x86_64-linux-android21" } - { os: android, arch: arm64, ndk: "aarch64-linux-android21" } - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" } - exclude: - - { os: darwin, arch: "386" } + - { os: android, arch: amd64, ndk: "x86_64-linux-android21" } + - { os: android, arch: "386", ndk: "i686-linux-android21" } steps: - name: Checkout uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 @@ -139,7 +146,10 @@ jobs: 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' @@ -159,14 +169,19 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set name run: |- - ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true) - LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true) - DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}" - PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}" + DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}" + if [[ -n "${{ matrix.goarm }}" ]]; then + DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}" + elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then + DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}" + elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then + DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}" + elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then + DIR_NAME="${DIR_NAME}-legacy" + fi echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" - echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}" PKG_VERSION="${{ needs.calculate_version.outputs.version }}" - PKG_VERSION="${PKG_VERSION//-/\~}" + PKG_VERSION="${PKG_VERSION//-/\~}-1" echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}" - name: Package DEB if: matrix.debian != '' @@ -174,9 +189,10 @@ jobs: set -xeuo pipefail sudo gem install fpm sudo apt-get install -y debsigs + cp .fpm_systemd .fpm fpm -t deb \ -v "$PKG_VERSION" \ - -p "dist/${PKG_NAME}.deb" \ + -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \ --architecture ${{ matrix.debian }} \ dist/sing-box=/usr/bin/sing-box curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff' @@ -191,9 +207,10 @@ jobs: run: |- set -xeuo pipefail sudo gem install fpm + cp .fpm_systemd .fpm fpm -t rpm \ -v "$PKG_VERSION" \ - -p "dist/${PKG_NAME}.rpm" \ + -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \ --architecture ${{ matrix.rpm }} \ dist/sing-box=/usr/bin/sing-box cat > $HOME/.rpmmacros < /dev/null - sudo apt-get update - sudo apt-get install sing-box # or sing-box-beta - ``` - -=== ":material-redhat: Redhat / DNF" - - ```bash - sudo dnf -y install dnf-plugins-core - sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo - sudo dnf install sing-box # or sing-box-beta - ``` - (This applies to any distribution that uses `dnf` as the package manager: Fedora, CentOS, even OpenSUSE with DNF installed.) + ```bash + sudo mkdir -p /etc/apt/keyrings && + sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc && + sudo chmod a+r /etc/apt/keyrings/sagernet.asc && + echo ' + Types: deb + URIs: https://deb.sagernet.org/ + Suites: * + Components: * + Enabled: yes + Signed-By: /etc/apt/keyrings/sagernet.asc + ' | sudo tee /etc/apt/sources.list.d/sagernet.sources && + sudo apt-get update && + sudo apt-get install sing-box # or sing-box-beta + ``` + +=== ":material-redhat: Redhat / DNF 5" + + ```bash + sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && + sudo dnf install sing-box # or sing-box-beta + ``` + +=== ":material-redhat: Redhat / DNF 4" + + ```bash + sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && + sudo dnf -y install dnf-plugins-core && + sudo dnf install sing-box # or sing-box-beta + ``` ## :material-download-box: Manual Installation -=== ":material-debian: Debian / DEB" - - ```bash - bash <(curl -fsSL https://sing-box.app/deb-install.sh) - ``` - -=== ":material-redhat: Redhat / RPM" - - ```bash - bash <(curl -fsSL https://sing-box.app/rpm-install.sh) - ``` - (This applies to any distribution that uses `rpm` and `systemd`. Because of how `rpm` defines dependencies, if it installs, it probably works.) - -=== ":simple-archlinux: Archlinux / PKG" - - ```bash - bash <(curl -fsSL https://sing-box.app/arch-install.sh) - ``` + The script download and install the latest package from GitHub releases for deb or rpm based Linux distributions, ArchLinux and OpenWrt. + + ```shell + curl -fsSL https://sing-box.app/install.sh | sh + ``` + + or latest beta: + + ```shell + curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta + ``` + + or specific version: + + ```shell + curl -fsSL https://sing-box.app/install.sh | sh -s -- --version + ``` ## :material-book-lock-open: Managed Installation diff --git a/docs/installation/package-manager.zh.md b/docs/installation/package-manager.zh.md index e4ff9da359..b59e95cbb2 100644 --- a/docs/installation/package-manager.zh.md +++ b/docs/installation/package-manager.zh.md @@ -9,22 +9,35 @@ icon: material/package === ":material-debian: Debian / APT" ```bash - sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc - sudo chmod a+r /etc/apt/keyrings/sagernet.asc - echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \ - sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null - sudo apt-get update - sudo apt-get install sing-box # or sing-box-beta + sudo mkdir -p /etc/apt/keyrings && + sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc && + sudo chmod a+r /etc/apt/keyrings/sagernet.asc && + echo ' + Types: deb + URIs: https://deb.sagernet.org/ + Suites: * + Components: * + Enabled: yes + Signed-By: /etc/apt/keyrings/sagernet.asc + ' | sudo tee /etc/apt/sources.list.d/sagernet.sources && + sudo apt-get update && + sudo apt-get install sing-box # or sing-box-beta ``` -=== ":material-redhat: Redhat / DNF" +=== ":material-redhat: Redhat / DNF 5" - ```bash - sudo dnf -y install dnf-plugins-core - sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo - sudo dnf install sing-box # or sing-box-beta - ``` - (这适用于任何使用 `dnf` 作为包管理器的发行版:Fedora、CentOS,甚至安装了 DNF 的 OpenSUSE。) + ```bash + sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && + sudo dnf install sing-box # or sing-box-beta + ``` + +=== ":material-redhat: Redhat / DNF 4" + + ```bash + sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && + sudo dnf -y install dnf-plugins-core && + sudo dnf install sing-box # or sing-box-beta + ``` ## :material-download-box: 手动安装 diff --git a/docs/installation/tools/install.sh b/docs/installation/tools/install.sh new file mode 100755 index 0000000000..e042dde8dd --- /dev/null +++ b/docs/installation/tools/install.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +download_beta=false +download_version="" + +for arg in "$@"; do + if [[ "$arg" == "--beta" ]]; then + download_beta=true + elif [[ "$arg" == "--version" ]]; then + download_version=true + elif [[ "$download_version" == 'true' ]]; then + download_version="$arg" + else + echo "Unknown argument: $arg" + echo "Usage: $0 [--beta] [--version ]" + exit 1 + fi +done + +if [[ $(command -v dpkg) ]]; then + os="linux" + arch=$(dpkg --print-architecture) + package_suffix=".deb" + package_install="dpkg -i" +elif [[ $(command -v dnf) ]]; then + os="linux" + arch=$(uname -m) + package_suffix=".rpm" + package_install="dnf install -y" +elif [[ $(command -v rpm) ]]; then + os="linux" + arch=$(uname -m) + package_suffix=".rpm" + package_install="rpm -i" +elif [[ $(command -v pacman) ]]; then + os="linux" + arch=$(uname -m) + package_suffix=".pkg.tar.zst" + package_install="pacman -U --noconfirm" +elif [[ $(command -v opkg) ]]; then + os="openwrt" + source /etc/os-release + arch="$OPENWRT_ARCH" + package_suffix=".ipk" + package_install="opkg update && opkg install -y" +else + echo "Missing supported package manager." + exit 1 +fi + +if [[ -z "$download_version" ]]; then + if [[ "$download_beta" != 'true' ]]; then + if [[ -n "$GITHUB_TOKEN" ]]; then + latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest) + else + latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases/latest) + fi + curl_exit_status=$? + if [[ $curl_exit_status -ne 0 ]]; then + echo "$latest_release" + exit $? + fi + download_version=$(echo "$latest_release" | grep tag_name | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//') + else + if [[ -n "$GITHUB_TOKEN" ]]; then + latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases) + else + latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases) + fi + curl_exit_status=$? + if [[ $? -ne 0 ]]; then + echo "$latest_release" + exit $? + fi + download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//') + fi +fi + +package_name="sing-box_${download_version}_${os}_${arch}${package_suffix}" +package_url="https://github.com/SagerNet/sing-box/releases/download/v${download_version}/${package_name}" + +echo "Downloading $package_url" +if [[ -n "$GITHUB_TOKEN" ]]; then + curl --fail-with-body -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url" +else + curl --fail-with-body -Lo "$package_name" "$package_url" +fi + +if [[ $? -ne 0 ]]; then + exit $? +fi + +if [[ $(command -v sudo) ]]; then + package_install="sudo $package_install" +fi + +echo "$package_install $package_name" && $package_install "$package_name" && rm "$package_name" diff --git a/release/config/openwrt.init b/release/config/openwrt.init old mode 100644 new mode 100755 index bae70e4b2a..9979fc1ae0 --- a/release/config/openwrt.init +++ b/release/config/openwrt.init @@ -1,26 +1,27 @@ #!/bin/sh /etc/rc.common +USE_PROCD=1 +START=99 PROG="/usr/bin/sing-box" start_service() { config_load "sing-box" local enabled config_file working_directory - local log_stdout log_stderr + local log_stderr config_get_bool enabled "main" "enabled" "0" [ "$enabled" -eq "1" ] || return 0 config_get config_file "main" "conffile" "/etc/sing-box/config.json" config_get working_directory "main" "workdir" "/usr/share/sing-box" - config_get_bool log_stdout "main" "log_stdout" "1" config_get_bool log_stderr "main" "log_stderr" "1" procd_open_instance - procd_swet_param command "$PROG" run -c "$conffile" -D "$workdir" - procd_set_param file "$conffile" + procd_set_param command "$PROG" run -c "$config_file" -D "$working_directory" + procd_set_param file "$config_file" procd_set_param stderr "$log_stderr" procd_set_param limits core="unlimited" - sprocd_set_param limits nofile="1000000 1000000" + procd_set_param limits nofile="1000000 1000000" procd_set_param respawn procd_close_instance @@ -28,4 +29,4 @@ start_service() { service_triggers() { procd_add_reload_trigger "sing-box" -} \ No newline at end of file +} diff --git a/release/config/openwrt.keep b/release/config/openwrt.keep new file mode 100644 index 0000000000..b26dd531f4 --- /dev/null +++ b/release/config/openwrt.keep @@ -0,0 +1 @@ +/etc/sing-box/ diff --git a/release/config/openwrt.prerm b/release/config/openwrt.prerm new file mode 100755 index 0000000000..12d06ec78b --- /dev/null +++ b/release/config/openwrt.prerm @@ -0,0 +1,4 @@ +#!/bin/sh +[ -s ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0 +. ${IPKG_INSTROOT}/lib/functions.sh +default_prerm $0 $@ From 49498f643926c6f109f918f148c533298b4c8352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 16 Apr 2025 17:57:10 +0800 Subject: [PATCH 0282/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 8354b78e5d..55f31c29bb 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 8354b78e5d002d636827cfeed6ed5df8ea057452 +Subproject commit 55f31c29bb68895ce544e0dfbf852b4b3e32b530 diff --git a/docs/changelog.md b/docs/changelog.md index 4e563a81e2..864fdf7ddb 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,18 @@ icon: material/alert-decagram --- +### 1.11.8 + +* Improve `auto_redirect` **1** +* Fixes and improvements + +**1**: + +Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks, +see [Tun](/configuration/inbound/tun/#auto_redirect). + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ + ### 1.11.7 * Fixes and improvements From 6a051054dba8d5d2b3367bbdf7105cc863aa809b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 19 Apr 2025 18:38:12 +0800 Subject: [PATCH 0283/1272] release: Fix packages --- .github/workflows/build.yml | 2 +- .github/workflows/linux.yml | 2 + docs/installation/package-manager.md | 83 +++++++++--------- docs/installation/package-manager.zh.md | 43 +++++----- docs/installation/tools/install.sh | 107 ++++++++++++++---------- 5 files changed, 129 insertions(+), 108 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d8232bb94..2a7c4b0dbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -181,7 +181,7 @@ jobs: fi echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" PKG_VERSION="${{ needs.calculate_version.outputs.version }}" - PKG_VERSION="${PKG_VERSION//-/\~}-1" + PKG_VERSION="${PKG_VERSION//-/\~}" echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}" - name: Package DEB if: matrix.debian != '' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 965929df90..7393e65410 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -120,6 +120,7 @@ jobs: set -xeuo pipefail sudo gem install fpm sudo apt-get install -y debsigs + cp .fpm_systemd .fpm fpm -t deb \ --name "${NAME}" \ -v "$PKG_VERSION" \ @@ -138,6 +139,7 @@ jobs: run: |- set -xeuo pipefail sudo gem install fpm + cp .fpm_systemd .fpm fpm -t rpm \ --name "${NAME}" \ -v "$PKG_VERSION" \ diff --git a/docs/installation/package-manager.md b/docs/installation/package-manager.md index c54eee8732..fe59c73aa6 100644 --- a/docs/installation/package-manager.md +++ b/docs/installation/package-manager.md @@ -8,56 +8,57 @@ icon: material/package === ":material-debian: Debian / APT" - ```bash - sudo mkdir -p /etc/apt/keyrings && - sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc && - sudo chmod a+r /etc/apt/keyrings/sagernet.asc && - echo ' - Types: deb - URIs: https://deb.sagernet.org/ - Suites: * - Components: * - Enabled: yes - Signed-By: /etc/apt/keyrings/sagernet.asc - ' | sudo tee /etc/apt/sources.list.d/sagernet.sources && - sudo apt-get update && - sudo apt-get install sing-box # or sing-box-beta - ``` + ```bash + sudo mkdir -p /etc/apt/keyrings && + sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc && + sudo chmod a+r /etc/apt/keyrings/sagernet.asc && + echo ' + Types: deb + URIs: https://deb.sagernet.org/ + Suites: * + Components: * + Enabled: yes + Signed-By: /etc/apt/keyrings/sagernet.asc + ' | sudo tee /etc/apt/sources.list.d/sagernet.sources && + sudo apt-get update && + sudo apt-get install sing-box # or sing-box-beta + ``` === ":material-redhat: Redhat / DNF 5" - ```bash - sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && - sudo dnf install sing-box # or sing-box-beta - ``` + ```bash + sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && + sudo dnf install sing-box # or sing-box-beta + ``` === ":material-redhat: Redhat / DNF 4" - ```bash - sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && - sudo dnf -y install dnf-plugins-core && - sudo dnf install sing-box # or sing-box-beta - ``` + ```bash + sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && + sudo dnf -y install dnf-plugins-core && + sudo dnf install sing-box # or sing-box-beta + ``` ## :material-download-box: Manual Installation - The script download and install the latest package from GitHub releases for deb or rpm based Linux distributions, ArchLinux and OpenWrt. - - ```shell - curl -fsSL https://sing-box.app/install.sh | sh - ``` - - or latest beta: - - ```shell - curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta - ``` - - or specific version: - - ```shell - curl -fsSL https://sing-box.app/install.sh | sh -s -- --version - ``` +The script download and install the latest package from GitHub releases +for deb or rpm based Linux distributions, ArchLinux and OpenWrt. + +```shell +curl -fsSL https://sing-box.app/install.sh | sh +``` + +or latest beta: + +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta +``` + +or specific version: + +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --version +``` ## :material-book-lock-open: Managed Installation diff --git a/docs/installation/package-manager.zh.md b/docs/installation/package-manager.zh.md index b59e95cbb2..60905d1df3 100644 --- a/docs/installation/package-manager.zh.md +++ b/docs/installation/package-manager.zh.md @@ -26,39 +26,38 @@ icon: material/package === ":material-redhat: Redhat / DNF 5" - ```bash - sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && - sudo dnf install sing-box # or sing-box-beta - ``` + ```bash + sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && + sudo dnf install sing-box # or sing-box-beta + ``` === ":material-redhat: Redhat / DNF 4" - ```bash - sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && - sudo dnf -y install dnf-plugins-core && - sudo dnf install sing-box # or sing-box-beta - ``` + ```bash + sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && + sudo dnf -y install dnf-plugins-core && + sudo dnf install sing-box # or sing-box-beta + ``` ## :material-download-box: 手动安装 -=== ":material-debian: Debian / DEB" +该脚本从 GitHub 发布中下载并安装最新的软件包,适用于基于 deb 或 rpm 的 Linux 发行版、ArchLinux 和 OpenWrt。 - ```bash - bash <(curl -fsSL https://sing-box.app/deb-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh +``` -=== ":material-redhat: Redhat / RPM" +或最新测试版: - ```bash - bash <(curl -fsSL https://sing-box.app/rpm-install.sh) - ``` - (这适用于任何使用 `rpm` 和 `systemd` 的发行版。由于 `rpm` 定义依赖关系的方式,如果安装成功,就多半能用。) +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta +``` -=== ":simple-archlinux: Archlinux / PKG" +或指定版本: - ```bash - bash <(curl -fsSL https://sing-box.app/arch-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --version +``` ## :material-book-lock-open: 托管安装 diff --git a/docs/installation/tools/install.sh b/docs/installation/tools/install.sh index e042dde8dd..74166f022e 100755 --- a/docs/installation/tools/install.sh +++ b/docs/installation/tools/install.sh @@ -3,76 +3,92 @@ download_beta=false download_version="" -for arg in "$@"; do - if [[ "$arg" == "--beta" ]]; then - download_beta=true - elif [[ "$arg" == "--version" ]]; then - download_version=true - elif [[ "$download_version" == 'true' ]]; then - download_version="$arg" - else - echo "Unknown argument: $arg" - echo "Usage: $0 [--beta] [--version ]" - exit 1 - fi +while [ $# -gt 0 ]; do + case "$1" in + --beta) + download_beta=true + shift + ;; + --version) + shift + if [ $# -eq 0 ]; then + echo "Missing argument for --version" + echo "Usage: $0 [--beta] [--version ]" + exit 1 + fi + download_version="$1" + shift + ;; + *) + echo "Unknown argument: $1" + echo "Usage: $0 [--beta] [--version ]" + exit 1 + ;; + esac done -if [[ $(command -v dpkg) ]]; then +if command -v pacman >/dev/null 2>&1; then + os="linux" + arch=$(uname -m) + package_suffix=".pkg.tar.zst" + package_install="pacman -U --noconfirm" +elif command -v dpkg >/dev/null 2>&1; then os="linux" arch=$(dpkg --print-architecture) package_suffix=".deb" package_install="dpkg -i" -elif [[ $(command -v dnf) ]]; then +elif command -v dnf >/dev/null 2>&1; then os="linux" arch=$(uname -m) package_suffix=".rpm" package_install="dnf install -y" -elif [[ $(command -v rpm) ]]; then +elif command -v rpm >/dev/null 2>&1; then os="linux" arch=$(uname -m) package_suffix=".rpm" package_install="rpm -i" -elif [[ $(command -v pacman) ]]; then - os="linux" - arch=$(uname -m) - package_suffix=".pkg.tar.zst" - package_install="pacman -U --noconfirm" -elif [[ $(command -v opkg) ]]; then +elif command -v opkg >/dev/null 2>&1; then os="openwrt" - source /etc/os-release + . /etc/os-release arch="$OPENWRT_ARCH" package_suffix=".ipk" - package_install="opkg update && opkg install -y" + package_install="opkg update && opkg install" else echo "Missing supported package manager." exit 1 fi -if [[ -z "$download_version" ]]; then - if [[ "$download_beta" != 'true' ]]; then - if [[ -n "$GITHUB_TOKEN" ]]; then - latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest) +if [ -z "$download_version" ]; then + if [ "$download_beta" != "true" ]; then + if [ -n "$GITHUB_TOKEN" ]; then + latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest) else - latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases/latest) + latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest) fi curl_exit_status=$? - if [[ $curl_exit_status -ne 0 ]]; then + if [ $curl_exit_status -ne 0 ]; then + exit $curl_exit_status + fi + if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then echo "$latest_release" - exit $? + exit 1 fi - download_version=$(echo "$latest_release" | grep tag_name | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//') + download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g') else - if [[ -n "$GITHUB_TOKEN" ]]; then - latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases) + if [ -n "$GITHUB_TOKEN" ]; then + latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases) else - latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases) + latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases) fi curl_exit_status=$? - if [[ $? -ne 0 ]]; then + if [ $curl_exit_status -ne 0 ]; then + exit $curl_exit_status + fi + if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then echo "$latest_release" - exit $? + exit 1 fi - download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//') + download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g') fi fi @@ -80,18 +96,21 @@ package_name="sing-box_${download_version}_${os}_${arch}${package_suffix}" package_url="https://github.com/SagerNet/sing-box/releases/download/v${download_version}/${package_name}" echo "Downloading $package_url" -if [[ -n "$GITHUB_TOKEN" ]]; then - curl --fail-with-body -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url" +if [ -n "$GITHUB_TOKEN" ]; then + curl --fail -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url" else - curl --fail-with-body -Lo "$package_name" "$package_url" + curl --fail -Lo "$package_name" "$package_url" fi -if [[ $? -ne 0 ]]; then - exit $? +curl_exit_status=$? +if [ $curl_exit_status -ne 0 ]; then + exit $curl_exit_status fi -if [[ $(command -v sudo) ]]; then +if command -v sudo >/dev/null 2>&1; then package_install="sudo $package_install" fi -echo "$package_install $package_name" && $package_install "$package_name" && rm "$package_name" +echo "$package_install $package_name" +sh -c "$package_install \"$package_name\"" +rm -f "$package_name" From c54d50fd36b786cf1f7912b14e5ded6d5ee268c9 Mon Sep 17 00:00:00 2001 From: dyhkwong <50692134+dyhkwong@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:44:34 +0800 Subject: [PATCH 0284/1272] Fix websocket detour Signed-off-by: trimgop <20010323+trimgop@users.noreply.github.com> Co-authored-by: trimgop <20010323+trimgop@users.noreply.github.com> --- transport/v2raywebsocket/client.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/transport/v2raywebsocket/client.go b/transport/v2raywebsocket/client.go index 9c4950765c..748bae4cee 100644 --- a/transport/v2raywebsocket/client.go +++ b/transport/v2raywebsocket/client.go @@ -91,10 +91,7 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h } else { deadlineConn = conn } - err = deadlineConn.SetDeadline(time.Now().Add(C.TCPTimeout)) - if err != nil { - return nil, E.Cause(err, "set read deadline") - } + deadlineConn.SetDeadline(time.Now().Add(C.TCPTimeout)) var protocols []string if protocolHeader := headers.Get("Sec-WebSocket-Protocol"); protocolHeader != "" { protocols = []string{protocolHeader} From f2bbf6b2aaafd10ab7a6ca52e790732a4bcf447f Mon Sep 17 00:00:00 2001 From: dyhkwong <50692134+dyhkwong@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:44:55 +0800 Subject: [PATCH 0285/1272] Fix sniffer errors override each others * Fix sniffer errors override each others * Do not return ErrNeedMoreData if header is not expected --- common/sniff/bittorrent.go | 11 ++++++++--- common/sniff/bittorrent_test.go | 21 +++++++++++++++++++++ common/sniff/dns.go | 22 ++++++++++++++++++---- common/sniff/dns_test.go | 30 ++++++++++++++++++++++++++++++ common/sniff/sniff.go | 2 +- common/sniff/ssh.go | 5 +++-- common/sniff/ssh_test.go | 21 +++++++++++++++++++++ 7 files changed, 102 insertions(+), 10 deletions(-) diff --git a/common/sniff/bittorrent.go b/common/sniff/bittorrent.go index 39c1959812..e4d9f4b8f6 100644 --- a/common/sniff/bittorrent.go +++ b/common/sniff/bittorrent.go @@ -31,13 +31,18 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R return os.ErrInvalid } + const header = "BitTorrent protocol" var protocol [19]byte - _, err = reader.Read(protocol[:]) + var n int + n, err = reader.Read(protocol[:]) + if string(protocol[:n]) != header[:n] { + return os.ErrInvalid + } if err != nil { return E.Cause1(ErrNeedMoreData, err) } - if string(protocol[:]) != "BitTorrent protocol" { - return os.ErrInvalid + if n < 19 { + return ErrNeedMoreData } metadata.Protocol = C.ProtocolBitTorrent diff --git a/common/sniff/bittorrent_test.go b/common/sniff/bittorrent_test.go index f4762e3233..fcb5f6fa42 100644 --- a/common/sniff/bittorrent_test.go +++ b/common/sniff/bittorrent_test.go @@ -32,6 +32,27 @@ func TestSniffBittorrent(t *testing.T) { } } +func TestSniffIncompleteBittorrent(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("13426974546f7272656e74") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffNotBittorrent(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("13426974546f7272656e75") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NotEmpty(t, err) + require.NotErrorIs(t, err, sniff.ErrNeedMoreData) +} + func TestSniffUTP(t *testing.T) { t.Parallel() diff --git a/common/sniff/dns.go b/common/sniff/dns.go index 2e22f3d7f5..7125a08e0e 100644 --- a/common/sniff/dns.go +++ b/common/sniff/dns.go @@ -20,22 +20,36 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon if err != nil { return E.Cause1(ErrNeedMoreData, err) } - if length == 0 { + if length < 12 { return os.ErrInvalid } buffer := buf.NewSize(int(length)) defer buffer.Release() - _, err = buffer.ReadFullFrom(reader, buffer.FreeLen()) + var n int + n, err = buffer.ReadFullFrom(reader, buffer.FreeLen()) + packet := buffer.Bytes() + if n > 2 && packet[2]&0x80 != 0 { // QR + return os.ErrInvalid + } + if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT + return os.ErrInvalid + } + for i := 6; i < 10; i++ { + // ANCOUNT, NSCOUNT + if n > i && packet[i] != 0 { + return os.ErrInvalid + } + } if err != nil { return E.Cause1(ErrNeedMoreData, err) } - return DomainNameQuery(readCtx, metadata, buffer.Bytes()) + return DomainNameQuery(readCtx, metadata, packet) } func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { var msg mDNS.Msg err := msg.Unpack(packet) - if err != nil { + if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 { return err } metadata.Protocol = C.ProtocolDNS diff --git a/common/sniff/dns_test.go b/common/sniff/dns_test.go index eaf4dd1acb..d78b0bf591 100644 --- a/common/sniff/dns_test.go +++ b/common/sniff/dns_test.go @@ -1,6 +1,7 @@ package sniff_test import ( + "bytes" "context" "encoding/hex" "testing" @@ -21,3 +22,32 @@ func TestSniffDNS(t *testing.T) { require.NoError(t, err) require.Equal(t, C.ProtocolDNS, metadata.Protocol) } + +func TestSniffStreamDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query)) + require.NoError(t, err) + require.Equal(t, C.ProtocolDNS, metadata.Protocol) +} + +func TestSniffIncompleteStreamDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("001e740701000001000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query)) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffNotStreamDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("001e740701000000000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query)) + require.NotEmpty(t, err) + require.NotErrorIs(t, err, sniff.ErrNeedMoreData) +} diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 59e81aaacf..b3651e1ffa 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -68,7 +68,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net. } sniffError = E.Errors(sniffError, err) } - if !errors.Is(err, ErrNeedMoreData) { + if !errors.Is(sniffError, ErrNeedMoreData) { break } } diff --git a/common/sniff/ssh.go b/common/sniff/ssh.go index d373d292cd..dce5d54f62 100644 --- a/common/sniff/ssh.go +++ b/common/sniff/ssh.go @@ -15,10 +15,11 @@ func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) const sshPrefix = "SSH-2.0-" bReader := bufio.NewReader(reader) prefix, err := bReader.Peek(len(sshPrefix)) + if string(prefix[:]) != sshPrefix[:len(prefix)] { + return os.ErrInvalid + } if err != nil { return E.Cause1(ErrNeedMoreData, err) - } else if string(prefix) != sshPrefix { - return os.ErrInvalid } fistLine, _, err := bReader.ReadLine() if err != nil { diff --git a/common/sniff/ssh_test.go b/common/sniff/ssh_test.go index be53098079..7cea5aab0f 100644 --- a/common/sniff/ssh_test.go +++ b/common/sniff/ssh_test.go @@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) { require.Equal(t, C.ProtocolSSH, metadata.Protocol) require.Equal(t, "dropbear", metadata.Client) } + +func TestSniffIncompleteSSH(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("5353482d322e30") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffNotSSH(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("5353482d322e31") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NotEmpty(t, err) + require.NotErrorIs(t, err, sniff.ErrNeedMoreData) +} From e6d19de58ab9cade6456c29bdede54b50ced9f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 22 Apr 2025 14:55:44 +0800 Subject: [PATCH 0286/1272] Fix overriding address --- route/route.go | 1 + 1 file changed, 1 insertion(+) diff --git a/route/route.go b/route/route.go index d6611b4fa3..f3e7a98304 100644 --- a/route/route.go +++ b/route/route.go @@ -418,6 +418,7 @@ match: Port: metadata.Destination.Port, Fqdn: routeOptions.OverrideAddress.Fqdn, } + metadata.DestinationAddresses = nil } if routeOptions.OverridePort > 0 { metadata.Destination = M.Socksaddr{ From f600e02e471b5719f01d938edd73b08f78a4ae8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 22 Apr 2025 23:02:06 +0800 Subject: [PATCH 0287/1272] Fix DNS crash --- go.mod | 2 +- go.sum | 6 ++---- route/route_dns.go | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 4cc25b6e1a..7713ea34e3 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.6 - github.com/sagernet/sing-dns v0.4.1 + github.com/sagernet/sing-dns v0.4.2 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.1 github.com/sagernet/sing-shadowsocks v0.2.7 diff --git a/go.sum b/go.sum index d639308feb..1e1d615a4f 100644 --- a/go.sum +++ b/go.sum @@ -119,12 +119,10 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.6-0.20250406121928-926a5a1e8bb7 h1:ZJauxLmH12Gzv3nucfjsSBQw9UA8t7Sxu8pYHBSP2TU= -github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE= github.com/sagernet/sing v0.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk= -github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= +github.com/sagernet/sing-dns v0.4.2 h1:cWe2XPUBFLep2j9kJV4Epg3bctGhMvrrl/sWi9Wszfg= +github.com/sagernet/sing-dns v0.4.2/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= 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 h1:pxlMa4efZu/M07RgGagNNDDyl6ZUwpmNUjRTpgHOWK4= diff --git a/route/route_dns.go b/route/route_dns.go index 3d7dc64faf..62eff1c263 100644 --- a/route/route_dns.go +++ b/route/route_dns.go @@ -223,6 +223,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS err error ) printResult := func() { + if err == nil && len(responseAddrs) == 0 { + err = E.New("empty result") + } if err != nil { if errors.Is(err, dns.ErrResponseRejectedCached) { r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)") @@ -231,9 +234,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS } else { r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) } - } else if len(responseAddrs) == 0 { - r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") - err = dns.RCodeNameError } } responseAddrs, cached = r.dnsClient.LookupCache(ctx, domain, strategy) From 3a84acf122f9a985c0442b873e482b443651c22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 24 Apr 2025 21:46:23 +0800 Subject: [PATCH 0288/1272] Fix hysteria1 server panic --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7713ea34e3..97b5dfc2bd 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sagernet/sing v0.6.6 github.com/sagernet/sing-dns v0.4.2 github.com/sagernet/sing-mux v0.3.1 - github.com/sagernet/sing-quic v0.4.1 + github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0 diff --git a/go.sum b/go.sum index 1e1d615a4f..a7fcb2bea5 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,8 @@ github.com/sagernet/sing-dns v0.4.2 h1:cWe2XPUBFLep2j9kJV4Epg3bctGhMvrrl/sWi9Wsz github.com/sagernet/sing-dns v0.4.2/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= 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 h1:pxlMa4efZu/M07RgGagNNDDyl6ZUwpmNUjRTpgHOWK4= -github.com/sagernet/sing-quic v0.4.1/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= +github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 h1:iwpCX6H3nZEOGUGwx0q5azcgYOA9f6v9YssihXoRKHk= +github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= 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= From 56a7624618a2103e5a891f346b345aa55a61a377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 08:55:26 +0800 Subject: [PATCH 0289/1272] Fix vmess working with zero uuids --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 97b5dfc2bd..bdf364c9ca 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0 github.com/sagernet/sing-tun v0.6.4 - github.com/sagernet/sing-vmess v0.2.0 + github.com/sagernet/sing-vmess v0.2.1 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.1-beta.5 diff --git a/go.sum b/go.sum index a7fcb2bea5..00877c2dab 100644 --- a/go.sum +++ b/go.sum @@ -135,8 +135,8 @@ github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDar github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= github.com/sagernet/sing-tun v0.6.4 h1:3Iew6UtAf1+mucVeHKNhAEQI5xmq3CUCbGptUbjebts= github.com/sagernet/sing-tun v0.6.4/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/sing-vmess v0.2.1 h1:6izHC2+B68aQCxTagki6eZZc+g5eh4dYwxOV5a2Lhug= +github.com/sagernet/sing-vmess v0.2.1/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/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= From 787b5f193192ff749c47db523bb6b351def80b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 09:02:20 +0800 Subject: [PATCH 0290/1272] Fix set wireguard reserved on Linux --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bdf364c9ca..954473016c 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/sagernet/sing-vmess v0.2.1 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 - github.com/sagernet/wireguard-go v0.0.1-beta.5 + github.com/sagernet/wireguard-go v0.0.1-beta.6 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 00877c2dab..a84a94e7c9 100644 --- a/go.sum +++ b/go.sum @@ -141,8 +141,8 @@ github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxe github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= 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/wireguard-go v0.0.1-beta.6 h1:ID+kijtqBqZLRT+0oDsKidN5gGjS6MqSvwKsZKPdqzQ= +github.com/sagernet/wireguard-go v0.0.1-beta.6/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/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= From 03e8d029c26832970e95b86c1b6e035f740a931c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 09:07:41 +0800 Subject: [PATCH 0291/1272] release: Fix apt-get install --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2a7c4b0dbb..025fe84530 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -188,6 +188,7 @@ jobs: run: | set -xeuo pipefail sudo gem install fpm + sudo apt-get update sudo apt-get install -y debsigs cp .fpm_systemd .fpm fpm -t deb \ @@ -226,6 +227,7 @@ jobs: run: |- set -xeuo pipefail sudo gem install fpm + sudo apt-get update sudo apt-get install -y libarchive-tools cp .fpm_systemd .fpm fpm -t pacman \ From 7c7f5124058276107a881a9378cfca668fadc3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 09:47:45 +0800 Subject: [PATCH 0292/1272] option: Fix omitempty reject method --- option/rule_action.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/option/rule_action.go b/option/rule_action.go index b700362824..35f334d63d 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -252,6 +252,14 @@ type _RejectActionOptions struct { type RejectActionOptions _RejectActionOptions +func (r RejectActionOptions) MarshalJSON() ([]byte, error) { + switch r.Method { + case C.RuleActionRejectMethodDefault: + r.Method = "" + } + return json.Marshal((_RejectActionOptions)(r)) +} + func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { err := json.Unmarshal(bytes, (*_RejectActionOptions)(r)) if err != nil { From 300c961efadf75d56495538e044ff4974f4b3b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 09:51:54 +0800 Subject: [PATCH 0293/1272] option: Fix listable again and again --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 954473016c..2cb53398d5 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.6 + github.com/sagernet/sing v0.6.7 github.com/sagernet/sing-dns v0.4.2 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 diff --git a/go.sum b/go.sum index a84a94e7c9..3bc20fa1b3 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE= -github.com/sagernet/sing v0.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.7 h1:NIWBLZ9AUWDXAQBKGleKwsitbQrI9M0nqoheXhUKnrI= +github.com/sagernet/sing v0.6.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.2 h1:cWe2XPUBFLep2j9kJV4Epg3bctGhMvrrl/sWi9Wszfg= github.com/sagernet/sing-dns v0.4.2/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= From e5e81b4de12d030bd61e949eb566649cd752da67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 09:55:54 +0800 Subject: [PATCH 0294/1272] Fix wireguard listening --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2cb53398d5..13ac86fd07 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/sagernet/sing-vmess v0.2.1 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 - github.com/sagernet/wireguard-go v0.0.1-beta.6 + github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 3bc20fa1b3..755be1c07d 100644 --- a/go.sum +++ b/go.sum @@ -141,8 +141,8 @@ github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxe github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= 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.6 h1:ID+kijtqBqZLRT+0oDsKidN5gGjS6MqSvwKsZKPdqzQ= -github.com/sagernet/wireguard-go v0.0.1-beta.6/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= +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/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= From 134802d1ee080a01dad548ab374e0ccbad99c457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 11:35:06 +0800 Subject: [PATCH 0295/1272] Fix ssh outbound --- protocol/ssh/outbound.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/protocol/ssh/outbound.go b/protocol/ssh/outbound.go index eb9970b5fb..b329d4b3e6 100644 --- a/protocol/ssh/outbound.go +++ b/protocol/ssh/outbound.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" @@ -191,9 +192,29 @@ func (s *Outbound) DialContext(ctx context.Context, network string, destination if err != nil { return nil, err } - return client.Dial(network, destination.String()) + conn, err := client.Dial(network, destination.String()) + if err != nil { + return nil, err + } + return &chanConnWrapper{Conn: conn}, nil } func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } + +type chanConnWrapper struct { + net.Conn +} + +func (c *chanConnWrapper) SetDeadline(t time.Time) error { + return os.ErrInvalid +} + +func (c *chanConnWrapper) SetReadDeadline(t time.Time) error { + return os.ErrInvalid +} + +func (c *chanConnWrapper) SetWriteDeadline(t time.Time) error { + return os.ErrInvalid +} From d8b2d5142f922a9c7fab589b9b5083eb42116601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 11:49:25 +0800 Subject: [PATCH 0296/1272] Fix panic on some stupid input --- route/rule/rule_default.go | 5 ++++- route/rule/rule_dns.go | 5 ++++- route/rule/rule_headless.go | 5 ++++- route/rule/rule_item_domain.go | 15 +++++++++++++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 2794c28751..56cf32dc07 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -102,7 +102,10 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.allItems = append(rule.allItems, item) } if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { - item := NewDomainItem(options.Domain, options.DomainSuffix) + item, err := NewDomainItem(options.Domain, options.DomainSuffix) + if err != nil { + return nil, err + } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index fb8c6b786f..d6eb7104f5 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -93,7 +93,10 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { - item := NewDomainItem(options.Domain, options.DomainSuffix) + item, err := NewDomainItem(options.Domain, options.DomainSuffix) + if err != nil { + return nil, err + } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index 619856a5a5..ba17ca374c 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -47,7 +47,10 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR rule.allItems = append(rule.allItems, item) } if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { - item := NewDomainItem(options.Domain, options.DomainSuffix) + item, err := NewDomainItem(options.Domain, options.DomainSuffix) + if err != nil { + return nil, err + } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } else if options.DomainMatcher != nil { diff --git a/route/rule/rule_item_domain.go b/route/rule/rule_item_domain.go index b7655a7942..af790aa385 100644 --- a/route/rule/rule_item_domain.go +++ b/route/rule/rule_item_domain.go @@ -5,6 +5,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common/domain" + E "github.com/sagernet/sing/common/exceptions" ) var _ RuleItem = (*DomainItem)(nil) @@ -14,7 +15,17 @@ type DomainItem struct { description string } -func NewDomainItem(domains []string, domainSuffixes []string) *DomainItem { +func NewDomainItem(domains []string, domainSuffixes []string) (*DomainItem, error) { + for _, domainItem := range domains { + if domainItem == "" { + return nil, E.New("domain: empty item is not allowed") + } + } + for _, domainSuffixItem := range domainSuffixes { + if domainSuffixItem == "" { + return nil, E.New("domain_suffix: empty item is not allowed") + } + } var description string if dLen := len(domains); dLen > 0 { if dLen == 1 { @@ -40,7 +51,7 @@ func NewDomainItem(domains []string, domainSuffixes []string) *DomainItem { return &DomainItem{ domain.NewMatcher(domains, domainSuffixes, false), description, - } + }, nil } func NewRawDomainItem(matcher *domain.Matcher) *DomainItem { From 987899f94a7d32f144b23feb4d4060e404956bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 27 Apr 2025 21:29:23 +0800 Subject: [PATCH 0297/1272] Fix usages of wireguard listener --- common/dialer/default.go | 12 +++++++++++- transport/wireguard/endpoint.go | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index 77536c43db..bd8d901861 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -333,7 +333,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - return d.udpListener.ListenPacket(context.Background(), network, address) + 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 trackConn(conn net.Conn, err error) (net.Conn, error) { diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index bddd2a124c..3801640fe9 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -141,7 +141,7 @@ func (e *Endpoint) Start(resolve bool) error { return nil } var bind conn.Bind - wgListener, isWgListener := e.options.Dialer.(conn.Listener) + wgListener, isWgListener := common.Cast[conn.Listener](e.options.Dialer) if isWgListener { bind = conn.NewStdNetBind(wgListener) } else { From 348cc39975bc0d384c6b9d38c4ec0590d8ccf8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 27 Apr 2025 21:29:51 +0800 Subject: [PATCH 0298/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 55f31c29bb..6a15780ce1 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 55f31c29bb68895ce544e0dfbf852b4b3e32b530 +Subproject commit 6a15780ce1659a234816f7248cbc09e8ea54a1be diff --git a/docs/changelog.md b/docs/changelog.md index 864fdf7ddb..93964734f3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,12 @@ icon: material/alert-decagram --- +### 1.11.9 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ + ### 1.11.8 * Improve `auto_redirect` **1** From 1c0ffcf5b1481442f6cbb8104693c00e6a89775e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 28 Apr 2025 11:20:23 +0800 Subject: [PATCH 0299/1272] Fix counter position in auto redirect dnat rules --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 13ac86fd07..69c3f6e7ba 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0 - github.com/sagernet/sing-tun v0.6.4 + github.com/sagernet/sing-tun v0.6.5 github.com/sagernet/sing-vmess v0.2.1 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 755be1c07d..37caa69e25 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= -github.com/sagernet/sing-tun v0.6.4 h1:3Iew6UtAf1+mucVeHKNhAEQI5xmq3CUCbGptUbjebts= -github.com/sagernet/sing-tun v0.6.4/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-tun v0.6.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffssg= +github.com/sagernet/sing-tun v0.6.5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.1 h1:6izHC2+B68aQCxTagki6eZZc+g5eh4dYwxOV5a2Lhug= github.com/sagernet/sing-vmess v0.2.1/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= From 2d7df1e1f2a5427e4d8fc0fa0e0181eab5409023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 28 Apr 2025 23:12:39 +0800 Subject: [PATCH 0300/1272] Fix hysteria bytes format --- common/humanize/bytes.go | 158 ---------------------------------- debug.go | 6 +- debug_http.go | 8 +- experimental/libbox/setup.go | 6 +- go.mod | 2 +- go.sum | 4 +- option/debug.go | 47 ++-------- option/hysteria.go | 44 +++++----- protocol/hysteria/inbound.go | 16 +--- protocol/hysteria/outbound.go | 15 +--- 10 files changed, 53 insertions(+), 253 deletions(-) delete mode 100644 common/humanize/bytes.go diff --git a/common/humanize/bytes.go b/common/humanize/bytes.go deleted file mode 100644 index 6ee4d26898..0000000000 --- a/common/humanize/bytes.go +++ /dev/null @@ -1,158 +0,0 @@ -package humanize - -import ( - "fmt" - "math" - "strconv" - "strings" - "unicode" -) - -// IEC Sizes. -// kibis of bits -const ( - Byte = 1 << (iota * 10) - KiByte - MiByte - GiByte - TiByte - PiByte - EiByte -) - -// SI Sizes. -const ( - IByte = 1 - KByte = IByte * 1000 - MByte = KByte * 1000 - GByte = MByte * 1000 - TByte = GByte * 1000 - PByte = TByte * 1000 - EByte = PByte * 1000 -) - -var defaultSizeTable = map[string]uint64{ - "b": Byte, - "kib": KiByte, - "kb": KByte, - "mib": MiByte, - "mb": MByte, - "gib": GiByte, - "gb": GByte, - "tib": TiByte, - "tb": TByte, - "pib": PiByte, - "pb": PByte, - "eib": EiByte, - "eb": EByte, - // Without suffix - "": Byte, - "ki": KiByte, - "k": KByte, - "mi": MiByte, - "m": MByte, - "gi": GiByte, - "g": GByte, - "ti": TiByte, - "t": TByte, - "pi": PiByte, - "p": PByte, - "ei": EiByte, - "e": EByte, -} - -var memorysSizeTable = map[string]uint64{ - "b": Byte, - "kb": KiByte, - "mb": MiByte, - "gb": GiByte, - "tb": TiByte, - "pb": PiByte, - "eb": EiByte, - "": Byte, - "k": KiByte, - "m": MiByte, - "g": GiByte, - "t": TiByte, - "p": PiByte, - "e": EiByte, -} - -var ( - defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} - iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} -) - -func Bytes(s uint64) string { - return humanateBytes(s, 1000, defaultSizes) -} - -func MemoryBytes(s uint64) string { - return humanateBytes(s, 1024, defaultSizes) -} - -func IBytes(s uint64) string { - return humanateBytes(s, 1024, iSizes) -} - -func logn(n, b float64) float64 { - return math.Log(n) / math.Log(b) -} - -func humanateBytes(s uint64, base float64, sizes []string) string { - if s < 10 { - return fmt.Sprintf("%d B", s) - } - e := math.Floor(logn(float64(s), base)) - suffix := sizes[int(e)] - val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 - f := "%.0f %s" - if val < 10 { - f = "%.1f %s" - } - - return fmt.Sprintf(f, val, suffix) -} - -func ParseBytes(s string) (uint64, error) { - return parseBytes0(s, defaultSizeTable) -} - -func ParseMemoryBytes(s string) (uint64, error) { - return parseBytes0(s, memorysSizeTable) -} - -func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) { - lastDigit := 0 - hasComma := false - for _, r := range s { - if !(unicode.IsDigit(r) || r == '.' || r == ',') { - break - } - if r == ',' { - hasComma = true - } - lastDigit++ - } - - num := s[:lastDigit] - if hasComma { - num = strings.Replace(num, ",", "", -1) - } - - f, err := strconv.ParseFloat(num, 64) - if err != nil { - return 0, err - } - - extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) - if m, ok := sizeTable[extra]; ok { - f *= float64(m) - if f >= math.MaxUint64 { - return 0, fmt.Errorf("too large: %v", s) - } - return uint64(f), nil - } - - return 0, fmt.Errorf("unhandled size name: %v", extra) -} diff --git a/debug.go b/debug.go index 2fa962d642..1726c10e92 100644 --- a/debug.go +++ b/debug.go @@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) { if options.TraceBack != "" { debug.SetTraceback(options.TraceBack) } - if options.MemoryLimit != 0 { - debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5)) - conntrack.MemoryLimit = uint64(options.MemoryLimit) + 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 diff --git a/debug_http.go b/debug_http.go index 3215977821..e51a073143 100644 --- a/debug_http.go +++ b/debug_http.go @@ -7,9 +7,9 @@ import ( "runtime/debug" "strings" - "github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/byteformats" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) { runtime.ReadMemStats(&memStats) var memObject badjson.JSONObject - memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse)) - memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse)) - memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased)) + memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse)) + memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse)) + memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased)) memObject.Put("goroutines", runtime.NumGoroutine()) memObject.Put("rss", rusageMaxRSS()) diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index 184d525012..ad898fee7c 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -7,10 +7,10 @@ import ( "strconv" "time" - "github.com/sagernet/sing-box/common/humanize" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/locale" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/byteformats" ) var ( @@ -75,11 +75,11 @@ func Version() string { } func FormatBytes(length int64) string { - return humanize.Bytes(uint64(length)) + return byteformats.FormatBytes(uint64(length)) } func FormatMemoryBytes(length int64) string { - return humanize.MemoryBytes(uint64(length)) + return byteformats.FormatMemoryBytes(uint64(length)) } func FormatDuration(duration int64) string { diff --git a/go.mod b/go.mod index 69c3f6e7ba..2d99bb619d 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.7 + github.com/sagernet/sing v0.6.8-0.20250429070844-b63643251ed5 github.com/sagernet/sing-dns v0.4.2 github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 diff --git a/go.sum b/go.sum index 37caa69e25..11987904d7 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.7 h1:NIWBLZ9AUWDXAQBKGleKwsitbQrI9M0nqoheXhUKnrI= -github.com/sagernet/sing v0.6.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.8-0.20250429070844-b63643251ed5 h1:j5r2x3Lazb1giycHFxICc201LSyDn3ZH+ywa+8duNxo= +github.com/sagernet/sing v0.6.8-0.20250429070844-b63643251ed5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.2 h1:cWe2XPUBFLep2j9kJV4Epg3bctGhMvrrl/sWi9Wszfg= github.com/sagernet/sing-dns v0.4.2/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= diff --git a/option/debug.go b/option/debug.go index 0b0b825a82..3dfef7b0ca 100644 --- a/option/debug.go +++ b/option/debug.go @@ -1,43 +1,14 @@ package option -import ( - "github.com/sagernet/sing-box/common/humanize" - "github.com/sagernet/sing/common/json" -) +import "github.com/sagernet/sing/common/byteformats" type DebugOptions struct { - Listen string `json:"listen,omitempty"` - GCPercent *int `json:"gc_percent,omitempty"` - MaxStack *int `json:"max_stack,omitempty"` - MaxThreads *int `json:"max_threads,omitempty"` - PanicOnFault *bool `json:"panic_on_fault,omitempty"` - TraceBack string `json:"trace_back,omitempty"` - MemoryLimit MemoryBytes `json:"memory_limit,omitempty"` - OOMKiller *bool `json:"oom_killer,omitempty"` -} - -type MemoryBytes uint64 - -func (l MemoryBytes) MarshalJSON() ([]byte, error) { - return json.Marshal(humanize.MemoryBytes(uint64(l))) -} - -func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error { - var valueInteger int64 - err := json.Unmarshal(bytes, &valueInteger) - if err == nil { - *l = MemoryBytes(valueInteger) - return nil - } - var valueString string - err = json.Unmarshal(bytes, &valueString) - if err != nil { - return err - } - parsedValue, err := humanize.ParseMemoryBytes(valueString) - if err != nil { - return err - } - *l = MemoryBytes(parsedValue) - return nil + Listen string `json:"listen,omitempty"` + GCPercent *int `json:"gc_percent,omitempty"` + MaxStack *int `json:"max_stack,omitempty"` + MaxThreads *int `json:"max_threads,omitempty"` + PanicOnFault *bool `json:"panic_on_fault,omitempty"` + TraceBack string `json:"trace_back,omitempty"` + MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"` + OOMKiller *bool `json:"oom_killer,omitempty"` } diff --git a/option/hysteria.go b/option/hysteria.go index beb3685eac..6332be11bd 100644 --- a/option/hysteria.go +++ b/option/hysteria.go @@ -1,17 +1,19 @@ package option +import "github.com/sagernet/sing/common/byteformats" + type HysteriaInboundOptions struct { ListenOptions - Up string `json:"up,omitempty"` - UpMbps int `json:"up_mbps,omitempty"` - Down string `json:"down,omitempty"` - DownMbps int `json:"down_mbps,omitempty"` - Obfs string `json:"obfs,omitempty"` - Users []HysteriaUser `json:"users,omitempty"` - ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` - ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"` - MaxConnClient int `json:"max_conn_client,omitempty"` - DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` + Up *byteformats.NetworkBytesCompat `json:"up,omitempty"` + UpMbps int `json:"up_mbps,omitempty"` + Down *byteformats.NetworkBytesCompat `json:"down,omitempty"` + DownMbps int `json:"down_mbps,omitempty"` + Obfs string `json:"obfs,omitempty"` + Users []HysteriaUser `json:"users,omitempty"` + ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` + ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"` + MaxConnClient int `json:"max_conn_client,omitempty"` + DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` InboundTLSOptionsContainer } @@ -24,16 +26,16 @@ type HysteriaUser struct { type HysteriaOutboundOptions struct { DialerOptions ServerOptions - Up string `json:"up,omitempty"` - UpMbps int `json:"up_mbps,omitempty"` - Down string `json:"down,omitempty"` - DownMbps int `json:"down_mbps,omitempty"` - Obfs string `json:"obfs,omitempty"` - Auth []byte `json:"auth,omitempty"` - AuthString string `json:"auth_str,omitempty"` - ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` - ReceiveWindow uint64 `json:"recv_window,omitempty"` - DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` - Network NetworkList `json:"network,omitempty"` + Up *byteformats.NetworkBytesCompat `json:"up,omitempty"` + UpMbps int `json:"up_mbps,omitempty"` + Down *byteformats.NetworkBytesCompat `json:"down,omitempty"` + DownMbps int `json:"down_mbps,omitempty"` + Obfs string `json:"obfs,omitempty"` + Auth []byte `json:"auth,omitempty"` + AuthString string `json:"auth_str,omitempty"` + ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` + ReceiveWindow uint64 `json:"recv_window,omitempty"` + DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` + Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer } diff --git a/protocol/hysteria/inbound.go b/protocol/hysteria/inbound.go index 872e4dd770..5afc440d37 100644 --- a/protocol/hysteria/inbound.go +++ b/protocol/hysteria/inbound.go @@ -7,7 +7,6 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/inbound" - "github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -16,7 +15,6 @@ import ( "github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -56,19 +54,13 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo tlsConfig: tlsConfig, } var sendBps, receiveBps uint64 - if len(options.Up) > 0 { - sendBps, err = humanize.ParseBytes(options.Up) - if err != nil { - return nil, E.Cause(err, "invalid up speed format: ", options.Up) - } + if options.Up.Value() > 0 { + sendBps = options.Up.Value() } else { sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps } - if len(options.Down) > 0 { - receiveBps, err = humanize.ParseBytes(options.Down) - if err != nil { - return nil, E.Cause(err, "invalid down speed format: ", options.Down) - } + if options.Down.Value() > 0 { + receiveBps = options.Down.Value() } else { receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps } diff --git a/protocol/hysteria/outbound.go b/protocol/hysteria/outbound.go index e1d8716c45..e59b17a14b 100644 --- a/protocol/hysteria/outbound.go +++ b/protocol/hysteria/outbound.go @@ -8,7 +8,6 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -59,19 +58,13 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL password = string(options.Auth) } var sendBps, receiveBps uint64 - if len(options.Up) > 0 { - sendBps, err = humanize.ParseBytes(options.Up) - if err != nil { - return nil, E.Cause(err, "invalid up speed format: ", options.Up) - } + if options.Up.Value() > 0 { + sendBps = options.Up.Value() } else { sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps } - if len(options.Down) > 0 { - receiveBps, err = humanize.ParseBytes(options.Down) - if err != nil { - return nil, E.Cause(err, "invalid down speed format: ", options.Down) - } + if options.Down.Value() > 0 { + receiveBps = options.Down.Value() } else { receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps } From d574e9eb5291f6b1d38e0b70594b4ebafb3ed5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 30 Apr 2025 19:39:15 +0800 Subject: [PATCH 0301/1272] Update smux to v1.5.34 --- go.mod | 4 ++-- go.sum | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 2d99bb619d..5c7d9ba5ed 100644 --- a/go.mod +++ b/go.mod @@ -28,14 +28,14 @@ require ( github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.8-0.20250429070844-b63643251ed5 github.com/sagernet/sing-dns v0.4.2 - github.com/sagernet/sing-mux v0.3.1 + github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0 github.com/sagernet/sing-tun v0.6.5 github.com/sagernet/sing-vmess v0.2.1 - github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 + github.com/sagernet/smux v1.5.34-mod.1 github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 diff --git a/go.sum b/go.sum index 11987904d7..307ae12fc5 100644 --- a/go.sum +++ b/go.sum @@ -118,13 +118,13 @@ github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+ 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.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.8-0.20250429070844-b63643251ed5 h1:j5r2x3Lazb1giycHFxICc201LSyDn3ZH+ywa+8duNxo= github.com/sagernet/sing v0.6.8-0.20250429070844-b63643251ed5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.2 h1:cWe2XPUBFLep2j9kJV4Epg3bctGhMvrrl/sWi9Wszfg= github.com/sagernet/sing-dns v0.4.2/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= -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-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= +github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 h1:iwpCX6H3nZEOGUGwx0q5azcgYOA9f6v9YssihXoRKHk= github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= @@ -137,8 +137,8 @@ github.com/sagernet/sing-tun v0.6.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffs github.com/sagernet/sing-tun v0.6.5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.1 h1:6izHC2+B68aQCxTagki6eZZc+g5eh4dYwxOV5a2Lhug= github.com/sagernet/sing-vmess v0.2.1/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/smux v1.5.34-mod.1 h1:xZljMK3fVOX4HC+ND1N7eOiweqEa9bxRTKlliqe9DJE= +github.com/sagernet/smux v1.5.34-mod.1/go.mod h1:qI3fpNiLZmwrh83DmbJHX7sAsc2R/gbqdWw0/WzciU0= 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.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= @@ -150,8 +150,15 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.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/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= @@ -190,7 +197,7 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From af1bfe4e3eed9a00bbba55b891c22afee93773b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 1 May 2025 11:00:01 +0800 Subject: [PATCH 0302/1272] Make `rule_set.format` optional --- cmd/sing-box/cmd_rule_set_match.go | 9 +++++ docs/configuration/rule-set/index.md | 2 ++ docs/configuration/rule-set/index.zh.md | 2 ++ option/rule_set.go | 44 ++++++++++++++++++++++--- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index f5016117d7..e3dc19b8f8 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -5,6 +5,7 @@ import ( "context" "io" "os" + "path/filepath" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/srs" @@ -56,6 +57,14 @@ func ruleSetMatch(sourcePath string, domain string) error { if err != nil { return E.Cause(err, "read rule-set") } + if flagRuleSetMatchFormat == "" { + switch filepath.Ext(sourcePath) { + case ".json": + flagRuleSetMatchFormat = C.RuleSetFormatSource + case ".srs": + flagRuleSetMatchFormat = C.RuleSetFormatBinary + } + } var ruleSet option.PlainRuleSetCompat switch flagRuleSetMatchFormat { case C.RuleSetFormatSource: diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index c905e110fb..ea27860841 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -84,6 +84,8 @@ List of [Headless Rule](./headless-rule/). Format of rule-set file, `source` or `binary`. +Optional when `path` or `url` uses `json` or `srs` as extension. + ### Local Fields #### path diff --git a/docs/configuration/rule-set/index.zh.md b/docs/configuration/rule-set/index.zh.md index c3b86b6d84..17fdfa5aa9 100644 --- a/docs/configuration/rule-set/index.zh.md +++ b/docs/configuration/rule-set/index.zh.md @@ -84,6 +84,8 @@ icon: material/new-box 规则集格式, `source` 或 `binary`。 +当 `path` 或 `url` 使用 `json` 或 `srs` 作为扩展名时可选。 + ### 本地字段 #### path diff --git a/option/rule_set.go b/option/rule_set.go index bf644764ad..610d7ba2a2 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -1,6 +1,8 @@ package option import ( + "net/url" + "path/filepath" "reflect" C "github.com/sagernet/sing-box/constant" @@ -27,6 +29,18 @@ type _RuleSet struct { type RuleSet _RuleSet func (r RuleSet) MarshalJSON() ([]byte, error) { + if r.Type != C.RuleSetTypeInline { + var defaultFormat string + switch r.Type { + case C.RuleSetTypeLocal: + defaultFormat = ruleSetDefaultFormat(r.LocalOptions.Path) + case C.RuleSetTypeRemote: + defaultFormat = ruleSetDefaultFormat(r.RemoteOptions.URL) + } + if r.Format == defaultFormat { + r.Format = "" + } + } var v any switch r.Type { case "", C.RuleSetTypeInline: @@ -62,7 +76,19 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule-set type: " + r.Type) } + err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v) + if err != nil { + return err + } if r.Type != C.RuleSetTypeInline { + if r.Format == "" { + switch r.Type { + case C.RuleSetTypeLocal: + r.Format = ruleSetDefaultFormat(r.LocalOptions.Path) + case C.RuleSetTypeRemote: + r.Format = ruleSetDefaultFormat(r.RemoteOptions.URL) + } + } switch r.Format { case "": return E.New("missing format") @@ -73,13 +99,23 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { } else { r.Format = "" } - err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v) - if err != nil { - return err - } return nil } +func ruleSetDefaultFormat(path string) string { + if pathURL, err := url.Parse(path); err == nil { + path = pathURL.Path + } + switch filepath.Ext(path) { + case ".json": + return C.RuleSetFormatSource + case ".srs": + return C.RuleSetFormatBinary + default: + return "" + } +} + type LocalRuleSet struct { Path string `json:"path,omitempty"` } From 7dea6eb7a63cb40c1dffb8e0c1d041767c0dc4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 4 May 2025 18:13:51 +0800 Subject: [PATCH 0303/1272] Fix missing read waiter for cancelers --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5c7d9ba5ed..cde1595096 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.8-0.20250429070844-b63643251ed5 + github.com/sagernet/sing v0.6.8 github.com/sagernet/sing-dns v0.4.2 github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 diff --git a/go.sum b/go.sum index 307ae12fc5..8b049e189b 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.6.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.6.8-0.20250429070844-b63643251ed5 h1:j5r2x3Lazb1giycHFxICc201LSyDn3ZH+ywa+8duNxo= -github.com/sagernet/sing v0.6.8-0.20250429070844-b63643251ed5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.8 h1:rpi0MozOxq2xD2/sAqXJDaoxLOvGU+foBs1a6HsYErg= +github.com/sagernet/sing v0.6.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.2 h1:cWe2XPUBFLep2j9kJV4Epg3bctGhMvrrl/sWi9Wszfg= github.com/sagernet/sing-dns v0.4.2/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= From 8b489354e4f63237384c0202556d1eafec418d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 4 May 2025 18:45:46 +0800 Subject: [PATCH 0304/1272] Undeprecate the `block` outbound --- docs/configuration/outbound/block.md | 4 ---- docs/configuration/outbound/block.zh.md | 4 ---- option/outbound.go | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/docs/configuration/outbound/block.md b/docs/configuration/outbound/block.md index 1be04dd5b6..6e2322632c 100644 --- a/docs/configuration/outbound/block.md +++ b/docs/configuration/outbound/block.md @@ -2,10 +2,6 @@ icon: material/delete-clock --- -!!! failure "Deprecated in sing-box 1.11.0" - - Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions). - ### Structure ```json diff --git a/docs/configuration/outbound/block.zh.md b/docs/configuration/outbound/block.zh.md index 822478cea8..93227476ec 100644 --- a/docs/configuration/outbound/block.zh.md +++ b/docs/configuration/outbound/block.zh.md @@ -2,10 +2,6 @@ icon: material/delete-clock --- -!!! failure "已在 sing-box 1.11.0 废弃" - - 旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions). - `block` 出站关闭所有传入请求。 ### 结构 diff --git a/option/outbound.go b/option/outbound.go index 5cadd3e2ae..dc7a6f526b 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -39,7 +39,7 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err return E.New("missing outbound options registry in context") } switch h.Type { - case C.TypeBlock, C.TypeDNS: + case C.TypeDNS: deprecated.Report(ctx, deprecated.OptionSpecialOutbounds) } options, loaded := registry.CreateOptions(h.Type) From 101fb8825518604cded33f15e23df84706349d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 5 May 2025 09:37:44 +0800 Subject: [PATCH 0305/1272] Fix allocator put --- go.mod | 4 ++-- go.sum | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index cde1595096..cfd1d8dd95 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.8 + github.com/sagernet/sing v0.6.9 github.com/sagernet/sing-dns v0.4.2 github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 @@ -35,7 +35,7 @@ require ( github.com/sagernet/sing-shadowtls v0.2.0 github.com/sagernet/sing-tun v0.6.5 github.com/sagernet/sing-vmess v0.2.1 - github.com/sagernet/smux v1.5.34-mod.1 + github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 diff --git a/go.sum b/go.sum index 8b049e189b..7dfbf9e93b 100644 --- a/go.sum +++ b/go.sum @@ -118,9 +118,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+ 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.6.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.6.8 h1:rpi0MozOxq2xD2/sAqXJDaoxLOvGU+foBs1a6HsYErg= -github.com/sagernet/sing v0.6.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.9 h1:y/XJH17oyBd6hxgQtKnIdLXu7TsOHxO5i1JeVfVmjXw= +github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.2 h1:cWe2XPUBFLep2j9kJV4Epg3bctGhMvrrl/sWi9Wszfg= github.com/sagernet/sing-dns v0.4.2/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= @@ -137,8 +136,8 @@ github.com/sagernet/sing-tun v0.6.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffs github.com/sagernet/sing-tun v0.6.5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.1 h1:6izHC2+B68aQCxTagki6eZZc+g5eh4dYwxOV5a2Lhug= github.com/sagernet/sing-vmess v0.2.1/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= -github.com/sagernet/smux v1.5.34-mod.1 h1:xZljMK3fVOX4HC+ND1N7eOiweqEa9bxRTKlliqe9DJE= -github.com/sagernet/smux v1.5.34-mod.1/go.mod h1:qI3fpNiLZmwrh83DmbJHX7sAsc2R/gbqdWw0/WzciU0= +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/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.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= From aff12ff67192a7d5a79f902400c983f4bed70e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 4 May 2025 18:46:09 +0800 Subject: [PATCH 0306/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 31 +++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/clients/android b/clients/android index 6a15780ce1..9dd336679d 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 6a15780ce1659a234816f7248cbc09e8ea54a1be +Subproject commit 9dd336679d14a1d16c6d72b1a67c4abc4ccae4c0 diff --git a/docs/changelog.md b/docs/changelog.md index 93964734f3..4fe24664a9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,11 +2,25 @@ icon: material/alert-decagram --- +### 1.11.10 + +* Undeprecate the `block` outbound **1** +* Fixes and improvements + +**1**: + +Since we don’t have a replacement for using the `block` outbound in selectors yet, +we decided to temporarily undeprecate the `block` outbound until a replacement is available in the future. + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + ### 1.11.9 * Fixes and improvements -_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ ### 1.11.8 @@ -18,25 +32,29 @@ _We are temporarily unable to update sing-box apps on the App Store because the Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks, see [Tun](/configuration/inbound/tun/#auto_redirect). -_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ ### 1.11.7 * Fixes and improvements -_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ ### 1.11.6 * Fixes and improvements -_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ ### 1.11.5 * Fixes and improvements -_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ ### 1.11.4 @@ -46,7 +64,8 @@ _We are temporarily unable to update sing-box apps on the App Store because the * Fixes and improvements -_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._ +_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration +process._ ### 1.11.1 From 13e648e4b15b146092f02b5f98239e32e6c60873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 7 May 2025 15:12:17 +0800 Subject: [PATCH 0307/1272] Fix set edns0 subnet --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cfd1d8dd95..3ace9231a6 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.9 - github.com/sagernet/sing-dns v0.4.2 + github.com/sagernet/sing-dns v0.4.3 github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 github.com/sagernet/sing-shadowsocks v0.2.7 diff --git a/go.sum b/go.sum index 7dfbf9e93b..8e23f50afb 100644 --- a/go.sum +++ b/go.sum @@ -120,8 +120,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.6.9 h1:y/XJH17oyBd6hxgQtKnIdLXu7TsOHxO5i1JeVfVmjXw= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.4.2 h1:cWe2XPUBFLep2j9kJV4Epg3bctGhMvrrl/sWi9Wszfg= -github.com/sagernet/sing-dns v0.4.2/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= +github.com/sagernet/sing-dns v0.4.3 h1:R6X9oWYbdZ0Mm+8PkdqrBkqx3JwiAbnETUZGOpEdY4E= +github.com/sagernet/sing-dns v0.4.3/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 h1:iwpCX6H3nZEOGUGwx0q5azcgYOA9f6v9YssihXoRKHk= From 900888731c0b3a8181106647ce993042c901db9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 13 May 2025 18:05:31 +0800 Subject: [PATCH 0308/1272] Fix DNS reject response --- route/route_dns.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/route/route_dns.go b/route/route_dns.go index 62eff1c263..0e22cdfa89 100644 --- a/route/route_dns.go +++ b/route/route_dns.go @@ -161,7 +161,14 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er case *R.RuleActionReject: switch action.Method { case C.RuleActionRejectMethodDefault: - return dns.FixedResponse(message.Id, message.Question[0], nil, 0), nil + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Rcode: mDNS.RcodeRefused, + Response: true, + }, + Question: []mDNS.Question{message.Question[0]}, + }, nil case C.RuleActionRejectMethodDrop: return nil, tun.ErrDrop } From c5ecca3938d7e5dc6bd201433bcbd1e350b57701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 18 May 2025 16:47:03 +0800 Subject: [PATCH 0309/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 9dd336679d..cec05bf693 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 9dd336679d14a1d16c6d72b1a67c4abc4ccae4c0 +Subproject commit cec05bf6935eca219a722883212ae8880d2e863e diff --git a/docs/changelog.md b/docs/changelog.md index 4fe24664a9..e1aa794204 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,13 @@ icon: material/alert-decagram --- +### 1.11.11 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + ### 1.11.10 * Undeprecate the `block` outbound **1** From 81d32181ce407027d844841c7c2d962ea868cb28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 20 May 2025 18:17:20 +0800 Subject: [PATCH 0310/1272] Fix update route address set --- protocol/tun/inbound.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 70f78c0abe..ff02655316 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -214,7 +214,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if !loaded { return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet) } - ruleSet.IncRef() inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet) } for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet { @@ -222,7 +221,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if !loaded { return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet) } - ruleSet.IncRef() inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet) } if options.AutoRedirect { @@ -312,7 +310,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error { if len(ipSets) == 0 { t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name()) } - routeRuleSet.DecRef() + routeRuleSet.IncRef() t.routeAddressSet = append(t.routeAddressSet, ipSets...) if t.autoRedirect != nil { t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) @@ -324,7 +322,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error { if len(ipSets) == 0 { t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name()) } - routeExcludeRuleSet.DecRef() + routeExcludeRuleSet.IncRef() t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...) if t.autoRedirect != nil { t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) From 41226a60755c944486941538d2c36cab9b78bed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 23 May 2025 10:56:35 +0800 Subject: [PATCH 0311/1272] Fix interface finder --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 3ace9231a6..628bfba0e1 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.9 + github.com/sagernet/sing v0.6.10 github.com/sagernet/sing-dns v0.4.3 github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 diff --git a/go.sum b/go.sum index 8e23f50afb..7d4c0ccf1f 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,9 @@ github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+ 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.6.9 h1:y/XJH17oyBd6hxgQtKnIdLXu7TsOHxO5i1JeVfVmjXw= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4= +github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.3 h1:R6X9oWYbdZ0Mm+8PkdqrBkqx3JwiAbnETUZGOpEdY4E= github.com/sagernet/sing-dns v0.4.3/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= From 995267a04286ed32fe18aa6fe6b150749d1a76dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 24 May 2025 07:41:51 +0800 Subject: [PATCH 0312/1272] Remove wrong ALPNs in DOH/DOH3 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 628bfba0e1..53932f00d7 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.10 - github.com/sagernet/sing-dns v0.4.3 + github.com/sagernet/sing-dns v0.4.5 github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 github.com/sagernet/sing-shadowsocks v0.2.7 diff --git a/go.sum b/go.sum index 7d4c0ccf1f..a681c992bd 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4= github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.4.3 h1:R6X9oWYbdZ0Mm+8PkdqrBkqx3JwiAbnETUZGOpEdY4E= -github.com/sagernet/sing-dns v0.4.3/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= +github.com/sagernet/sing-dns v0.4.5 h1:D9REN14qx2FTrZRBrtFLL99f2CuFzQ9S7mIf8uV5hZI= +github.com/sagernet/sing-dns v0.4.5/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 h1:iwpCX6H3nZEOGUGwx0q5azcgYOA9f6v9YssihXoRKHk= From bc5eb1e1a58ce4870e0ee4d583669625dc3d95aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 24 May 2025 08:14:43 +0800 Subject: [PATCH 0313/1272] Fix RoutePacketConnectionEx --- route/route.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/route/route.go b/route/route.go index f3e7a98304..f354095dea 100644 --- a/route/route.go +++ b/route/route.go @@ -176,8 +176,6 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, } else { r.logger.ErrorContext(ctx, err) } - } else if onClose != nil { - onClose(nil) } } From 26bd698462482511d5342a5eeb88986123b56126 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sat, 24 May 2025 23:54:15 +0300 Subject: [PATCH 0314/1272] Integrate AmneziaWG --- go.mod | 7 ++++++- go.sum | 12 ++++++++++-- option/wireguard.go | 26 +++++++++++++++++++------ protocol/wireguard/endpoint.go | 15 ++++++++++++++ protocol/wireguard/outbound.go | 15 ++++++++++++++ transport/wireguard/endpoint.go | 12 ++++++++++++ transport/wireguard/endpoint_options.go | 13 +++++++++++++ 7 files changed, 91 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 3ace9231a6..722b27b564 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/sagernet/sing-box -go 1.20 +go 1.23.6 + +toolchain go1.24.3 require ( github.com/caddyserver/certmagic v0.20.0 @@ -88,6 +90,7 @@ require ( 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.5 // indirect + github.com/tevino/abool/v2 v2.1.0 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect @@ -102,3 +105,5 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect ) + +replace github.com/sagernet/wireguard-go => github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec diff --git a/go.sum b/go.sum index 8e23f50afb..346e9a2057 100644 --- a/go.sum +++ b/go.sum @@ -18,11 +18,14 @@ 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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec h1:jXekDkSozctYj5JQlV1mCsIW+qHpIkgSFhOIpgRSB1I= +github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec/go.mod h1:akc2Wh+rX9bFFNnHJGsQ8VIV3eJI1LXJYgx2Y+8lcW8= 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/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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 v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -34,6 +37,7 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm 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/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.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -61,6 +65,7 @@ github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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= @@ -85,6 +90,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= @@ -140,8 +146,6 @@ github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2Wk github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc= 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.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/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -160,6 +164,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c= +github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= @@ -171,6 +177,7 @@ github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvv 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.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= @@ -201,6 +208,7 @@ 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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= diff --git a/option/wireguard.go b/option/wireguard.go index 43d3139c5e..5d9b1d86e6 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -16,6 +16,7 @@ type WireGuardEndpointOptions struct { Peers []WireGuardPeer `json:"peers,omitempty"` UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` Workers int `json:"workers,omitempty"` + Amnezia *WireGuardAmnezia `json:"amnezia,omitempty"` DialerOptions } @@ -38,12 +39,13 @@ type LegacyWireGuardOutboundOptions struct { 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"` + 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"` + Amnezia *WireGuardAmnezia `json:"amnezia,omitempty"` } type LegacyWireGuardPeer struct { @@ -53,3 +55,15 @@ type LegacyWireGuardPeer struct { AllowedIPs badoption.Listable[netip.Prefix] `json:"allowed_ips,omitempty"` Reserved []uint8 `json:"reserved,omitempty"` } + +type WireGuardAmnezia struct { + JC int `json:"jc,omitempty"` + JMin int `json:"jmin,omitempty"` + JMax int `json:"jmax,omitempty"` + S1 int `json:"s1,omitempty"` + S2 int `json:"s2,omitempty"` + H1 uint32 `json:"h1,omitempty"` + H2 uint32 `json:"h2,omitempty"` + H3 uint32 `json:"h3,omitempty"` + H4 uint32 `json:"h4,omitempty"` +} diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 2f6825fd5c..0383066319 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -56,6 +56,20 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL } else { udpTimeout = C.UDPTimeout } + var amnezia *wireguard.AmneziaOptions + if options.Amnezia != nil { + amnezia = &wireguard.AmneziaOptions{ + JC: options.Amnezia.JC, + JMin: options.Amnezia.JMin, + JMax: options.Amnezia.JMax, + S1: options.Amnezia.S1, + S2: options.Amnezia.S2, + H1: options.Amnezia.H1, + H2: options.Amnezia.H2, + H3: options.Amnezia.H3, + H4: options.Amnezia.H4, + } + } wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ Context: ctx, Logger: logger, @@ -91,6 +105,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL } }), Workers: options.Workers, + Amnezia: amnezia, }) if err != nil { return nil, err diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 808dade45c..c25b945a19 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -74,6 +74,20 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL Reserved: options.Reserved, }} } + var amnezia *wireguard.AmneziaOptions + if options.Amnezia != nil { + amnezia = &wireguard.AmneziaOptions{ + JC: options.Amnezia.JC, + JMin: options.Amnezia.JMin, + JMax: options.Amnezia.JMax, + S1: options.Amnezia.S1, + S2: options.Amnezia.S2, + H1: options.Amnezia.H1, + H2: options.Amnezia.H2, + H3: options.Amnezia.H3, + H4: options.Amnezia.H4, + } + } wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ Context: ctx, Logger: logger, @@ -97,6 +111,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL }, Peers: peers, Workers: options.Workers, + Amnezia: amnezia, }) if err != nil { return nil, err diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 3801640fe9..903132e769 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -8,6 +8,7 @@ import ( "net" "net/netip" "os" + "strconv" "strings" "github.com/sagernet/sing/common" @@ -179,6 +180,17 @@ func (e *Endpoint) Start(resolve bool) error { wgDevice := device.NewDevice(e.options.Context, e.tunDevice, bind, logger, e.options.Workers) e.tunDevice.SetDevice(wgDevice) ipcConf := e.ipcConf + if e.options.Amnezia != nil { + ipcConf += "\njc=" + strconv.Itoa(e.options.Amnezia.JC) + "\n" + ipcConf += "jmin=" + strconv.Itoa(e.options.Amnezia.JMin) + "\n" + ipcConf += "jmax=" + strconv.Itoa(e.options.Amnezia.JMax) + "\n" + ipcConf += "s1=" + strconv.Itoa(e.options.Amnezia.S1) + "\n" + ipcConf += "s2=" + strconv.Itoa(e.options.Amnezia.S2) + "\n" + ipcConf += "h1=" + strconv.FormatUint(uint64(e.options.Amnezia.H1), 10) + "\n" + ipcConf += "h2=" + strconv.FormatUint(uint64(e.options.Amnezia.H2), 10) + "\n" + ipcConf += "h3=" + strconv.FormatUint(uint64(e.options.Amnezia.H3), 10) + "\n" + ipcConf += "h4=" + strconv.FormatUint(uint64(e.options.Amnezia.H4), 10) + } for _, peer := range e.peers { ipcConf += peer.GenerateIpcLines() } diff --git a/transport/wireguard/endpoint_options.go b/transport/wireguard/endpoint_options.go index bb9a46e69f..46640c7baa 100644 --- a/transport/wireguard/endpoint_options.go +++ b/transport/wireguard/endpoint_options.go @@ -27,6 +27,7 @@ type EndpointOptions struct { ResolvePeer func(domain string) (netip.Addr, error) Peers []PeerOptions Workers int + Amnezia *AmneziaOptions } type PeerOptions struct { @@ -37,3 +38,15 @@ type PeerOptions struct { PersistentKeepaliveInterval uint16 Reserved []uint8 } + +type AmneziaOptions struct { + JC int + JMin int + JMax int + S1 int + S2 int + H1 uint32 + H2 uint32 + H3 uint32 + H4 uint32 +} From 50227c0f5fc8682cc27895e9eda7840234e399d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 26 May 2025 17:51:32 +0800 Subject: [PATCH 0315/1272] Fix sniff action --- route/route.go | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/route/route.go b/route/route.go index f354095dea..d0f93e0b44 100644 --- a/route/route.go +++ b/route/route.go @@ -496,7 +496,9 @@ func (r *Router) actionSniff( return } if inputConn != nil { - sniffBuffer := buf.NewPacket() + if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 { + return + } var streamSniffers []sniff.StreamSniffer if len(action.StreamSniffers) > 0 { streamSniffers = action.StreamSniffers @@ -510,6 +512,7 @@ func (r *Router) actionSniff( sniff.RDP, } } + sniffBuffer := buf.NewPacket() err := sniff.PeekStream( ctx, metadata, @@ -541,10 +544,25 @@ func (r *Router) actionSniff( sniffBuffer.Release() } } else if inputPacketConn != nil { - if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) { + if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 { + return + } else if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) { r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError) return } + var packetSniffers []sniff.PacketSniffer + if len(action.PacketSniffers) > 0 { + packetSniffers = action.PacketSniffers + } else { + packetSniffers = []sniff.PacketSniffer{ + sniff.DomainNameQuery, + sniff.QUICClientHello, + sniff.STUNMessage, + sniff.UTP, + sniff.UDPTracker, + sniff.DTLSRecord, + } + } for { var ( sniffBuffer = buf.NewPacket() @@ -584,19 +602,6 @@ func (r *Router) actionSniff( sniff.QUICClientHello, ) } else { - var packetSniffers []sniff.PacketSniffer - if len(action.PacketSniffers) > 0 { - packetSniffers = action.PacketSniffers - } else { - packetSniffers = []sniff.PacketSniffer{ - sniff.DomainNameQuery, - sniff.QUICClientHello, - sniff.STUNMessage, - sniff.UTP, - sniff.UDPTracker, - sniff.DTLSRecord, - } - } err = sniff.PeekPacket( ctx, metadata, sniffBuffer.Bytes(), From ee3a42a67ea5cceec1aca5dc9699e08f664e1746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 26 May 2025 18:09:32 +0800 Subject: [PATCH 0316/1272] Fix none method read buffer --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 53932f00d7..83eb81b063 100644 --- a/go.mod +++ b/go.mod @@ -30,8 +30,8 @@ require ( github.com/sagernet/sing-dns v0.4.5 github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 - github.com/sagernet/sing-shadowsocks v0.2.7 - github.com/sagernet/sing-shadowsocks2 v0.2.0 + github.com/sagernet/sing-shadowsocks v0.2.8 + github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.0 github.com/sagernet/sing-tun v0.6.5 github.com/sagernet/sing-vmess v0.2.1 diff --git a/go.sum b/go.sum index a681c992bd..cdfbed2a26 100644 --- a/go.sum +++ b/go.sum @@ -127,10 +127,10 @@ github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/ github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 h1:iwpCX6H3nZEOGUGwx0q5azcgYOA9f6v9YssihXoRKHk= github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= -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-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.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= github.com/sagernet/sing-tun v0.6.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffssg= From e7b3a8eebe15ec223ec978bef84e067efecdbc99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 3 May 2025 11:35:53 +0800 Subject: [PATCH 0317/1272] Fix vmess read request --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 83eb81b063..688968c024 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.0 github.com/sagernet/sing-tun v0.6.5 - github.com/sagernet/sing-vmess v0.2.1 + github.com/sagernet/sing-vmess v0.2.3 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.1-beta.7 diff --git a/go.sum b/go.sum index cdfbed2a26..6382e32942 100644 --- a/go.sum +++ b/go.sum @@ -137,6 +137,8 @@ github.com/sagernet/sing-tun v0.6.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffs github.com/sagernet/sing-tun v0.6.5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.1 h1:6izHC2+B68aQCxTagki6eZZc+g5eh4dYwxOV5a2Lhug= github.com/sagernet/sing-vmess v0.2.1/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= +github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44= +github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= 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/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= From 83cf5f5c6abcc43877eb012bdac9ebef2c08f3b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 27 May 2025 14:29:44 +0800 Subject: [PATCH 0318/1272] Fix ws closed error message --- transport/v2raywebsocket/conn.go | 43 ++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/transport/v2raywebsocket/conn.go b/transport/v2raywebsocket/conn.go index f909976457..a796e6126d 100644 --- a/transport/v2raywebsocket/conn.go +++ b/transport/v2raywebsocket/conn.go @@ -3,6 +3,7 @@ package v2raywebsocket import ( "context" "encoding/base64" + "errors" "io" "net" "os" @@ -61,7 +62,7 @@ func (c *WebsocketConn) Close() error { func (c *WebsocketConn) Read(b []byte) (n int, err error) { var header ws.Header for { - n, err = c.reader.Read(b) + n, err = wrapWsError0(c.reader.Read(b)) if n > 0 { err = nil return @@ -95,7 +96,7 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) { } func (c *WebsocketConn) Write(p []byte) (n int, err error) { - err = wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p) + err = wrapWsError(wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p)) if err != nil { return } @@ -146,7 +147,7 @@ func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) { return 0, c.err } } - return c.conn.Read(b) + return wrapWsError0(c.conn.Read(b)) } func (c *EarlyWebsocketConn) writeRequest(content []byte) error { @@ -177,12 +178,12 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error { conn, err = c.dialContext(c.ctx, &c.requestURL, c.headers) } if err != nil { - return err + return wrapWsError(err) } if len(lateData) > 0 { _, err = conn.Write(lateData) if err != nil { - return err + return wrapWsError(err) } } c.conn = conn @@ -191,7 +192,7 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error { func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) { if c.conn != nil { - return c.conn.Write(b) + return wrapWsError0(c.conn.Write(b)) } c.access.Lock() defer c.access.Unlock() @@ -199,9 +200,9 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) { return 0, c.err } if c.conn != nil { - return c.conn.Write(b) + return wrapWsError0(c.conn.Write(b)) } - err = c.writeRequest(b) + err = wrapWsError(c.writeRequest(b)) c.err = err close(c.create) if err != nil { @@ -212,17 +213,17 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) { func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error { if c.conn != nil { - return c.conn.WriteBuffer(buffer) + return wrapWsError(c.conn.WriteBuffer(buffer)) } c.access.Lock() defer c.access.Unlock() if c.conn != nil { - return c.conn.WriteBuffer(buffer) + return wrapWsError(c.conn.WriteBuffer(buffer)) } if c.err != nil { return c.err } - err := c.writeRequest(buffer.Bytes()) + err := wrapWsError(c.writeRequest(buffer.Bytes())) c.err = err close(c.create) return err @@ -272,3 +273,23 @@ func (c *EarlyWebsocketConn) Upstream() any { func (c *EarlyWebsocketConn) LazyHeadroom() bool { return c.conn == nil } + +func wrapWsError(err error) error { + if err == nil { + return nil + } + var closedErr *wsutil.ClosedError + if errors.As(err, &closedErr) { + if closedErr.Code == ws.StatusNormalClosure { + err = io.EOF + } + } + return err +} + +func wrapWsError0[T any](value T, err error) (T, error) { + if err == nil { + return value, nil + } + return common.DefaultValue[T](), wrapWsError(err) +} From 2cfc8092adac3caa4b3a116ae2e63c3a4941bbc6 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Thu, 29 May 2025 22:48:15 +0300 Subject: [PATCH 0319/1272] Fix endpoint manager locks --- adapter/endpoint/manager.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adapter/endpoint/manager.go b/adapter/endpoint/manager.go index 5a633beed3..00f8ea3e13 100644 --- a/adapter/endpoint/manager.go +++ b/adapter/endpoint/manager.go @@ -35,7 +35,6 @@ func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Ma func (m *Manager) Start(stage adapter.StartStage) error { m.access.Lock() - defer m.access.Unlock() if m.started && m.stage >= stage { panic("already started") } @@ -43,9 +42,12 @@ func (m *Manager) Start(stage adapter.StartStage) error { m.stage = stage if stage == adapter.StartStateStart { // started with outbound manager + m.access.Unlock() return nil } - for _, endpoint := range m.endpoints { + endpoints := m.endpoints + m.access.Unlock() + for _, endpoint := range endpoints { err := adapter.LegacyStart(endpoint, stage) if err != nil { return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]") From 5c911c97d86d4a75fa67088274735480da7da4b1 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 1 Jun 2025 22:06:34 +0300 Subject: [PATCH 0320/1272] Added WARP endpoint --- adapter/experimental.go | 2 + common/cloudflare/api.go | 58 ++++++++++ common/cloudflare/option.go | 17 +++ common/cloudflare/profile.go | 65 +++++++++++ constant/proxy.go | 3 + experimental/cachefile/cache.go | 33 ++++++ go.mod | 6 + go.sum | 6 + include/wireguard.go | 1 + option/wireguard.go | 16 +++ protocol/wireguard/endpoint_warp.go | 173 ++++++++++++++++++++++++++++ 11 files changed, 380 insertions(+) create mode 100644 common/cloudflare/api.go create mode 100644 common/cloudflare/option.go create mode 100644 common/cloudflare/profile.go create mode 100644 protocol/wireguard/endpoint_warp.go diff --git a/adapter/experimental.go b/adapter/experimental.go index 648eb41880..fc00b78ab9 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -41,6 +41,8 @@ type CacheFile interface { StoreGroupExpand(group string, expand bool) error LoadRuleSet(tag string) *SavedBinary SaveRuleSet(tag string, set *SavedBinary) error + LoadCloudflareProfile(tag string) *SavedBinary + SaveCloudflareProfile(tag string, set *SavedBinary) error } type SavedBinary struct { diff --git a/common/cloudflare/api.go b/common/cloudflare/api.go new file mode 100644 index 0000000000..136ed1921c --- /dev/null +++ b/common/cloudflare/api.go @@ -0,0 +1,58 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/tidwall/gjson" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +type CloudeflareApi struct { + client http.Client +} + +func NewCloudeflareApi(opts ...CloudflareApiOption) *CloudeflareApi { + api := &CloudeflareApi{http.Client{Timeout: 30 * time.Second}} + for _, opt := range opts { + opt(api) + } + return api +} + +func (api *CloudeflareApi) CreateProfile(ctx context.Context) (*CloudflareProfile, error) { + privateKey, err := wgtypes.GeneratePrivateKey() + if err != nil { + return nil, err + } + request, err := http.NewRequest("POST", "https://api.cloudflareclient.com/v0i1909051800/reg", strings.NewReader( + fmt.Sprintf( + "{\"install_id\":\"\",\"tos\":\"%s\",\"key\":\"%s\",\"fcm_token\":\"\",\"type\":\"ios\",\"locale\":\"en_US\"}", + time.Now().Format("2006-01-02T15:04:05.000Z"), + privateKey.PublicKey().String(), + ), + )) + if err != nil { + return nil, err + } + response, err := api.client.Do(request.WithContext(ctx)) + if err != nil { + return nil, err + } + defer response.Body.Close() + if response.StatusCode != 200 { + return nil, fmt.Errorf("status code is not 200") + } + content, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + profile := new(CloudflareProfile) + profile.Config.PrivateKey = privateKey.String() + return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile) +} diff --git a/common/cloudflare/option.go b/common/cloudflare/option.go new file mode 100644 index 0000000000..00c0d6d4d5 --- /dev/null +++ b/common/cloudflare/option.go @@ -0,0 +1,17 @@ +package cloudflare + +import ( + "context" + "net" + "net/http" +) + +type CloudflareApiOption func(api *CloudeflareApi) + +func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) CloudflareApiOption { + return func(api *CloudeflareApi) { + api.client.Transport = &http.Transport{ + DialContext: dialContext, + } + } +} diff --git a/common/cloudflare/profile.go b/common/cloudflare/profile.go new file mode 100644 index 0000000000..f9cf76d277 --- /dev/null +++ b/common/cloudflare/profile.go @@ -0,0 +1,65 @@ +package cloudflare + +import "time" + +type CloudflareProfile struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Key string `json:"key"` + Account struct { + ID string `json:"id"` + AccountType string `json:"account_type"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` + PremiumData int `json:"premium_data"` + Quota int `json:"quota"` + Usage int `json:"usage"` + WarpPlus bool `json:"warp_plus"` + ReferralCount int `json:"referral_count"` + ReferralRenewalCountdown int `json:"referral_renewal_countdown"` + Role string `json:"role"` + License string `json:"license"` + TTL time.Time `json:"ttl"` + } `json:"account"` + Config struct { + ClientID string `json:"client_id"` + PrivateKey string `json:"private_key"` + Peers []struct { + PublicKey string `json:"public_key"` + Endpoint struct { + V4 string `json:"v4"` + V6 string `json:"v6"` + Host string `json:"host"` + Ports []int `json:"ports"` + } `json:"endpoint"` + } `json:"peers"` + Interface struct { + Addresses struct { + V4 string `json:"v4"` + V6 string `json:"v6"` + } `json:"addresses"` + } `json:"interface"` + Services struct { + HTTPProxy string `json:"http_proxy"` + } `json:"services"` + Metrics struct { + Ping int `json:"ping"` + Report int `json:"report"` + } `json:"metrics"` + } `json:"config"` + Token string `json:"token"` + WarpEnabled bool `json:"warp_enabled"` + WaitlistEnabled bool `json:"waitlist_enabled"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` + Tos time.Time `json:"tos"` + Place int `json:"place"` + Locale string `json:"locale"` + Enabled bool `json:"enabled"` + InstallID string `json:"install_id"` + FcmToken string `json:"fcm_token"` + Policy struct { + TunnelProtocol string `json:"tunnel_protocol"` + } `json:"policy"` +} diff --git a/constant/proxy.go b/constant/proxy.go index 3197de6052..753eb0bf0c 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -15,6 +15,7 @@ const ( TypeTrojan = "trojan" TypeNaive = "naive" TypeWireGuard = "wireguard" + TypeWARP = "warp" TypeHysteria = "hysteria" TypeTor = "tor" TypeSSH = "ssh" @@ -60,6 +61,8 @@ func ProxyDisplayName(proxyType string) string { return "Naive" case TypeWireGuard: return "WireGuard" + case TypeWARP: + return "WARP" case TypeHysteria: return "Hysteria" case TypeTor: diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 88cffdbe93..7ac324e205 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -316,3 +316,36 @@ func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error { return bucket.Put([]byte(tag), setBinary) }) } + +func (c *CacheFile) LoadCloudflareProfile(tag string) *adapter.SavedBinary { + var savedProfile adapter.SavedBinary + err := c.DB.View(func(t *bbolt.Tx) error { + bucket := c.bucket(t, bucketRuleSet) + if bucket == nil { + return os.ErrNotExist + } + profileBinary := bucket.Get([]byte(tag)) + if len(profileBinary) == 0 { + return os.ErrInvalid + } + return savedProfile.UnmarshalBinary(profileBinary) + }) + if err != nil { + return nil + } + return &savedProfile +} + +func (c *CacheFile) SaveCloudflareProfile(tag string, set *adapter.SavedBinary) error { + return c.DB.Batch(func(t *bbolt.Tx) error { + bucket, err := c.createBucket(t, bucketRuleSet) + if err != nil { + return err + } + profileBinary, err := set.MarshalBinary() + if err != nil { + return err + } + return bucket.Put([]byte(tag), profileBinary) + }) +} diff --git a/go.mod b/go.mod index 722b27b564..4daa087a97 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,11 @@ require ( howett.net/plist v1.0.1 ) +require ( + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect +) + //replace github.com/sagernet/sing => ../sing require ( @@ -91,6 +96,7 @@ require ( github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tevino/abool/v2 v2.1.0 // indirect + github.com/tidwall/gjson v1.18.0 github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect diff --git a/go.sum b/go.sum index 346e9a2057..09c49c619c 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c= github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY= +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 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= diff --git a/include/wireguard.go b/include/wireguard.go index f2ce9e2341..ead47ced07 100644 --- a/include/wireguard.go +++ b/include/wireguard.go @@ -14,4 +14,5 @@ func registerWireGuardOutbound(registry *outbound.Registry) { func registerWireGuardEndpoint(registry *endpoint.Registry) { wireguard.RegisterEndpoint(registry) + wireguard.RegisterWarpEndpoint(registry) } diff --git a/option/wireguard.go b/option/wireguard.go index 5d9b1d86e6..f819ca8ffa 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -30,6 +30,22 @@ type WireGuardPeer struct { Reserved []uint8 `json:"reserved,omitempty"` } +type WireGuardWarpEndpointOptions struct { + System bool `json:"system,omitempty"` + Name string `json:"name,omitempty"` + ListenPort uint16 `json:"listen_port,omitempty"` + UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` + Workers int `json:"workers,omitempty"` + Amnezia *WireGuardAmnezia `json:"amnezia,omitempty"` + Profile *WireGuardWarpProfile `json:"profile,omitempty"` + DialerOptions +} + +type WireGuardWarpProfile struct { + Detour string `json:"detour,omitempty"` + Recreate bool `json:"recreate,omitempty"` +} + type LegacyWireGuardOutboundOptions struct { DialerOptions SystemInterface bool `json:"system_interface,omitempty"` diff --git a/protocol/wireguard/endpoint_warp.go b/protocol/wireguard/endpoint_warp.go new file mode 100644 index 0000000000..8b4d02812e --- /dev/null +++ b/protocol/wireguard/endpoint_warp.go @@ -0,0 +1,173 @@ +package wireguard + +import ( + "context" + "encoding/json" + "math/rand" + "net" + "net/netip" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/common/cloudflare" + 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/json/badoption" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +func RegisterWarpEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.WireGuardWarpEndpointOptions](registry, C.TypeWARP, NewWarpEndpoint) +} + +type WarpEndpoint struct { + endpoint.Adapter + endpoint adapter.Endpoint + ctx context.Context + router adapter.Router + logger log.ContextLogger + tag string + options option.WireGuardWarpEndpointOptions +} + +func NewWarpEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardWarpEndpointOptions) (adapter.Endpoint, error) { + var dependencies []string + if options.Detour != "" { + dependencies = append(dependencies, options.Detour) + } + if options.Profile != nil { + if options.Profile.Detour != "" { + dependencies = append(dependencies, options.Profile.Detour) + } + } + return &WarpEndpoint{ + Adapter: endpoint.NewAdapter(C.TypeWARP, tag, []string{N.NetworkTCP, N.NetworkUDP}, dependencies), + ctx: ctx, + router: router, + logger: logger, + tag: tag, + options: options, + }, nil +} + +func (w *WarpEndpoint) Start(stage adapter.StartStage) error { + if stage != adapter.StartStatePostStart { + return nil + } + cacheFile := service.FromContext[adapter.CacheFile](w.ctx) + var profile *cloudflare.CloudflareProfile + var err error + if !w.options.Profile.Recreate { + if cacheFile != nil { + savedProfile := cacheFile.LoadCloudflareProfile(w.tag) + if savedProfile != nil { + err := json.Unmarshal(savedProfile.Content, &profile) + if err != nil { + return err + } + } + } + } + if profile == nil { + opts := make([]cloudflare.CloudflareApiOption, 0, 1) + if w.options.Profile != nil { + if w.options.Profile.Detour != "" { + detour, ok := service.FromContext[adapter.OutboundManager](w.ctx).Outbound(w.options.Profile.Detour) + if !ok { + return E.New("outbound detour not found: ", w.options.Profile.Detour) + } + opts = append(opts, cloudflare.WithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) { + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) + })) + } + } + api := cloudflare.NewCloudeflareApi(opts...) + profile, err = api.CreateProfile(w.ctx) + if err != nil { + return err + } + if cacheFile != nil { + content, err := json.Marshal(profile) + if err != nil { + return err + } + cacheFile.SaveCloudflareProfile(w.tag, &adapter.SavedBinary{ + LastUpdated: time.Now(), + Content: content, + LastEtag: "", + }) + } + } + peer := profile.Config.Peers[0] + hostParts := strings.Split(peer.Endpoint.Host, ":") + w.endpoint, err = NewEndpoint( + w.ctx, + w.router, + w.logger, + w.tag, + option.WireGuardEndpointOptions{ + System: w.options.System, + Name: w.options.Name, + ListenPort: w.options.ListenPort, + UDPTimeout: w.options.UDPTimeout, + Workers: w.options.Workers, + Amnezia: w.options.Amnezia, + DialerOptions: w.options.DialerOptions, + + Address: badoption.Listable[netip.Prefix]{ + netip.MustParsePrefix(profile.Config.Interface.Addresses.V4 + "/32"), + netip.MustParsePrefix(profile.Config.Interface.Addresses.V6 + "/128"), + }, + PrivateKey: profile.Config.PrivateKey, + Peers: []option.WireGuardPeer{ + { + Address: hostParts[0], + Port: uint16(peer.Endpoint.Ports[rand.Intn(len(peer.Endpoint.Ports))]), + PublicKey: peer.PublicKey, + AllowedIPs: badoption.Listable[netip.Prefix]{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + }, + }, + MTU: 1280, + }, + ) + if err != nil { + return err + } + if err := w.endpoint.Start(adapter.StartStateStart); err != nil { + return err + } + if err := w.endpoint.Start(adapter.StartStatePostStart); err != nil { + return err + } + return nil +} + +func (w *WarpEndpoint) Close() error { + if w.endpoint == nil { + return E.New("endpoint not initialized") + } + return w.endpoint.Close() +} + +func (w *WarpEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if w.endpoint == nil { + return nil, E.New("endpoint not initialized") + } + return w.endpoint.DialContext(ctx, network, destination) +} + +func (w *WarpEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if w.endpoint == nil { + return nil, E.New("endpoint not initialized") + } + return w.endpoint.ListenPacket(ctx, destination) +} From b4d294c05e22bfcfb9b35a7001a732adf09c29aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 4 Jun 2025 20:03:45 +0800 Subject: [PATCH 0321/1272] Fix TUIC read buffer --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 688968c024..f44affaed5 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sagernet/sing v0.6.10 github.com/sagernet/sing-dns v0.4.5 github.com/sagernet/sing-mux v0.3.2 - github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 + github.com/sagernet/sing-quic v0.4.3 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.0 diff --git a/go.sum b/go.sum index 6382e32942..8d01ac3ba8 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,8 @@ github.com/sagernet/sing-dns v0.4.5 h1:D9REN14qx2FTrZRBrtFLL99f2CuFzQ9S7mIf8uV5h github.com/sagernet/sing-dns v0.4.5/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 h1:iwpCX6H3nZEOGUGwx0q5azcgYOA9f6v9YssihXoRKHk= -github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= +github.com/sagernet/sing-quic v0.4.3 h1:OZ/kGvSzjtYg+t0DY3F606hlT5LeiQQXDxfBopcRryQ= +github.com/sagernet/sing-quic v0.4.3/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= 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= @@ -135,8 +135,6 @@ github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDar github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= github.com/sagernet/sing-tun v0.6.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffssg= github.com/sagernet/sing-tun v0.6.5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= -github.com/sagernet/sing-vmess v0.2.1 h1:6izHC2+B68aQCxTagki6eZZc+g5eh4dYwxOV5a2Lhug= -github.com/sagernet/sing-vmess v0.2.1/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44= github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= From 3ea5f764707897360d41f763bd0347dacf23322b Mon Sep 17 00:00:00 2001 From: Mahdi <80265960+Mahdi-zarei@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:35:30 +0330 Subject: [PATCH 0322/1272] Fix nil logger at v2rayhttp server --- transport/v2rayhttp/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index dd2bc9a2f7..c74e59aa79 100644 --- a/transport/v2rayhttp/server.go +++ b/transport/v2rayhttp/server.go @@ -47,6 +47,7 @@ func NewServer(ctx context.Context, logger logger.ContextLogger, options option. server := &Server{ ctx: ctx, tlsConfig: tlsConfig, + logger: logger, handler: handler, h2Server: &http2.Server{ IdleTimeout: time.Duration(options.IdleTimeout), From 78ae935468bb395777de48642d505a97be62a29e Mon Sep 17 00:00:00 2001 From: Sentsuki <52487960+Sentsuki@users.noreply.github.com> Date: Wed, 4 Jun 2025 20:04:39 +0800 Subject: [PATCH 0323/1272] documentation: Fix typo Signed-off-by: Sentsuki <52487960+Sentsuki@users.noreply.github.com> --- docs/configuration/inbound/tun.zh.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index b3b561f1aa..391097ebbb 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -398,11 +398,11 @@ UDP NAT 过期时间。 TCP/IP 栈。 -| 栈 | 描述 | -|--------|------------------------------------------------------------------| -| system | 基于系统网络栈执行 L3 到 L4 转换 | -| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 | -| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 | +| 栈 | 描述 | +|----------|-------------------------------------------------------------------------------------------------------| +| `system` | 基于系统网络栈执行 L3 到 L4 转换 | +| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 | +| `mixed` | 混合 `system` TCP 栈与 `gvisor` UDP 栈 | 默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。 From dba0b5276bbbf70905bd7db584ea0273c2b25b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 27 May 2025 14:11:37 +0800 Subject: [PATCH 0324/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index cec05bf693..c0885a2dc3 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit cec05bf6935eca219a722883212ae8880d2e863e +Subproject commit c0885a2dc304797336756c8066c77bb4c193b009 diff --git a/docs/changelog.md b/docs/changelog.md index e1aa794204..7586b032db 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,13 @@ icon: material/alert-decagram --- +### 1.11.12 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + ### 1.11.11 * Fixes and improvements From 098a00b0257cbb8721870a6f5d7d1dd990f07a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 4 Jun 2025 23:23:36 +0800 Subject: [PATCH 0325/1272] Fix v2ray websocket transport --- transport/v2raywebsocket/conn.go | 21 +++++++++++---------- transport/v2raywebsocket/deadline.go | 22 ---------------------- transport/v2raywebsocket/writer.go | 2 +- 3 files changed, 12 insertions(+), 33 deletions(-) delete mode 100644 transport/v2raywebsocket/deadline.go diff --git a/transport/v2raywebsocket/conn.go b/transport/v2raywebsocket/conn.go index a796e6126d..7f347dc987 100644 --- a/transport/v2raywebsocket/conn.go +++ b/transport/v2raywebsocket/conn.go @@ -62,15 +62,16 @@ func (c *WebsocketConn) Close() error { func (c *WebsocketConn) Read(b []byte) (n int, err error) { var header ws.Header for { - n, err = wrapWsError0(c.reader.Read(b)) + n, err = c.reader.Read(b) if n > 0 { err = nil return } if !E.IsMulti(err, io.EOF, wsutil.ErrNoFrameAdvance) { + err = wrapWsError(err) return } - header, err = c.reader.NextFrame() + header, err = wrapWsError0(c.reader.NextFrame()) if err != nil { return } @@ -79,14 +80,14 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) { err = wsutil.ErrFrameTooLarge return } - err = c.controlHandler(header, c.reader) + err = wrapWsError(c.controlHandler(header, c.reader)) if err != nil { return } continue } if header.OpCode&ws.OpBinary == 0 { - err = c.reader.Discard() + err = wrapWsError(c.reader.Discard()) if err != nil { return } @@ -178,12 +179,12 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error { conn, err = c.dialContext(c.ctx, &c.requestURL, c.headers) } if err != nil { - return wrapWsError(err) + return err } if len(lateData) > 0 { _, err = conn.Write(lateData) if err != nil { - return wrapWsError(err) + return err } } c.conn = conn @@ -202,7 +203,7 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) { if c.conn != nil { return wrapWsError0(c.conn.Write(b)) } - err = wrapWsError(c.writeRequest(b)) + err = c.writeRequest(b) c.err = err close(c.create) if err != nil { @@ -223,7 +224,7 @@ func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error { if c.err != nil { return c.err } - err := wrapWsError(c.writeRequest(buffer.Bytes())) + err := c.writeRequest(buffer.Bytes()) c.err = err close(c.create) return err @@ -278,7 +279,7 @@ func wrapWsError(err error) error { if err == nil { return nil } - var closedErr *wsutil.ClosedError + var closedErr wsutil.ClosedError if errors.As(err, &closedErr) { if closedErr.Code == ws.StatusNormalClosure { err = io.EOF @@ -291,5 +292,5 @@ func wrapWsError0[T any](value T, err error) (T, error) { if err == nil { return value, nil } - return common.DefaultValue[T](), wrapWsError(err) + return value, wrapWsError(err) } diff --git a/transport/v2raywebsocket/deadline.go b/transport/v2raywebsocket/deadline.go deleted file mode 100644 index 195565aa55..0000000000 --- a/transport/v2raywebsocket/deadline.go +++ /dev/null @@ -1,22 +0,0 @@ -package v2raywebsocket - -import ( - "net" - "time" -) - -type deadConn struct { - net.Conn -} - -func (c *deadConn) SetDeadline(t time.Time) error { - return nil -} - -func (c *deadConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (c *deadConn) SetWriteDeadline(t time.Time) error { - return nil -} diff --git a/transport/v2raywebsocket/writer.go b/transport/v2raywebsocket/writer.go index 5bd0d0a14f..c08a654f00 100644 --- a/transport/v2raywebsocket/writer.go +++ b/transport/v2raywebsocket/writer.go @@ -66,7 +66,7 @@ func (w *Writer) WriteBuffer(buffer *buf.Buffer) error { ws.Cipher(data, *(*[4]byte)(header[1+payloadBitLength:]), 0) } - return w.writer.WriteBuffer(buffer) + return wrapWsError(w.writer.WriteBuffer(buffer)) } func (w *Writer) FrontHeadroom() int { From 255068fd40e92ca857115322b596801173525a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 4 Jun 2025 23:25:57 +0800 Subject: [PATCH 0326/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/android b/clients/android index c0885a2dc3..320170a107 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit c0885a2dc304797336756c8066c77bb4c193b009 +Subproject commit 320170a1077ea5c93872b3e055b96b8836615ef0 diff --git a/docs/changelog.md b/docs/changelog.md index 7586b032db..71f261c9d7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -### 1.11.12 +### 1.11.13 * Fixes and improvements From 43a9016c8304a4a15d2206ac1d9a8293f000fa36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 6 Jun 2025 14:28:09 +0800 Subject: [PATCH 0327/1272] Fix leak in hijack-dns --- route/dns.go | 12 +++++++++++- route/route.go | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/route/dns.go b/route/dns.go index 2c6efefe40..8d57c64675 100644 --- a/route/dns.go +++ b/route/dns.go @@ -31,7 +31,7 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad } } -func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext) { +func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if natConn, isNatConn := conn.(udpnat.Conn); isNatConn { metadata.Destination = M.Socksaddr{} for _, packet := range packetBuffers { @@ -45,10 +45,12 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB conn: conn, ctx: ctx, metadata: metadata, + onClose: onClose, }) return } err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil && !E.IsClosedOrCanceled(err) { r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) } @@ -85,8 +87,16 @@ type dnsHijacker struct { conn N.PacketConn ctx context.Context metadata adapter.InboundContext + onClose N.CloseHandlerFunc } func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) { go ExchangeDNSPacket(h.ctx, h.router, h.conn, buffer, h.metadata, destination) } + +func (h *dnsHijacker) Close() error { + if h.onClose != nil { + h.onClose(nil) + } + return nil +} diff --git a/route/route.go b/route/route.go index d0f93e0b44..d9bf263821 100644 --- a/route/route.go +++ b/route/route.go @@ -120,7 +120,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad for _, buffer := range buffers { conn = bufio.NewCachedConn(conn, buffer) } - r.hijackDNSStream(ctx, conn, metadata) + N.CloseOnHandshakeFailure(conn, onClose, r.hijackDNSStream(ctx, conn, metadata)) return nil } } @@ -233,7 +233,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) return nil case *rule.RuleActionHijackDNS: - r.hijackDNSPacket(ctx, conn, packetBuffers, metadata) + r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose) return nil } } From 0238c54261c578ab50954e858d118b9bd36108c7 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 8 Jun 2025 19:35:59 +0300 Subject: [PATCH 0328/1272] Add xhttp transport --- common/xray/buf/buffer.go | 337 ++++++++++++++++++ common/xray/buf/copy.go | 124 +++++++ common/xray/buf/io.go | 126 +++++++ common/xray/buf/multi_buffer.go | 310 +++++++++++++++++ common/xray/buf/override.go | 38 ++ common/xray/buf/reader.go | 175 ++++++++++ common/xray/buf/writer.go | 270 +++++++++++++++ common/xray/bytespool/pool.go | 72 ++++ common/xray/common.go | 19 + common/xray/crypto/crypto.go | 14 + common/xray/errors/errors.go | 25 ++ common/xray/interfaces.go | 52 +++ common/xray/json/badoption/range.go | 53 +++ common/xray/net/address.go | 181 ++++++++++ common/xray/net/destination.go | 146 ++++++++ common/xray/net/net.go | 13 + common/xray/net/network.go | 33 ++ common/xray/net/port.go | 55 +++ common/xray/net/system.go | 84 +++++ common/xray/pipe/impl.go | 215 ++++++++++++ common/xray/pipe/pipe.go | 53 +++ common/xray/pipe/reader.go | 41 +++ common/xray/pipe/writer.go | 29 ++ common/xray/serial/serial.go | 29 ++ common/xray/serial/string.go | 35 ++ common/xray/signal/done/done.go | 49 +++ common/xray/signal/notifier.go | 26 ++ common/xray/signal/pubsub/pubsub.go | 105 ++++++ common/xray/signal/semaphore/semaphore.go | 27 ++ common/xray/signal/timer.go | 82 +++++ common/xray/stat/connection.go | 34 ++ common/xray/stats/stats.go | 13 + common/xray/task/common.go | 10 + common/xray/task/periodic.go | 85 +++++ common/xray/task/task.go | 64 ++++ common/xray/uuid/uuid.go | 101 ++++++ constant/v2ray.go | 1 + go.mod | 8 +- go.sum | 8 +- option/v2ray_transport.go | 152 ++++++++ route/conn.go | 5 +- transport/v2ray/transport.go | 5 + transport/v2rayxhttp/client.go | 400 ++++++++++++++++++++++ transport/v2rayxhttp/conn.go | 108 ++++++ transport/v2rayxhttp/dialer.go | 192 +++++++++++ transport/v2rayxhttp/http.go | 41 +++ transport/v2rayxhttp/mux.go | 104 ++++++ transport/v2rayxhttp/server.go | 354 +++++++++++++++++++ transport/v2rayxhttp/upload_queue.go | 157 +++++++++ transport/v2rayxhttp/writer.go | 38 ++ 50 files changed, 4691 insertions(+), 7 deletions(-) create mode 100644 common/xray/buf/buffer.go create mode 100644 common/xray/buf/copy.go create mode 100644 common/xray/buf/io.go create mode 100644 common/xray/buf/multi_buffer.go create mode 100644 common/xray/buf/override.go create mode 100644 common/xray/buf/reader.go create mode 100644 common/xray/buf/writer.go create mode 100644 common/xray/bytespool/pool.go create mode 100644 common/xray/common.go create mode 100644 common/xray/crypto/crypto.go create mode 100644 common/xray/errors/errors.go create mode 100644 common/xray/interfaces.go create mode 100644 common/xray/json/badoption/range.go create mode 100644 common/xray/net/address.go create mode 100644 common/xray/net/destination.go create mode 100644 common/xray/net/net.go create mode 100644 common/xray/net/network.go create mode 100644 common/xray/net/port.go create mode 100644 common/xray/net/system.go create mode 100644 common/xray/pipe/impl.go create mode 100644 common/xray/pipe/pipe.go create mode 100644 common/xray/pipe/reader.go create mode 100644 common/xray/pipe/writer.go create mode 100644 common/xray/serial/serial.go create mode 100644 common/xray/serial/string.go create mode 100644 common/xray/signal/done/done.go create mode 100644 common/xray/signal/notifier.go create mode 100644 common/xray/signal/pubsub/pubsub.go create mode 100644 common/xray/signal/semaphore/semaphore.go create mode 100644 common/xray/signal/timer.go create mode 100644 common/xray/stat/connection.go create mode 100644 common/xray/stats/stats.go create mode 100644 common/xray/task/common.go create mode 100644 common/xray/task/periodic.go create mode 100644 common/xray/task/task.go create mode 100644 common/xray/uuid/uuid.go create mode 100644 transport/v2rayxhttp/client.go create mode 100644 transport/v2rayxhttp/conn.go create mode 100644 transport/v2rayxhttp/dialer.go create mode 100644 transport/v2rayxhttp/http.go create mode 100644 transport/v2rayxhttp/mux.go create mode 100644 transport/v2rayxhttp/server.go create mode 100644 transport/v2rayxhttp/upload_queue.go create mode 100644 transport/v2rayxhttp/writer.go diff --git a/common/xray/buf/buffer.go b/common/xray/buf/buffer.go new file mode 100644 index 0000000000..1981fa5c47 --- /dev/null +++ b/common/xray/buf/buffer.go @@ -0,0 +1,337 @@ +package buf + +import ( + "io" + + "github.com/sagernet/sing-box/common/xray/bytespool" + "github.com/sagernet/sing-box/common/xray/net" + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + // Size of a regular buffer. + Size = 8192 +) + +var zero = [Size * 10]byte{0} + +var pool = bytespool.GetPool(Size) + +// ownership represents the data owner of the buffer. +type ownership uint8 + +const ( + managed ownership = iota + unmanaged + bytespools +) + +// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles +// the buffer into an internal buffer pool, in order to recreate a buffer more +// quickly. +type Buffer struct { + v []byte + start int32 + end int32 + ownership ownership + UDP *net.Destination +} + +// New creates a Buffer with 0 length and 8K capacity, managed. +func New() *Buffer { + buf := pool.Get().([]byte) + if cap(buf) >= Size { + buf = buf[:Size] + } else { + buf = make([]byte, Size) + } + + return &Buffer{ + v: buf, + } +} + +// NewExisted creates a standard size Buffer with an existed bytearray, managed. +func NewExisted(b []byte) *Buffer { + if cap(b) < Size { + panic("Invalid buffer") + } + + oLen := len(b) + if oLen < Size { + b = b[:Size] + } + + return &Buffer{ + v: b, + end: int32(oLen), + } +} + +// FromBytes creates a Buffer with an existed bytearray, unmanaged. +func FromBytes(b []byte) *Buffer { + return &Buffer{ + v: b, + end: int32(len(b)), + ownership: unmanaged, + } +} + +// StackNew creates a new Buffer object on stack, managed. +// This method is for buffers that is released in the same function. +func StackNew() Buffer { + buf := pool.Get().([]byte) + if cap(buf) >= Size { + buf = buf[:Size] + } else { + buf = make([]byte, Size) + } + + return Buffer{ + v: buf, + } +} + +// NewWithSize creates a Buffer with 0 length and capacity with at least the given size, bytespool's. +func NewWithSize(size int32) *Buffer { + return &Buffer{ + v: bytespool.Alloc(size), + ownership: bytespools, + } +} + +// Release recycles the buffer into an internal buffer pool. +func (b *Buffer) Release() { + if b == nil || b.v == nil || b.ownership == unmanaged { + return + } + + p := b.v + b.v = nil + b.Clear() + + switch b.ownership { + case managed: + if cap(p) == Size { + pool.Put(p) + } + case bytespools: + bytespool.Free(p) + } + b.UDP = nil +} + +// Clear clears the content of the buffer, results an empty buffer with +// Len() = 0. +func (b *Buffer) Clear() { + b.start = 0 + b.end = 0 +} + +// Byte returns the bytes at index. +func (b *Buffer) Byte(index int32) byte { + return b.v[b.start+index] +} + +// SetByte sets the byte value at index. +func (b *Buffer) SetByte(index int32, value byte) { + b.v[b.start+index] = value +} + +// Bytes returns the content bytes of this Buffer. +func (b *Buffer) Bytes() []byte { + return b.v[b.start:b.end] +} + +// Extend increases the buffer size by n bytes, and returns the extended part. +// It panics if result size is larger than buf.Size. +func (b *Buffer) Extend(n int32) []byte { + end := b.end + n + if end > int32(len(b.v)) { + panic("extending out of bound") + } + ext := b.v[b.end:end] + b.end = end + copy(ext, zero[:]) + return ext +} + +// BytesRange returns a slice of this buffer with given from and to boundary. +func (b *Buffer) BytesRange(from, to int32) []byte { + if from < 0 { + from += b.Len() + } + if to < 0 { + to += b.Len() + } + return b.v[b.start+from : b.start+to] +} + +// BytesFrom returns a slice of this Buffer starting from the given position. +func (b *Buffer) BytesFrom(from int32) []byte { + if from < 0 { + from += b.Len() + } + return b.v[b.start+from : b.end] +} + +// BytesTo returns a slice of this Buffer from start to the given position. +func (b *Buffer) BytesTo(to int32) []byte { + if to < 0 { + to += b.Len() + } + if to < 0 { + to = 0 + } + return b.v[b.start : b.start+to] +} + +// Check makes sure that 0 <= b.start <= b.end. +func (b *Buffer) Check() { + if b.start < 0 { + b.start = 0 + } + if b.end < 0 { + b.end = 0 + } + if b.start > b.end { + b.start = b.end + } +} + +// Resize cuts the buffer at the given position. +func (b *Buffer) Resize(from, to int32) { + oldEnd := b.end + if from < 0 { + from += b.Len() + } + if to < 0 { + to += b.Len() + } + if to < from { + panic("Invalid slice") + } + b.end = b.start + to + b.start += from + b.Check() + if b.end > oldEnd { + copy(b.v[oldEnd:b.end], zero[:]) + } +} + +// Advance cuts the buffer at the given position. +func (b *Buffer) Advance(from int32) { + if from < 0 { + from += b.Len() + } + b.start += from + b.Check() +} + +// Len returns the length of the buffer content. +func (b *Buffer) Len() int32 { + if b == nil { + return 0 + } + return b.end - b.start +} + +// Cap returns the capacity of the buffer content. +func (b *Buffer) Cap() int32 { + if b == nil { + return 0 + } + return int32(len(b.v)) +} + +// IsEmpty returns true if the buffer is empty. +func (b *Buffer) IsEmpty() bool { + return b.Len() == 0 +} + +// IsFull returns true if the buffer has no more room to grow. +func (b *Buffer) IsFull() bool { + return b != nil && b.end == int32(len(b.v)) +} + +// Write implements Write method in io.Writer. +func (b *Buffer) Write(data []byte) (int, error) { + nBytes := copy(b.v[b.end:], data) + b.end += int32(nBytes) + return nBytes, nil +} + +// WriteByte writes a single byte into the buffer. +func (b *Buffer) WriteByte(v byte) error { + if b.IsFull() { + return E.New("buffer full") + } + b.v[b.end] = v + b.end++ + return nil +} + +// WriteString implements io.StringWriter. +func (b *Buffer) WriteString(s string) (int, error) { + return b.Write([]byte(s)) +} + +// ReadByte implements io.ByteReader +func (b *Buffer) ReadByte() (byte, error) { + if b.start == b.end { + return 0, io.EOF + } + + nb := b.v[b.start] + b.start++ + return nb, nil +} + +// ReadBytes implements bufio.Reader.ReadBytes +func (b *Buffer) ReadBytes(length int32) ([]byte, error) { + if b.end-b.start < length { + return nil, io.EOF + } + + nb := b.v[b.start : b.start+length] + b.start += length + return nb, nil +} + +// Read implements io.Reader.Read(). +func (b *Buffer) Read(data []byte) (int, error) { + if b.Len() == 0 { + return 0, io.EOF + } + nBytes := copy(data, b.v[b.start:b.end]) + if int32(nBytes) == b.Len() { + b.Clear() + } else { + b.start += int32(nBytes) + } + return nBytes, nil +} + +// ReadFrom implements io.ReaderFrom. +func (b *Buffer) ReadFrom(reader io.Reader) (int64, error) { + n, err := reader.Read(b.v[b.end:]) + b.end += int32(n) + return int64(n), err +} + +// ReadFullFrom reads exact size of bytes from given reader, or until error occurs. +func (b *Buffer) ReadFullFrom(reader io.Reader, size int32) (int64, error) { + end := b.end + size + if end > int32(len(b.v)) { + v := end + return 0, E.New("out of bound: ", v) + } + n, err := io.ReadFull(reader, b.v[b.end:end]) + b.end += int32(n) + return int64(n), err +} + +// String returns the string form of this Buffer. +func (b *Buffer) String() string { + return string(b.Bytes()) +} diff --git a/common/xray/buf/copy.go b/common/xray/buf/copy.go new file mode 100644 index 0000000000..efab0e175a --- /dev/null +++ b/common/xray/buf/copy.go @@ -0,0 +1,124 @@ +package buf + +import ( + "io" + "time" + + "github.com/sagernet/sing-box/common/xray/errors" + "github.com/sagernet/sing-box/common/xray/signal" + E "github.com/sagernet/sing/common/exceptions" +) + +type dataHandler func(MultiBuffer) + +type copyHandler struct { + onData []dataHandler +} + +// SizeCounter is for counting bytes copied by Copy(). +type SizeCounter struct { + Size int64 +} + +// CopyOption is an option for copying data. +type CopyOption func(*copyHandler) + +// UpdateActivity is a CopyOption to update activity on each data copy operation. +func UpdateActivity(timer signal.ActivityUpdater) CopyOption { + return func(handler *copyHandler) { + handler.onData = append(handler.onData, func(MultiBuffer) { + timer.Update() + }) + } +} + +// CountSize is a CopyOption that sums the total size of data copied into the given SizeCounter. +func CountSize(sc *SizeCounter) CopyOption { + return func(handler *copyHandler) { + handler.onData = append(handler.onData, func(b MultiBuffer) { + sc.Size += int64(b.Len()) + }) + } +} + +type readError struct { + error +} + +func (e readError) Error() string { + return e.error.Error() +} + +func (e readError) Unwrap() error { + return e.error +} + +// IsReadError returns true if the error in Copy() comes from reading. +func IsReadError(err error) bool { + _, ok := err.(readError) + return ok +} + +type writeError struct { + error +} + +func (e writeError) Error() string { + return e.error.Error() +} + +func (e writeError) Unwrap() error { + return e.error +} + +// IsWriteError returns true if the error in Copy() comes from writing. +func IsWriteError(err error) bool { + _, ok := err.(writeError) + return ok +} + +func copyInternal(reader Reader, writer Writer, handler *copyHandler) error { + for { + buffer, err := reader.ReadMultiBuffer() + if !buffer.IsEmpty() { + for _, handler := range handler.onData { + handler(buffer) + } + + if werr := writer.WriteMultiBuffer(buffer); werr != nil { + return writeError{werr} + } + } + + if err != nil { + return readError{err} + } + } +} + +// Copy dumps all payload from reader to writer or stops when an error occurs. It returns nil when EOF. +func Copy(reader Reader, writer Writer, options ...CopyOption) error { + var handler copyHandler + for _, option := range options { + option(&handler) + } + err := copyInternal(reader, writer, &handler) + if err != nil && errors.Cause(err) != io.EOF { + return err + } + return nil +} + +var ErrNotTimeoutReader = E.New("not a TimeoutReader") + +func CopyOnceTimeout(reader Reader, writer Writer, timeout time.Duration) error { + timeoutReader, ok := reader.(TimeoutReader) + if !ok { + return ErrNotTimeoutReader + } + mb, err := timeoutReader.ReadMultiBufferTimeout(timeout) + if err != nil { + return err + } + return writer.WriteMultiBuffer(mb) +} diff --git a/common/xray/buf/io.go b/common/xray/buf/io.go new file mode 100644 index 0000000000..a745c1b543 --- /dev/null +++ b/common/xray/buf/io.go @@ -0,0 +1,126 @@ +package buf + +import ( + "io" + "net" + "syscall" + "time" + + "github.com/sagernet/sing-box/common/xray/stat" + "github.com/sagernet/sing-box/common/xray/stats" + E "github.com/sagernet/sing/common/exceptions" +) + +// Reader extends io.Reader with MultiBuffer. +type Reader interface { + // ReadMultiBuffer reads content from underlying reader, and put it into a MultiBuffer. + ReadMultiBuffer() (MultiBuffer, error) +} + +// ErrReadTimeout is an error that happens with IO timeout. +var ErrReadTimeout = E.New("IO timeout") + +// TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout. +type TimeoutReader interface { + ReadMultiBufferTimeout(time.Duration) (MultiBuffer, error) +} + +// Writer extends io.Writer with MultiBuffer. +type Writer interface { + // WriteMultiBuffer writes a MultiBuffer into underlying writer. + WriteMultiBuffer(MultiBuffer) error +} + +// WriteAllBytes ensures all bytes are written into the given writer. +func WriteAllBytes(writer io.Writer, payload []byte, c stats.Counter) error { + wc := 0 + defer func() { + if c != nil { + c.Add(int64(wc)) + } + }() + + for len(payload) > 0 { + n, err := writer.Write(payload) + wc += n + if err != nil { + return err + } + payload = payload[n:] + } + return nil +} + +func isPacketReader(reader io.Reader) bool { + _, ok := reader.(net.PacketConn) + return ok +} + +// NewReader creates a new Reader. +// The Reader instance doesn't take the ownership of reader. +func NewReader(reader io.Reader) Reader { + if mr, ok := reader.(Reader); ok { + return mr + } + + if isPacketReader(reader) { + return &PacketReader{ + Reader: reader, + } + } + + return &SingleReader{ + Reader: reader, + } +} + +// NewPacketReader creates a new PacketReader based on the given reader. +func NewPacketReader(reader io.Reader) Reader { + if mr, ok := reader.(Reader); ok { + return mr + } + + return &PacketReader{ + Reader: reader, + } +} + +func isPacketWriter(writer io.Writer) bool { + if _, ok := writer.(net.PacketConn); ok { + return true + } + + // If the writer doesn't implement syscall.Conn, it is probably not a TCP connection. + if _, ok := writer.(syscall.Conn); !ok { + return true + } + return false +} + +// NewWriter creates a new Writer. +func NewWriter(writer io.Writer) Writer { + if mw, ok := writer.(Writer); ok { + return mw + } + + iConn := writer + if statConn, ok := writer.(*stat.CounterConnection); ok { + iConn = statConn.Connection + } + + if isPacketWriter(iConn) { + return &SequentialWriter{ + Writer: writer, + } + } + + var counter stats.Counter + + if statConn, ok := writer.(*stat.CounterConnection); ok { + counter = statConn.WriteCounter + } + return &BufferToBytesWriter{ + Writer: iConn, + counter: counter, + } +} diff --git a/common/xray/buf/multi_buffer.go b/common/xray/buf/multi_buffer.go new file mode 100644 index 0000000000..4d00843a6f --- /dev/null +++ b/common/xray/buf/multi_buffer.go @@ -0,0 +1,310 @@ +package buf + +import ( + "io" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/errors" + "github.com/sagernet/sing-box/common/xray/serial" +) + +// ReadAllToBytes reads all content from the reader into a byte array, until EOF. +func ReadAllToBytes(reader io.Reader) ([]byte, error) { + mb, err := ReadFrom(reader) + if err != nil { + return nil, err + } + if mb.Len() == 0 { + return nil, nil + } + b := make([]byte, mb.Len()) + mb, _ = SplitBytes(mb, b) + ReleaseMulti(mb) + return b, nil +} + +// MultiBuffer is a list of Buffers. The order of Buffer matters. +type MultiBuffer []*Buffer + +// MergeMulti merges content from src to dest, and returns the new address of dest and src +func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) { + dest = append(dest, src...) + for idx := range src { + src[idx] = nil + } + return dest, src[:0] +} + +// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer. +func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer { + n := len(dest) + if n > 0 && !(dest)[n-1].IsFull() { + nBytes, _ := (dest)[n-1].Write(src) + src = src[nBytes:] + } + + for len(src) > 0 { + b := New() + nBytes, _ := b.Write(src) + src = src[nBytes:] + dest = append(dest, b) + } + + return dest +} + +// ReleaseMulti releases all content of the MultiBuffer, and returns an empty MultiBuffer. +func ReleaseMulti(mb MultiBuffer) MultiBuffer { + for i := range mb { + mb[i].Release() + mb[i] = nil + } + return mb[:0] +} + +// Copy copied the beginning part of the MultiBuffer into the given byte array. +func (mb MultiBuffer) Copy(b []byte) int { + total := 0 + for _, bb := range mb { + nBytes := copy(b[total:], bb.Bytes()) + total += nBytes + if int32(nBytes) < bb.Len() { + break + } + } + return total +} + +// ReadFrom reads all content from reader until EOF. +func ReadFrom(reader io.Reader) (MultiBuffer, error) { + mb := make(MultiBuffer, 0, 16) + for { + b := New() + _, err := b.ReadFullFrom(reader, Size) + if b.IsEmpty() { + b.Release() + } else { + mb = append(mb, b) + } + if err != nil { + if errors.Cause(err) == io.EOF || errors.Cause(err) == io.ErrUnexpectedEOF { + return mb, nil + } + return mb, err + } + } +} + +// SplitBytes splits the given amount of bytes from the beginning of the MultiBuffer. +// It returns the new address of MultiBuffer leftover, and number of bytes written into the input byte slice. +func SplitBytes(mb MultiBuffer, b []byte) (MultiBuffer, int) { + totalBytes := 0 + endIndex := -1 + for i := range mb { + pBuffer := mb[i] + nBytes, _ := pBuffer.Read(b) + totalBytes += nBytes + b = b[nBytes:] + if !pBuffer.IsEmpty() { + endIndex = i + break + } + pBuffer.Release() + mb[i] = nil + } + + if endIndex == -1 { + mb = mb[:0] + } else { + mb = mb[endIndex:] + } + + return mb, totalBytes +} + +// SplitFirstBytes splits the first buffer from MultiBuffer, and then copy its content into the given slice. +func SplitFirstBytes(mb MultiBuffer, p []byte) (MultiBuffer, int) { + mb, b := SplitFirst(mb) + if b == nil { + return mb, 0 + } + n := copy(p, b.Bytes()) + b.Release() + return mb, n +} + +// Compact returns another MultiBuffer by merging all content of the given one together. +func Compact(mb MultiBuffer) MultiBuffer { + if len(mb) == 0 { + return mb + } + + mb2 := make(MultiBuffer, 0, len(mb)) + last := mb[0] + + for i := 1; i < len(mb); i++ { + curr := mb[i] + if last.Len()+curr.Len() > Size { + mb2 = append(mb2, last) + last = curr + } else { + common.Must2(last.ReadFrom(curr)) + curr.Release() + } + } + + mb2 = append(mb2, last) + return mb2 +} + +// SplitFirst splits the first Buffer from the beginning of the MultiBuffer. +func SplitFirst(mb MultiBuffer) (MultiBuffer, *Buffer) { + if len(mb) == 0 { + return mb, nil + } + + b := mb[0] + mb[0] = nil + mb = mb[1:] + return mb, b +} + +// SplitSize splits the beginning of the MultiBuffer into another one, for at most size bytes. +func SplitSize(mb MultiBuffer, size int32) (MultiBuffer, MultiBuffer) { + if len(mb) == 0 { + return mb, nil + } + + if mb[0].Len() > size { + b := New() + copy(b.Extend(size), mb[0].BytesTo(size)) + mb[0].Advance(size) + return mb, MultiBuffer{b} + } + + totalBytes := int32(0) + var r MultiBuffer + endIndex := -1 + for i := range mb { + if totalBytes+mb[i].Len() > size { + endIndex = i + break + } + totalBytes += mb[i].Len() + r = append(r, mb[i]) + mb[i] = nil + } + if endIndex == -1 { + // To reuse mb array + mb = mb[:0] + } else { + mb = mb[endIndex:] + } + return mb, r +} + +// SplitMulti splits the beginning of the MultiBuffer into first one, the index i and after into second one +func SplitMulti(mb MultiBuffer, i int) (MultiBuffer, MultiBuffer) { + mb2 := make(MultiBuffer, 0, len(mb)) + if i < len(mb) && i >= 0 { + mb2 = append(mb2, mb[i:]...) + for j := i; j < len(mb); j++ { + mb[j] = nil + } + mb = mb[:i] + } + return mb, mb2 +} + +// WriteMultiBuffer writes all buffers from the MultiBuffer to the Writer one by one, and return error if any, with leftover MultiBuffer. +func WriteMultiBuffer(writer io.Writer, mb MultiBuffer) (MultiBuffer, error) { + for { + mb2, b := SplitFirst(mb) + mb = mb2 + if b == nil { + break + } + + _, err := writer.Write(b.Bytes()) + b.Release() + if err != nil { + return mb, err + } + } + + return nil, nil +} + +// Len returns the total number of bytes in the MultiBuffer. +func (mb MultiBuffer) Len() int32 { + if mb == nil { + return 0 + } + + size := int32(0) + for _, b := range mb { + size += b.Len() + } + return size +} + +// IsEmpty returns true if the MultiBuffer has no content. +func (mb MultiBuffer) IsEmpty() bool { + for _, b := range mb { + if !b.IsEmpty() { + return false + } + } + return true +} + +// String returns the content of the MultiBuffer in string. +func (mb MultiBuffer) String() string { + v := make([]interface{}, len(mb)) + for i, b := range mb { + v[i] = b + } + return serial.Concat(v...) +} + +// MultiBufferContainer is a ReadWriteCloser wrapper over MultiBuffer. +type MultiBufferContainer struct { + MultiBuffer +} + +// Read implements io.Reader. +func (c *MultiBufferContainer) Read(b []byte) (int, error) { + if c.MultiBuffer.IsEmpty() { + return 0, io.EOF + } + + mb, nBytes := SplitBytes(c.MultiBuffer, b) + c.MultiBuffer = mb + return nBytes, nil +} + +// ReadMultiBuffer implements Reader. +func (c *MultiBufferContainer) ReadMultiBuffer() (MultiBuffer, error) { + mb := c.MultiBuffer + c.MultiBuffer = nil + return mb, nil +} + +// Write implements io.Writer. +func (c *MultiBufferContainer) Write(b []byte) (int, error) { + c.MultiBuffer = MergeBytes(c.MultiBuffer, b) + return len(b), nil +} + +// WriteMultiBuffer implements Writer. +func (c *MultiBufferContainer) WriteMultiBuffer(b MultiBuffer) error { + mb, _ := MergeMulti(c.MultiBuffer, b) + c.MultiBuffer = mb + return nil +} + +// Close implements io.Closer. +func (c *MultiBufferContainer) Close() error { + c.MultiBuffer = ReleaseMulti(c.MultiBuffer) + return nil +} diff --git a/common/xray/buf/override.go b/common/xray/buf/override.go new file mode 100644 index 0000000000..2032854f6f --- /dev/null +++ b/common/xray/buf/override.go @@ -0,0 +1,38 @@ +package buf + +import ( + "github.com/sagernet/sing-box/common/xray/net" +) + +type EndpointOverrideReader struct { + Reader + Dest net.Address + OriginalDest net.Address +} + +func (r *EndpointOverrideReader) ReadMultiBuffer() (MultiBuffer, error) { + mb, err := r.Reader.ReadMultiBuffer() + if err == nil { + for _, b := range mb { + if b.UDP != nil && b.UDP.Address == r.OriginalDest { + b.UDP.Address = r.Dest + } + } + } + return mb, err +} + +type EndpointOverrideWriter struct { + Writer + Dest net.Address + OriginalDest net.Address +} + +func (w *EndpointOverrideWriter) WriteMultiBuffer(mb MultiBuffer) error { + for _, b := range mb { + if b.UDP != nil && b.UDP.Address == w.Dest { + b.UDP.Address = w.OriginalDest + } + } + return w.Writer.WriteMultiBuffer(mb) +} diff --git a/common/xray/buf/reader.go b/common/xray/buf/reader.go new file mode 100644 index 0000000000..17b2e49191 --- /dev/null +++ b/common/xray/buf/reader.go @@ -0,0 +1,175 @@ +package buf + +import ( + "io" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/errors" + E "github.com/sagernet/sing/common/exceptions" +) + +func readOneUDP(r io.Reader) (*Buffer, error) { + b := New() + for i := 0; i < 64; i++ { + _, err := b.ReadFrom(r) + if !b.IsEmpty() { + return b, nil + } + if err != nil { + b.Release() + return nil, err + } + } + + b.Release() + return nil, E.New("Reader returns too many empty payloads.") +} + +// ReadBuffer reads a Buffer from the given reader. +func ReadBuffer(r io.Reader) (*Buffer, error) { + b := New() + n, err := b.ReadFrom(r) + if n > 0 { + return b, err + } + b.Release() + return nil, err +} + +// BufferedReader is a Reader that keeps its internal buffer. +type BufferedReader struct { + // Reader is the underlying reader to be read from + Reader Reader + // Buffer is the internal buffer to be read from first + Buffer MultiBuffer + // Splitter is a function to read bytes from MultiBuffer + Splitter func(MultiBuffer, []byte) (MultiBuffer, int) +} + +// BufferedBytes returns the number of bytes that is cached in this reader. +func (r *BufferedReader) BufferedBytes() int32 { + return r.Buffer.Len() +} + +// ReadByte implements io.ByteReader. +func (r *BufferedReader) ReadByte() (byte, error) { + var b [1]byte + _, err := r.Read(b[:]) + return b[0], err +} + +// Read implements io.Reader. It reads from internal buffer first (if available) and then reads from the underlying reader. +func (r *BufferedReader) Read(b []byte) (int, error) { + spliter := r.Splitter + if spliter == nil { + spliter = SplitBytes + } + + if !r.Buffer.IsEmpty() { + buffer, nBytes := spliter(r.Buffer, b) + r.Buffer = buffer + if r.Buffer.IsEmpty() { + r.Buffer = nil + } + return nBytes, nil + } + + mb, err := r.Reader.ReadMultiBuffer() + if err != nil { + return 0, err + } + + mb, nBytes := spliter(mb, b) + if !mb.IsEmpty() { + r.Buffer = mb + } + return nBytes, nil +} + +// ReadMultiBuffer implements Reader. +func (r *BufferedReader) ReadMultiBuffer() (MultiBuffer, error) { + if !r.Buffer.IsEmpty() { + mb := r.Buffer + r.Buffer = nil + return mb, nil + } + + return r.Reader.ReadMultiBuffer() +} + +// ReadAtMost returns a MultiBuffer with at most size. +func (r *BufferedReader) ReadAtMost(size int32) (MultiBuffer, error) { + if r.Buffer.IsEmpty() { + mb, err := r.Reader.ReadMultiBuffer() + if mb.IsEmpty() && err != nil { + return nil, err + } + r.Buffer = mb + } + + rb, mb := SplitSize(r.Buffer, size) + r.Buffer = rb + if r.Buffer.IsEmpty() { + r.Buffer = nil + } + return mb, nil +} + +func (r *BufferedReader) writeToInternal(writer io.Writer) (int64, error) { + mbWriter := NewWriter(writer) + var sc SizeCounter + if r.Buffer != nil { + sc.Size = int64(r.Buffer.Len()) + if err := mbWriter.WriteMultiBuffer(r.Buffer); err != nil { + return 0, err + } + r.Buffer = nil + } + + err := Copy(r.Reader, mbWriter, CountSize(&sc)) + return sc.Size, err +} + +// WriteTo implements io.WriterTo. +func (r *BufferedReader) WriteTo(writer io.Writer) (int64, error) { + nBytes, err := r.writeToInternal(writer) + if errors.Cause(err) == io.EOF { + return nBytes, nil + } + return nBytes, err +} + +// Interrupt implements common.Interruptible. +func (r *BufferedReader) Interrupt() { + common.Interrupt(r.Reader) +} + +// Close implements io.Closer. +func (r *BufferedReader) Close() error { + return common.Close(r.Reader) +} + +// SingleReader is a Reader that read one Buffer every time. +type SingleReader struct { + io.Reader +} + +// ReadMultiBuffer implements Reader. +func (r *SingleReader) ReadMultiBuffer() (MultiBuffer, error) { + b, err := ReadBuffer(r.Reader) + return MultiBuffer{b}, err +} + +// PacketReader is a Reader that read one Buffer every time. +type PacketReader struct { + io.Reader +} + +// ReadMultiBuffer implements Reader. +func (r *PacketReader) ReadMultiBuffer() (MultiBuffer, error) { + b, err := readOneUDP(r.Reader) + if err != nil { + return nil, err + } + return MultiBuffer{b}, nil +} diff --git a/common/xray/buf/writer.go b/common/xray/buf/writer.go new file mode 100644 index 0000000000..96aea75f9f --- /dev/null +++ b/common/xray/buf/writer.go @@ -0,0 +1,270 @@ +package buf + +import ( + "io" + "net" + "sync" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/errors" + "github.com/sagernet/sing-box/common/xray/stats" +) + +// BufferToBytesWriter is a Writer that writes alloc.Buffer into underlying writer. +type BufferToBytesWriter struct { + io.Writer + + counter stats.Counter + cache [][]byte +} + +// WriteMultiBuffer implements Writer. This method takes ownership of the given buffer. +func (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error { + defer ReleaseMulti(mb) + + size := mb.Len() + if size == 0 { + return nil + } + + if len(mb) == 1 { + return WriteAllBytes(w.Writer, mb[0].Bytes(), w.counter) + } + + if cap(w.cache) < len(mb) { + w.cache = make([][]byte, 0, len(mb)) + } + + bs := w.cache + for _, b := range mb { + bs = append(bs, b.Bytes()) + } + + defer func() { + for idx := range bs { + bs[idx] = nil + } + }() + + nb := net.Buffers(bs) + wc := int64(0) + defer func() { + if w.counter != nil { + w.counter.Add(wc) + } + }() + for size > 0 { + n, err := nb.WriteTo(w.Writer) + wc += n + if err != nil { + return err + } + size -= int32(n) + } + + return nil +} + +// ReadFrom implements io.ReaderFrom. +func (w *BufferToBytesWriter) ReadFrom(reader io.Reader) (int64, error) { + var sc SizeCounter + err := Copy(NewReader(reader), w, CountSize(&sc)) + return sc.Size, err +} + +// BufferedWriter is a Writer with internal buffer. +type BufferedWriter struct { + sync.Mutex + writer Writer + buffer *Buffer + buffered bool +} + +// NewBufferedWriter creates a new BufferedWriter. +func NewBufferedWriter(writer Writer) *BufferedWriter { + return &BufferedWriter{ + writer: writer, + buffer: New(), + buffered: true, + } +} + +// WriteByte implements io.ByteWriter. +func (w *BufferedWriter) WriteByte(c byte) error { + return common.Error2(w.Write([]byte{c})) +} + +// Write implements io.Writer. +func (w *BufferedWriter) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + + w.Lock() + defer w.Unlock() + + if !w.buffered { + if writer, ok := w.writer.(io.Writer); ok { + return writer.Write(b) + } + } + + totalBytes := 0 + for len(b) > 0 { + if w.buffer == nil { + w.buffer = New() + } + + nBytes, err := w.buffer.Write(b) + totalBytes += nBytes + if err != nil { + return totalBytes, err + } + if !w.buffered || w.buffer.IsFull() { + if err := w.flushInternal(); err != nil { + return totalBytes, err + } + } + b = b[nBytes:] + } + + return totalBytes, nil +} + +// WriteMultiBuffer implements Writer. It takes ownership of the given MultiBuffer. +func (w *BufferedWriter) WriteMultiBuffer(b MultiBuffer) error { + if b.IsEmpty() { + return nil + } + + w.Lock() + defer w.Unlock() + + if !w.buffered { + return w.writer.WriteMultiBuffer(b) + } + + reader := MultiBufferContainer{ + MultiBuffer: b, + } + defer reader.Close() + + for !reader.MultiBuffer.IsEmpty() { + if w.buffer == nil { + w.buffer = New() + } + common.Must2(w.buffer.ReadFrom(&reader)) + if w.buffer.IsFull() { + if err := w.flushInternal(); err != nil { + return err + } + } + } + + return nil +} + +// Flush flushes buffered content into underlying writer. +func (w *BufferedWriter) Flush() error { + w.Lock() + defer w.Unlock() + + return w.flushInternal() +} + +func (w *BufferedWriter) flushInternal() error { + if w.buffer.IsEmpty() { + return nil + } + + b := w.buffer + w.buffer = nil + + if writer, ok := w.writer.(io.Writer); ok { + err := WriteAllBytes(writer, b.Bytes(), nil) + b.Release() + return err + } + + return w.writer.WriteMultiBuffer(MultiBuffer{b}) +} + +// SetBuffered sets whether the internal buffer is used. If set to false, Flush() will be called to clear the buffer. +func (w *BufferedWriter) SetBuffered(f bool) error { + w.Lock() + defer w.Unlock() + + w.buffered = f + if !f { + return w.flushInternal() + } + return nil +} + +// ReadFrom implements io.ReaderFrom. +func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) { + if err := w.SetBuffered(false); err != nil { + return 0, err + } + + var sc SizeCounter + err := Copy(NewReader(reader), w, CountSize(&sc)) + return sc.Size, err +} + +// Close implements io.Closable. +func (w *BufferedWriter) Close() error { + if err := w.Flush(); err != nil { + return err + } + return common.Close(w.writer) +} + +// SequentialWriter is a Writer that writes MultiBuffer sequentially into the underlying io.Writer. +type SequentialWriter struct { + io.Writer +} + +// WriteMultiBuffer implements Writer. +func (w *SequentialWriter) WriteMultiBuffer(mb MultiBuffer) error { + mb, err := WriteMultiBuffer(w.Writer, mb) + ReleaseMulti(mb) + return err +} + +type noOpWriter byte + +func (noOpWriter) WriteMultiBuffer(b MultiBuffer) error { + ReleaseMulti(b) + return nil +} + +func (noOpWriter) Write(b []byte) (int, error) { + return len(b), nil +} + +func (noOpWriter) ReadFrom(reader io.Reader) (int64, error) { + b := New() + defer b.Release() + + totalBytes := int64(0) + for { + b.Clear() + _, err := b.ReadFrom(reader) + totalBytes += int64(b.Len()) + if err != nil { + if errors.Cause(err) == io.EOF { + return totalBytes, nil + } + return totalBytes, err + } + } +} + +var ( + // Discard is a Writer that swallows all contents written in. + Discard Writer = noOpWriter(0) + + // DiscardBytes is an io.Writer that swallows all contents written in. + DiscardBytes io.Writer = noOpWriter(0) +) diff --git a/common/xray/bytespool/pool.go b/common/xray/bytespool/pool.go new file mode 100644 index 0000000000..6f632d5241 --- /dev/null +++ b/common/xray/bytespool/pool.go @@ -0,0 +1,72 @@ +package bytespool + +import "sync" + +func createAllocFunc(size int32) func() interface{} { + return func() interface{} { + return make([]byte, size) + } +} + +// The following parameters controls the size of buffer pools. +// There are numPools pools. Starting from 2k size, the size of each pool is sizeMulti of the previous one. +// Package buf is guaranteed to not use buffers larger than the largest pool. +// Other packets may use larger buffers. +const ( + numPools = 4 + sizeMulti = 4 +) + +var ( + pool [numPools]sync.Pool + poolSize [numPools]int32 +) + +func init() { + size := int32(2048) + for i := 0; i < numPools; i++ { + pool[i] = sync.Pool{ + New: createAllocFunc(size), + } + poolSize[i] = size + size *= sizeMulti + } +} + +// GetPool returns a sync.Pool that generates bytes array with at least the given size. +// It may return nil if no such pool exists. +// +// xray:api:stable +func GetPool(size int32) *sync.Pool { + for idx, ps := range poolSize { + if size <= ps { + return &pool[idx] + } + } + return nil +} + +// Alloc returns a byte slice with at least the given size. Minimum size of returned slice is 2048. +// +// xray:api:stable +func Alloc(size int32) []byte { + pool := GetPool(size) + if pool != nil { + return pool.Get().([]byte) + } + return make([]byte, size) +} + +// Free puts a byte slice into the internal pool. +// +// xray:api:stable +func Free(b []byte) { + size := int32(cap(b)) + b = b[0:cap(b)] + for i := numPools - 1; i >= 0; i-- { + if size >= poolSize[i] { + pool[i].Put(b) + return + } + } +} diff --git a/common/xray/common.go b/common/xray/common.go new file mode 100644 index 0000000000..8d5c858d44 --- /dev/null +++ b/common/xray/common.go @@ -0,0 +1,19 @@ +package common + +// Must panics if err is not nil. +func Must(err error) { + if err != nil { + panic(err) + } +} + +// Must2 panics if the second parameter is not nil, otherwise returns the first parameter. +func Must2(v interface{}, err error) interface{} { + Must(err) + return v +} + +// Error2 returns the err from the 2nd parameter. +func Error2(v interface{}, err error) error { + return err +} diff --git a/common/xray/crypto/crypto.go b/common/xray/crypto/crypto.go new file mode 100644 index 0000000000..24c5adb815 --- /dev/null +++ b/common/xray/crypto/crypto.go @@ -0,0 +1,14 @@ +package crypto + +import ( + "crypto/rand" + "math/big" +) + +func RandBetween(from int64, to int64) int64 { + if from == to { + return from + } + bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) + return from + bigInt.Int64() +} diff --git a/common/xray/errors/errors.go b/common/xray/errors/errors.go new file mode 100644 index 0000000000..54af7cbe1d --- /dev/null +++ b/common/xray/errors/errors.go @@ -0,0 +1,25 @@ +package errors + +type hasInnerError interface { + // Unwrap returns the underlying error of this one. + Unwrap() error +} + +func Cause(err error) error { + if err == nil { + return nil + } +L: + for { + switch inner := err.(type) { + case hasInnerError: + if inner.Unwrap() == nil { + break L + } + err = inner.Unwrap() + default: + break L + } + } + return err +} diff --git a/common/xray/interfaces.go b/common/xray/interfaces.go new file mode 100644 index 0000000000..428c3388f7 --- /dev/null +++ b/common/xray/interfaces.go @@ -0,0 +1,52 @@ +package common + +// Closable is the interface for objects that can release its resources. +// +// xray:api:beta +type Closable interface { + // Close release all resources used by this object, including goroutines. + Close() error +} + +// Interruptible is an interface for objects that can be stopped before its completion. +// +// xray:api:beta +type Interruptible interface { + Interrupt() +} + +// Close closes the obj if it is a Closable. +// +// xray:api:beta +func Close(obj interface{}) error { + if c, ok := obj.(Closable); ok { + return c.Close() + } + return nil +} + +// Interrupt calls Interrupt() if object implements Interruptible interface, or Close() if the object implements Closable interface. +// +// xray:api:beta +func Interrupt(obj interface{}) error { + if c, ok := obj.(Interruptible); ok { + c.Interrupt() + return nil + } + return Close(obj) +} + +// Runnable is the interface for objects that can start to work and stop on demand. +type Runnable interface { + // Start starts the runnable object. Upon the method returning nil, the object begins to function properly. + Start() error + + Closable +} + +// HasType is the interface for objects that knows its type. +type HasType interface { + // Type returns the type of the object. + // Usually it returns (*Type)(nil) of the object. + Type() interface{} +} diff --git a/common/xray/json/badoption/range.go b/common/xray/json/badoption/range.go new file mode 100644 index 0000000000..6390a158ea --- /dev/null +++ b/common/xray/json/badoption/range.go @@ -0,0 +1,53 @@ +package badoption + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/sagernet/sing-box/common/xray/crypto" + E "github.com/sagernet/sing/common/exceptions" +) + +type Range struct { + From int32 `json:"from"` + To int32 `json:"to"` +} + +func (c *Range) Build() *Range { + return (*Range)(c) +} + +func (c *Range) MarshalJSON() ([]byte, error) { + return json.Marshal(fmt.Sprintf("%d-%d", c.From, c.To)) +} + +func (c *Range) UnmarshalJSON(content []byte) error { + var stringValue string + err := json.Unmarshal(content, &stringValue) + if err != nil { + return err + } + parts := strings.Split(stringValue, "-") + if len(parts) != 2 { + return E.New("invalid length of range parts") + } + from, err := strconv.ParseInt(parts[0], 10, 32) + if err != nil { + return err + } + to, err := strconv.ParseInt(parts[1], 10, 32) + if err != nil { + return err + } + if from > to { + return E.New("invalid range") + } + *c = Range{int32(from), int32(to)} + return nil +} + +func (c Range) Rand() int32 { + return int32(crypto.RandBetween(int64(c.From), int64(c.To))) +} diff --git a/common/xray/net/address.go b/common/xray/net/address.go new file mode 100644 index 0000000000..7a99c8adc9 --- /dev/null +++ b/common/xray/net/address.go @@ -0,0 +1,181 @@ +package net + +import ( + "bytes" + "net" + "strings" +) + +var ( + // LocalHostIP is a constant value for localhost IP in IPv4. + LocalHostIP = IPAddress([]byte{127, 0, 0, 1}) + + // AnyIP is a constant value for any IP in IPv4. + AnyIP = IPAddress([]byte{0, 0, 0, 0}) + + // LocalHostDomain is a constant value for localhost domain. + LocalHostDomain = DomainAddress("localhost") + + // LocalHostIPv6 is a constant value for localhost IP in IPv6. + LocalHostIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) + + // AnyIPv6 is a constant value for any IP in IPv6. + AnyIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) +) + +// AddressFamily is the type of address. +type AddressFamily byte + +const ( + // AddressFamilyIPv4 represents address as IPv4 + AddressFamilyIPv4 = AddressFamily(0) + + // AddressFamilyIPv6 represents address as IPv6 + AddressFamilyIPv6 = AddressFamily(1) + + // AddressFamilyDomain represents address as Domain + AddressFamilyDomain = AddressFamily(2) +) + +// IsIPv4 returns true if current AddressFamily is IPv4. +func (af AddressFamily) IsIPv4() bool { + return af == AddressFamilyIPv4 +} + +// IsIPv6 returns true if current AddressFamily is IPv6. +func (af AddressFamily) IsIPv6() bool { + return af == AddressFamilyIPv6 +} + +// IsIP returns true if current AddressFamily is IPv6 or IPv4. +func (af AddressFamily) IsIP() bool { + return af == AddressFamilyIPv4 || af == AddressFamilyIPv6 +} + +// IsDomain returns true if current AddressFamily is Domain. +func (af AddressFamily) IsDomain() bool { + return af == AddressFamilyDomain +} + +// Address represents a network address to be communicated with. It may be an IP address or domain +// address, not both. This interface doesn't resolve IP address for a given domain. +type Address interface { + IP() net.IP // IP of this Address + Domain() string // Domain of this Address + Family() AddressFamily + + String() string // String representation of this Address +} + +func isAlphaNum(c byte) bool { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +// ParseAddress parses a string into an Address. The return value will be an IPAddress when +// the string is in the form of IPv4 or IPv6 address, or a DomainAddress otherwise. +func ParseAddress(addr string) Address { + // Handle IPv6 address in form as "[2001:4860:0:2001::68]" + lenAddr := len(addr) + if lenAddr > 0 && addr[0] == '[' && addr[lenAddr-1] == ']' { + addr = addr[1 : lenAddr-1] + lenAddr -= 2 + } + + if lenAddr > 0 && (!isAlphaNum(addr[0]) || !isAlphaNum(addr[len(addr)-1])) { + addr = strings.TrimSpace(addr) + } + + ip := net.ParseIP(addr) + if ip != nil { + return IPAddress(ip) + } + return DomainAddress(addr) +} + +var bytes0 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +// IPAddress creates an Address with given IP. +func IPAddress(ip []byte) Address { + switch len(ip) { + case net.IPv4len: + var addr ipv4Address = [4]byte{ip[0], ip[1], ip[2], ip[3]} + return addr + case net.IPv6len: + if bytes.Equal(ip[:10], bytes0) && ip[10] == 0xff && ip[11] == 0xff { + return IPAddress(ip[12:16]) + } + var addr ipv6Address = [16]byte{ + ip[0], ip[1], ip[2], ip[3], + ip[4], ip[5], ip[6], ip[7], + ip[8], ip[9], ip[10], ip[11], + ip[12], ip[13], ip[14], ip[15], + } + return addr + default: + return nil + } +} + +// DomainAddress creates an Address with given domain. +// This is an internal function that forcibly converts a string to domain. +// It's mainly used in test files and mux. +// Unless you have a specific reason, use net.ParseAddress instead, +// as this function does not check whether the input is an IP address. +// Otherwise, you will get strange results like domain: 1.1.1.1 +func DomainAddress(domain string) Address { + return domainAddress(domain) +} + +type ipv4Address [4]byte + +func (a ipv4Address) IP() net.IP { + return net.IP(a[:]) +} + +func (ipv4Address) Domain() string { + panic("Calling Domain() on an IPv4Address.") +} + +func (ipv4Address) Family() AddressFamily { + return AddressFamilyIPv4 +} + +func (a ipv4Address) String() string { + return a.IP().String() +} + +type ipv6Address [16]byte + +func (a ipv6Address) IP() net.IP { + return net.IP(a[:]) +} + +func (ipv6Address) Domain() string { + panic("Calling Domain() on an IPv6Address.") +} + +func (ipv6Address) Family() AddressFamily { + return AddressFamilyIPv6 +} + +func (a ipv6Address) String() string { + return "[" + a.IP().String() + "]" +} + +type domainAddress string + +func (domainAddress) IP() net.IP { + panic("Calling IP() on a DomainAddress.") +} + +func (a domainAddress) Domain() string { + return string(a) +} + +func (domainAddress) Family() AddressFamily { + return AddressFamilyDomain +} + +func (a domainAddress) String() string { + return a.Domain() +} diff --git a/common/xray/net/destination.go b/common/xray/net/destination.go new file mode 100644 index 0000000000..561c7726ba --- /dev/null +++ b/common/xray/net/destination.go @@ -0,0 +1,146 @@ +package net + +import ( + "net" + "strings" +) + +// Destination represents a network destination including address and protocol (tcp / udp). +type Destination struct { + Address Address + Port Port + Network Network +} + +// DestinationFromAddr generates a Destination from a net address. +func DestinationFromAddr(addr net.Addr) Destination { + switch addr := addr.(type) { + case *net.TCPAddr: + return TCPDestination(IPAddress(addr.IP), Port(addr.Port)) + case *net.UDPAddr: + return UDPDestination(IPAddress(addr.IP), Port(addr.Port)) + case *net.UnixAddr: + return UnixDestination(DomainAddress(addr.Name)) + default: + panic("Net: Unknown address type.") + } +} + +// ParseDestination converts a destination from its string presentation. +func ParseDestination(dest string) (Destination, error) { + d := Destination{ + Address: AnyIP, + Port: Port(0), + } + if strings.HasPrefix(dest, "tcp:") { + d.Network = Network_TCP + dest = dest[4:] + } else if strings.HasPrefix(dest, "udp:") { + d.Network = Network_UDP + dest = dest[4:] + } else if strings.HasPrefix(dest, "unix:") { + d = UnixDestination(DomainAddress(dest[5:])) + return d, nil + } + + hstr, pstr, err := SplitHostPort(dest) + if err != nil { + return d, err + } + if len(hstr) > 0 { + d.Address = ParseAddress(hstr) + } + if len(pstr) > 0 { + port, err := PortFromString(pstr) + if err != nil { + return d, err + } + d.Port = port + } + return d, nil +} + +// TCPDestination creates a TCP destination with given address +func TCPDestination(address Address, port Port) Destination { + return Destination{ + Network: Network_TCP, + Address: address, + Port: port, + } +} + +// UDPDestination creates a UDP destination with given address +func UDPDestination(address Address, port Port) Destination { + return Destination{ + Network: Network_UDP, + Address: address, + Port: port, + } +} + +// UnixDestination creates a Unix destination with given address +func UnixDestination(address Address) Destination { + return Destination{ + Network: Network_UNIX, + Address: address, + } +} + +// NetAddr returns the network address in this Destination in string form. +func (d Destination) NetAddr() string { + addr := "" + if d.Network == Network_TCP || d.Network == Network_UDP { + addr = d.Address.String() + ":" + d.Port.String() + } else if d.Network == Network_UNIX { + addr = d.Address.String() + } + return addr +} + +// RawNetAddr converts a net.Addr from its Destination presentation. +func (d Destination) RawNetAddr() net.Addr { + var addr net.Addr + switch d.Network { + case Network_TCP: + if d.Address.Family().IsIP() { + addr = &net.TCPAddr{ + IP: d.Address.IP(), + Port: int(d.Port), + } + } + case Network_UDP: + if d.Address.Family().IsIP() { + addr = &net.UDPAddr{ + IP: d.Address.IP(), + Port: int(d.Port), + } + } + case Network_UNIX: + if d.Address.Family().IsDomain() { + addr = &net.UnixAddr{ + Name: d.Address.String(), + Net: d.Network.SystemString(), + } + } + } + return addr +} + +// String returns the strings form of this Destination. +func (d Destination) String() string { + prefix := "unknown:" + switch d.Network { + case Network_TCP: + prefix = "tcp:" + case Network_UDP: + prefix = "udp:" + case Network_UNIX: + prefix = "unix:" + } + return prefix + d.NetAddr() +} + +// IsValid returns true if this Destination is valid. +func (d Destination) IsValid() bool { + return d.Network != Network_Unknown +} diff --git a/common/xray/net/net.go b/common/xray/net/net.go new file mode 100644 index 0000000000..9bc049d98a --- /dev/null +++ b/common/xray/net/net.go @@ -0,0 +1,13 @@ +package net + +import "time" + +// defines the maximum time an idle TCP session can survive in the tunnel, so +// it should be consistent across HTTP versions and with other transports. +const ConnIdleTimeout = 300 * time.Second + +// consistent with quic-go +const QuicgoH3KeepAlivePeriod = 10 * time.Second + +// consistent with chrome +const ChromeH2KeepAlivePeriod = 45 * time.Second diff --git a/common/xray/net/network.go b/common/xray/net/network.go new file mode 100644 index 0000000000..356d7fdab8 --- /dev/null +++ b/common/xray/net/network.go @@ -0,0 +1,33 @@ +package net + +type Network int32 + +const ( + Network_Unknown Network = 0 + Network_TCP Network = 2 + Network_UDP Network = 3 + Network_UNIX Network = 4 +) + +func (n Network) SystemString() string { + switch n { + case Network_TCP: + return "tcp" + case Network_UDP: + return "udp" + case Network_UNIX: + return "unix" + default: + return "unknown" + } +} + +// HasNetwork returns true if the network list has a certain network. +func HasNetwork(list []Network, network Network) bool { + for _, value := range list { + if value == network { + return true + } + } + return false +} diff --git a/common/xray/net/port.go b/common/xray/net/port.go new file mode 100644 index 0000000000..6d42c40b6f --- /dev/null +++ b/common/xray/net/port.go @@ -0,0 +1,55 @@ +package net + +import ( + "encoding/binary" + "strconv" + + E "github.com/sagernet/sing/common/exceptions" +) + +// Port represents a network port in TCP and UDP protocol. +type Port uint16 + +// PortFromBytes converts a byte array to a Port, assuming bytes are in big endian order. +// @unsafe Caller must ensure that the byte array has at least 2 elements. +func PortFromBytes(port []byte) Port { + return Port(binary.BigEndian.Uint16(port)) +} + +// PortFromInt converts an integer to a Port. +// @error when the integer is not positive or larger then 65535 +func PortFromInt(val uint32) (Port, error) { + if val > 65535 { + return Port(0), E.New("invalid port range: ", val) + } + return Port(val), nil +} + +// PortFromString converts a string to a Port. +// @error when the string is not an integer or the integral value is a not a valid Port. +func PortFromString(s string) (Port, error) { + val, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return Port(0), E.New("invalid port range: ", s) + } + return PortFromInt(uint32(val)) +} + +// Value return the corresponding uint16 value of a Port. +func (p Port) Value() uint16 { + return uint16(p) +} + +// String returns the string presentation of a Port. +func (p Port) String() string { + return strconv.Itoa(int(p)) +} + +type MemoryPortRange struct { + From Port + To Port +} + +func (r MemoryPortRange) Contains(port Port) bool { + return r.From <= port && port <= r.To +} diff --git a/common/xray/net/system.go b/common/xray/net/system.go new file mode 100644 index 0000000000..7e1c4b01a3 --- /dev/null +++ b/common/xray/net/system.go @@ -0,0 +1,84 @@ +package net + +import "net" + +// DialTCP is an alias of net.DialTCP. +var ( + DialTCP = net.DialTCP + DialUDP = net.DialUDP + DialUnix = net.DialUnix + Dial = net.Dial +) + +type ListenConfig = net.ListenConfig + +var ( + Listen = net.Listen + ListenTCP = net.ListenTCP + ListenUDP = net.ListenUDP + ListenUnix = net.ListenUnix +) + +var LookupIP = net.LookupIP + +var FileConn = net.FileConn + +// ParseIP is an alias of net.ParseIP +var ParseIP = net.ParseIP + +var SplitHostPort = net.SplitHostPort + +var CIDRMask = net.CIDRMask + +type ( + Addr = net.Addr + Conn = net.Conn + PacketConn = net.PacketConn +) + +type ( + TCPAddr = net.TCPAddr + TCPConn = net.TCPConn +) + +type ( + UDPAddr = net.UDPAddr + UDPConn = net.UDPConn +) + +type ( + UnixAddr = net.UnixAddr + UnixConn = net.UnixConn +) + +// IP is an alias for net.IP. +type ( + IP = net.IP + IPMask = net.IPMask + IPNet = net.IPNet +) + +const ( + IPv4len = net.IPv4len + IPv6len = net.IPv6len +) + +type ( + Error = net.Error + AddrError = net.AddrError +) + +type ( + Dialer = net.Dialer + Listener = net.Listener + TCPListener = net.TCPListener + UnixListener = net.UnixListener +) + +var ( + ResolveTCPAddr = net.ResolveTCPAddr + ResolveUDPAddr = net.ResolveUDPAddr + ResolveUnixAddr = net.ResolveUnixAddr +) + +type Resolver = net.Resolver diff --git a/common/xray/pipe/impl.go b/common/xray/pipe/impl.go new file mode 100644 index 0000000000..f36d4d3628 --- /dev/null +++ b/common/xray/pipe/impl.go @@ -0,0 +1,215 @@ +package pipe + +import ( + "errors" + "io" + "runtime" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/buf" + "github.com/sagernet/sing-box/common/xray/signal" + "github.com/sagernet/sing-box/common/xray/signal/done" +) + +type state byte + +const ( + open state = iota + closed + errord +) + +type pipeOption struct { + limit int32 // maximum buffer size in bytes + discardOverflow bool +} + +func (o *pipeOption) isFull(curSize int32) bool { + return o.limit >= 0 && curSize > o.limit +} + +type pipe struct { + sync.Mutex + data buf.MultiBuffer + readSignal *signal.Notifier + writeSignal *signal.Notifier + done *done.Instance + errChan chan error + option pipeOption + state state +} + +var ( + errBufferFull = errors.New("buffer full") + errSlowDown = errors.New("slow down") +) + +func (p *pipe) Len() int32 { + data := p.data + if data == nil { + return 0 + } + return data.Len() +} + +func (p *pipe) getState(forRead bool) error { + switch p.state { + case open: + if !forRead && p.option.isFull(p.data.Len()) { + return errBufferFull + } + return nil + case closed: + if !forRead { + return io.ErrClosedPipe + } + if !p.data.IsEmpty() { + return nil + } + return io.EOF + case errord: + return io.ErrClosedPipe + default: + panic("impossible case") + } +} + +func (p *pipe) readMultiBufferInternal() (buf.MultiBuffer, error) { + p.Lock() + defer p.Unlock() + + if err := p.getState(true); err != nil { + return nil, err + } + + data := p.data + p.data = nil + return data, nil +} + +func (p *pipe) ReadMultiBuffer() (buf.MultiBuffer, error) { + for { + data, err := p.readMultiBufferInternal() + if data != nil || err != nil { + p.writeSignal.Signal() + return data, err + } + + select { + case <-p.readSignal.Wait(): + case <-p.done.Wait(): + case err = <-p.errChan: + return nil, err + } + } +} + +func (p *pipe) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) { + timer := time.NewTimer(d) + defer timer.Stop() + + for { + data, err := p.readMultiBufferInternal() + if data != nil || err != nil { + p.writeSignal.Signal() + return data, err + } + + select { + case <-p.readSignal.Wait(): + case <-p.done.Wait(): + case <-timer.C: + return nil, buf.ErrReadTimeout + } + } +} + +func (p *pipe) writeMultiBufferInternal(mb buf.MultiBuffer) error { + p.Lock() + defer p.Unlock() + + if err := p.getState(false); err != nil { + return err + } + + if p.data == nil { + p.data = mb + return nil + } + + p.data, _ = buf.MergeMulti(p.data, mb) + return errSlowDown +} + +func (p *pipe) WriteMultiBuffer(mb buf.MultiBuffer) error { + if mb.IsEmpty() { + return nil + } + + for { + err := p.writeMultiBufferInternal(mb) + if err == nil { + p.readSignal.Signal() + return nil + } + + if err == errSlowDown { + p.readSignal.Signal() + + // Yield current goroutine. Hopefully the reading counterpart can pick up the payload. + runtime.Gosched() + return nil + } + + if err == errBufferFull && p.option.discardOverflow { + buf.ReleaseMulti(mb) + return nil + } + + if err != errBufferFull { + buf.ReleaseMulti(mb) + p.readSignal.Signal() + return err + } + + select { + case <-p.writeSignal.Wait(): + case <-p.done.Wait(): + return io.ErrClosedPipe + } + } +} + +func (p *pipe) Close() error { + p.Lock() + defer p.Unlock() + + if p.state == closed || p.state == errord { + return nil + } + + p.state = closed + common.Must(p.done.Close()) + return nil +} + +// Interrupt implements common.Interruptible. +func (p *pipe) Interrupt() { + p.Lock() + defer p.Unlock() + + if p.state == closed || p.state == errord { + return + } + + p.state = errord + + if !p.data.IsEmpty() { + buf.ReleaseMulti(p.data) + p.data = nil + } + + common.Must(p.done.Close()) +} diff --git a/common/xray/pipe/pipe.go b/common/xray/pipe/pipe.go new file mode 100644 index 0000000000..dad266788c --- /dev/null +++ b/common/xray/pipe/pipe.go @@ -0,0 +1,53 @@ +package pipe + +import ( + "github.com/sagernet/sing-box/common/xray/signal" + "github.com/sagernet/sing-box/common/xray/signal/done" +) + +// Option for creating new Pipes. +type Option func(*pipeOption) + +// WithoutSizeLimit returns an Option for Pipe to have no size limit. +func WithoutSizeLimit() Option { + return func(opt *pipeOption) { + opt.limit = -1 + } +} + +// WithSizeLimit returns an Option for Pipe to have the given size limit. +func WithSizeLimit(limit int32) Option { + return func(opt *pipeOption) { + opt.limit = limit + } +} + +// DiscardOverflow returns an Option for Pipe to discard writes if full. +func DiscardOverflow() Option { + return func(opt *pipeOption) { + opt.discardOverflow = true + } +} + +// New creates a new Reader and Writer that connects to each other. +func New(opts ...Option) (*Reader, *Writer) { + p := &pipe{ + readSignal: signal.NewNotifier(), + writeSignal: signal.NewNotifier(), + done: done.New(), + errChan: make(chan error, 1), + option: pipeOption{ + limit: -1, + }, + } + + for _, opt := range opts { + opt(&(p.option)) + } + + return &Reader{ + pipe: p, + }, &Writer{ + pipe: p, + } +} diff --git a/common/xray/pipe/reader.go b/common/xray/pipe/reader.go new file mode 100644 index 0000000000..cafe444a49 --- /dev/null +++ b/common/xray/pipe/reader.go @@ -0,0 +1,41 @@ +package pipe + +import ( + "time" + + "github.com/sagernet/sing-box/common/xray/buf" +) + +// Reader is a buf.Reader that reads content from a pipe. +type Reader struct { + pipe *pipe +} + +// ReadMultiBuffer implements buf.Reader. +func (r *Reader) ReadMultiBuffer() (buf.MultiBuffer, error) { + return r.pipe.ReadMultiBuffer() +} + +// ReadMultiBufferTimeout reads content from a pipe within the given duration, or returns buf.ErrTimeout otherwise. +func (r *Reader) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) { + return r.pipe.ReadMultiBufferTimeout(d) +} + +// Interrupt implements common.Interruptible. +func (r *Reader) Interrupt() { + r.pipe.Interrupt() +} + +// ReturnAnError makes ReadMultiBuffer return an error, only once. +func (r *Reader) ReturnAnError(err error) { + r.pipe.errChan <- err +} + +// Recover catches an error set by ReturnAnError, if exists. +func (r *Reader) Recover() (err error) { + select { + case err = <-r.pipe.errChan: + default: + } + return +} diff --git a/common/xray/pipe/writer.go b/common/xray/pipe/writer.go new file mode 100644 index 0000000000..153126357c --- /dev/null +++ b/common/xray/pipe/writer.go @@ -0,0 +1,29 @@ +package pipe + +import ( + "github.com/sagernet/sing-box/common/xray/buf" +) + +// Writer is a buf.Writer that writes data into a pipe. +type Writer struct { + pipe *pipe +} + +// WriteMultiBuffer implements buf.Writer. +func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error { + return w.pipe.WriteMultiBuffer(mb) +} + +// Close implements io.Closer. After the pipe is closed, writing to the pipe will return io.ErrClosedPipe, while reading will return io.EOF. +func (w *Writer) Close() error { + return w.pipe.Close() +} + +func (w *Writer) Len() int32 { + return w.pipe.Len() +} + +// Interrupt implements common.Interruptible. +func (w *Writer) Interrupt() { + w.pipe.Interrupt() +} diff --git a/common/xray/serial/serial.go b/common/xray/serial/serial.go new file mode 100644 index 0000000000..9d8cb4caf6 --- /dev/null +++ b/common/xray/serial/serial.go @@ -0,0 +1,29 @@ +package serial + +import ( + "encoding/binary" + "io" +) + +// ReadUint16 reads first two bytes from the reader, and then converts them to an uint16 value. +func ReadUint16(reader io.Reader) (uint16, error) { + var b [2]byte + if _, err := io.ReadFull(reader, b[:]); err != nil { + return 0, err + } + return binary.BigEndian.Uint16(b[:]), nil +} + +// WriteUint16 writes an uint16 value into writer. +func WriteUint16(writer io.Writer, value uint16) (int, error) { + var b [2]byte + binary.BigEndian.PutUint16(b[:], value) + return writer.Write(b[:]) +} + +// WriteUint64 writes an uint64 value into writer. +func WriteUint64(writer io.Writer, value uint64) (int, error) { + var b [8]byte + binary.BigEndian.PutUint64(b[:], value) + return writer.Write(b[:]) +} diff --git a/common/xray/serial/string.go b/common/xray/serial/string.go new file mode 100644 index 0000000000..8fdcd7f61f --- /dev/null +++ b/common/xray/serial/string.go @@ -0,0 +1,35 @@ +package serial + +import ( + "fmt" + "strings" +) + +// ToString serializes an arbitrary value into string. +func ToString(v interface{}) string { + if v == nil { + return "" + } + + switch value := v.(type) { + case string: + return value + case *string: + return *value + case fmt.Stringer: + return value.String() + case error: + return value.Error() + default: + return fmt.Sprintf("%+v", value) + } +} + +// Concat concatenates all input into a single string. +func Concat(v ...interface{}) string { + builder := strings.Builder{} + for _, value := range v { + builder.WriteString(ToString(value)) + } + return builder.String() +} diff --git a/common/xray/signal/done/done.go b/common/xray/signal/done/done.go new file mode 100644 index 0000000000..189a8cf37c --- /dev/null +++ b/common/xray/signal/done/done.go @@ -0,0 +1,49 @@ +package done + +import ( + "sync" +) + +// Instance is a utility for notifications of something being done. +type Instance struct { + access sync.Mutex + c chan struct{} + closed bool +} + +// New returns a new Done. +func New() *Instance { + return &Instance{ + c: make(chan struct{}), + } +} + +// Done returns true if Close() is called. +func (d *Instance) Done() bool { + select { + case <-d.Wait(): + return true + default: + return false + } +} + +// Wait returns a channel for waiting for done. +func (d *Instance) Wait() <-chan struct{} { + return d.c +} + +// Close marks this Done 'done'. This method may be called multiple times. All calls after first call will have no effect on its status. +func (d *Instance) Close() error { + d.access.Lock() + defer d.access.Unlock() + + if d.closed { + return nil + } + + d.closed = true + close(d.c) + + return nil +} diff --git a/common/xray/signal/notifier.go b/common/xray/signal/notifier.go new file mode 100644 index 0000000000..19836e54f9 --- /dev/null +++ b/common/xray/signal/notifier.go @@ -0,0 +1,26 @@ +package signal + +// Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously. +type Notifier struct { + c chan struct{} +} + +// NewNotifier creates a new Notifier. +func NewNotifier() *Notifier { + return &Notifier{ + c: make(chan struct{}, 1), + } +} + +// Signal signals a change, usually by producer. This method never blocks. +func (n *Notifier) Signal() { + select { + case n.c <- struct{}{}: + default: + } +} + +// Wait returns a channel for waiting for changes. The returned channel never gets closed. +func (n *Notifier) Wait() <-chan struct{} { + return n.c +} diff --git a/common/xray/signal/pubsub/pubsub.go b/common/xray/signal/pubsub/pubsub.go new file mode 100644 index 0000000000..6de453e852 --- /dev/null +++ b/common/xray/signal/pubsub/pubsub.go @@ -0,0 +1,105 @@ +package pubsub + +import ( + "errors" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/signal/done" + "github.com/sagernet/sing-box/common/xray/task" +) + +type Subscriber struct { + buffer chan interface{} + done *done.Instance +} + +func (s *Subscriber) push(msg interface{}) { + select { + case s.buffer <- msg: + default: + } +} + +func (s *Subscriber) Wait() <-chan interface{} { + return s.buffer +} + +func (s *Subscriber) Close() error { + return s.done.Close() +} + +func (s *Subscriber) IsClosed() bool { + return s.done.Done() +} + +type Service struct { + sync.RWMutex + subs map[string][]*Subscriber + ctask *task.Periodic +} + +func NewService() *Service { + s := &Service{ + subs: make(map[string][]*Subscriber), + } + s.ctask = &task.Periodic{ + Execute: s.Cleanup, + Interval: time.Second * 30, + } + return s +} + +// Cleanup cleans up internal caches of subscribers. +// Visible for testing only. +func (s *Service) Cleanup() error { + s.Lock() + defer s.Unlock() + + if len(s.subs) == 0 { + return errors.New("nothing to do") + } + + for name, subs := range s.subs { + newSub := make([]*Subscriber, 0, len(s.subs)) + for _, sub := range subs { + if !sub.IsClosed() { + newSub = append(newSub, sub) + } + } + if len(newSub) == 0 { + delete(s.subs, name) + } else { + s.subs[name] = newSub + } + } + + if len(s.subs) == 0 { + s.subs = make(map[string][]*Subscriber) + } + return nil +} + +func (s *Service) Subscribe(name string) *Subscriber { + sub := &Subscriber{ + buffer: make(chan interface{}, 16), + done: done.New(), + } + s.Lock() + s.subs[name] = append(s.subs[name], sub) + s.Unlock() + common.Must(s.ctask.Start()) + return sub +} + +func (s *Service) Publish(name string, message interface{}) { + s.RLock() + defer s.RUnlock() + + for _, sub := range s.subs[name] { + if !sub.IsClosed() { + sub.push(message) + } + } +} diff --git a/common/xray/signal/semaphore/semaphore.go b/common/xray/signal/semaphore/semaphore.go new file mode 100644 index 0000000000..8696b148bd --- /dev/null +++ b/common/xray/signal/semaphore/semaphore.go @@ -0,0 +1,27 @@ +package semaphore + +// Instance is an implementation of semaphore. +type Instance struct { + token chan struct{} +} + +// New create a new Semaphore with n permits. +func New(n int) *Instance { + s := &Instance{ + token: make(chan struct{}, n), + } + for i := 0; i < n; i++ { + s.token <- struct{}{} + } + return s +} + +// Wait returns a channel for acquiring a permit. +func (s *Instance) Wait() <-chan struct{} { + return s.token +} + +// Signal releases a permit into the semaphore. +func (s *Instance) Signal() { + s.token <- struct{}{} +} diff --git a/common/xray/signal/timer.go b/common/xray/signal/timer.go new file mode 100644 index 0000000000..822350a1b0 --- /dev/null +++ b/common/xray/signal/timer.go @@ -0,0 +1,82 @@ +package signal + +import ( + "context" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/task" +) + +type ActivityUpdater interface { + Update() +} + +type ActivityTimer struct { + sync.RWMutex + updated chan struct{} + checkTask *task.Periodic + onTimeout func() +} + +func (t *ActivityTimer) Update() { + select { + case t.updated <- struct{}{}: + default: + } +} + +func (t *ActivityTimer) check() error { + select { + case <-t.updated: + default: + t.finish() + } + return nil +} + +func (t *ActivityTimer) finish() { + t.Lock() + defer t.Unlock() + + if t.onTimeout != nil { + t.onTimeout() + t.onTimeout = nil + } + if t.checkTask != nil { + t.checkTask.Close() + t.checkTask = nil + } +} + +func (t *ActivityTimer) SetTimeout(timeout time.Duration) { + if timeout == 0 { + t.finish() + return + } + + checkTask := &task.Periodic{ + Interval: timeout, + Execute: t.check, + } + + t.Lock() + + if t.checkTask != nil { + t.checkTask.Close() + } + t.checkTask = checkTask + t.Unlock() + t.Update() + common.Must(checkTask.Start()) +} + +func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer { + timer := &ActivityTimer{ + updated: make(chan struct{}, 1), + onTimeout: cancel, + } + timer.SetTimeout(timeout) + return timer +} diff --git a/common/xray/stat/connection.go b/common/xray/stat/connection.go new file mode 100644 index 0000000000..463db62d5c --- /dev/null +++ b/common/xray/stat/connection.go @@ -0,0 +1,34 @@ +package stat + +import ( + "net" + + "github.com/sagernet/sing-box/common/xray/stats" +) + +type Connection interface { + net.Conn +} + +type CounterConnection struct { + Connection + ReadCounter stats.Counter + WriteCounter stats.Counter +} + +func (c *CounterConnection) Read(b []byte) (int, error) { + nBytes, err := c.Connection.Read(b) + if c.ReadCounter != nil { + c.ReadCounter.Add(int64(nBytes)) + } + + return nBytes, err +} + +func (c *CounterConnection) Write(b []byte) (int, error) { + nBytes, err := c.Connection.Write(b) + if c.WriteCounter != nil { + c.WriteCounter.Add(int64(nBytes)) + } + return nBytes, err +} diff --git a/common/xray/stats/stats.go b/common/xray/stats/stats.go new file mode 100644 index 0000000000..70bbd0269d --- /dev/null +++ b/common/xray/stats/stats.go @@ -0,0 +1,13 @@ +package stats + +// Counter is the interface for stats counters. +// +// xray:api:stable +type Counter interface { + // Value is the current value of the counter. + Value() int64 + // Set sets a new value to the counter, and returns the previous one. + Set(int64) int64 + // Add adds a value to the current counter value, and returns the previous value. + Add(int64) int64 +} diff --git a/common/xray/task/common.go b/common/xray/task/common.go new file mode 100644 index 0000000000..11078fb991 --- /dev/null +++ b/common/xray/task/common.go @@ -0,0 +1,10 @@ +package task + +import "github.com/sagernet/sing-box/common/xray" + +// Close returns a func() that closes v. +func Close(v interface{}) func() error { + return func() error { + return common.Close(v) + } +} diff --git a/common/xray/task/periodic.go b/common/xray/task/periodic.go new file mode 100644 index 0000000000..6abe41ae0b --- /dev/null +++ b/common/xray/task/periodic.go @@ -0,0 +1,85 @@ +package task + +import ( + "sync" + "time" +) + +// Periodic is a task that runs periodically. +type Periodic struct { + // Interval of the task being run + Interval time.Duration + // Execute is the task function + Execute func() error + + access sync.Mutex + timer *time.Timer + running bool +} + +func (t *Periodic) hasClosed() bool { + t.access.Lock() + defer t.access.Unlock() + + return !t.running +} + +func (t *Periodic) checkedExecute() error { + if t.hasClosed() { + return nil + } + + if err := t.Execute(); err != nil { + t.access.Lock() + t.running = false + t.access.Unlock() + return err + } + + t.access.Lock() + defer t.access.Unlock() + + if !t.running { + return nil + } + + t.timer = time.AfterFunc(t.Interval, func() { + t.checkedExecute() + }) + + return nil +} + +// Start implements common.Runnable. +func (t *Periodic) Start() error { + t.access.Lock() + if t.running { + t.access.Unlock() + return nil + } + t.running = true + t.access.Unlock() + + if err := t.checkedExecute(); err != nil { + t.access.Lock() + t.running = false + t.access.Unlock() + return err + } + + return nil +} + +// Close implements common.Closable. +func (t *Periodic) Close() error { + t.access.Lock() + defer t.access.Unlock() + + t.running = false + if t.timer != nil { + t.timer.Stop() + t.timer = nil + } + + return nil +} diff --git a/common/xray/task/task.go b/common/xray/task/task.go new file mode 100644 index 0000000000..a153839249 --- /dev/null +++ b/common/xray/task/task.go @@ -0,0 +1,64 @@ +package task + +import ( + "context" + + "github.com/sagernet/sing-box/common/xray/signal/semaphore" +) + +// OnSuccess executes g() after f() returns nil. +func OnSuccess(f func() error, g func() error) func() error { + return func() error { + if err := f(); err != nil { + return err + } + return g() + } +} + +// Run executes a list of tasks in parallel, returns the first error encountered or nil if all tasks pass. +func Run(ctx context.Context, tasks ...func() error) error { + n := len(tasks) + s := semaphore.New(n) + done := make(chan error, 1) + + for _, task := range tasks { + <-s.Wait() + go func(f func() error) { + err := f() + if err == nil { + s.Signal() + return + } + + select { + case done <- err: + default: + } + }(task) + } + + /* + if altctx := ctx.Value("altctx"); altctx != nil { + ctx = altctx.(context.Context) + } + */ + + for i := 0; i < n; i++ { + select { + case err := <-done: + return err + case <-ctx.Done(): + return ctx.Err() + case <-s.Wait(): + } + } + + /* + if cancel := ctx.Value("cancel"); cancel != nil { + cancel.(context.CancelFunc)() + } + */ + + return nil +} diff --git a/common/xray/uuid/uuid.go b/common/xray/uuid/uuid.go new file mode 100644 index 0000000000..a0219036f2 --- /dev/null +++ b/common/xray/uuid/uuid.go @@ -0,0 +1,101 @@ +package uuid + +import ( + "bytes" + "crypto/rand" + "crypto/sha1" + "encoding/hex" + + "github.com/sagernet/sing-box/common/xray" + E "github.com/sagernet/sing/common/exceptions" +) + +var byteGroups = []int{8, 4, 4, 4, 12} + +type UUID [16]byte + +// String returns the string representation of this UUID. +func (u *UUID) String() string { + bytes := u.Bytes() + result := hex.EncodeToString(bytes[0 : byteGroups[0]/2]) + start := byteGroups[0] / 2 + for i := 1; i < len(byteGroups); i++ { + nBytes := byteGroups[i] / 2 + result += "-" + result += hex.EncodeToString(bytes[start : start+nBytes]) + start += nBytes + } + return result +} + +// Bytes returns the bytes representation of this UUID. +func (u *UUID) Bytes() []byte { + return u[:] +} + +// Equals returns true if this UUID equals another UUID by value. +func (u *UUID) Equals(another *UUID) bool { + if u == nil && another == nil { + return true + } + if u == nil || another == nil { + return false + } + return bytes.Equal(u.Bytes(), another.Bytes()) +} + +// New creates a UUID with random value. +func New() UUID { + var uuid UUID + common.Must2(rand.Read(uuid.Bytes())) + uuid[6] = (uuid[6] & 0x0f) | (4 << 4) + uuid[8] = (uuid[8]&(0xff>>2) | (0x02 << 6)) + return uuid +} + +// ParseBytes converts a UUID in byte form to object. +func ParseBytes(b []byte) (UUID, error) { + var uuid UUID + if len(b) != 16 { + return uuid, E.New("invalid UUID: ", b) + } + copy(uuid[:], b) + return uuid, nil +} + +// ParseString converts a UUID in string form to object. +func ParseString(str string) (UUID, error) { + var uuid UUID + + text := []byte(str) + if l := len(text); l < 32 || l > 36 { + if l == 0 || l > 30 { + return uuid, E.New("invalid UUID: ", str) + } + h := sha1.New() + h.Write(uuid[:]) + h.Write(text) + u := h.Sum(nil)[:16] + u[6] = (u[6] & 0x0f) | (5 << 4) + u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) + copy(uuid[:], u) + return uuid, nil + } + + b := uuid.Bytes() + + for _, byteGroup := range byteGroups { + if text[0] == '-' { + text = text[1:] + } + + if _, err := hex.Decode(b[:byteGroup/2], text[:byteGroup]); err != nil { + return uuid, err + } + + text = text[byteGroup:] + b = b[byteGroup/2:] + } + + return uuid, nil +} diff --git a/constant/v2ray.go b/constant/v2ray.go index c3089a6cf6..1811df5f26 100644 --- a/constant/v2ray.go +++ b/constant/v2ray.go @@ -6,4 +6,5 @@ const ( V2RayTransportTypeQUIC = "quic" V2RayTransportTypeGRPC = "grpc" V2RayTransportTypeHTTPUpgrade = "httpupgrade" + V2RayTransportTypeXHTTP = "xhttp" ) diff --git a/go.mod b/go.mod index 4daa087a97..631abc3db5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sagernet/sing-box -go 1.23.6 +go 1.24 toolchain go1.24.3 @@ -19,6 +19,7 @@ require ( github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.63 github.com/oschwald/maxminddb-golang v1.12.0 + github.com/quic-go/quic-go v0.52.0 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 @@ -59,6 +60,7 @@ require ( require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + go.uber.org/mock v0.5.0 // indirect ) //replace github.com/sagernet/sing => ../sing @@ -90,7 +92,7 @@ require ( github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect @@ -103,7 +105,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/time v0.7.0 golang.org/x/tools v0.24.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect diff --git a/go.sum b/go.sum index 09c49c619c..4c8a8db038 100644 --- a/go.sum +++ b/go.sum @@ -97,10 +97,12 @@ github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +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/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/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA= +github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4= github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU= @@ -184,6 +186,8 @@ 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.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 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= diff --git a/option/v2ray_transport.go b/option/v2ray_transport.go index 68c2385811..7b5287b755 100644 --- a/option/v2ray_transport.go +++ b/option/v2ray_transport.go @@ -1,6 +1,11 @@ package option import ( + "net/http" + "net/url" + "strings" + + Xbadoption "github.com/sagernet/sing-box/common/xray/json/badoption" C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -15,6 +20,7 @@ type _V2RayTransportOptions struct { QUICOptions V2RayQUICOptions `json:"-"` GRPCOptions V2RayGRPCOptions `json:"-"` HTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:"-"` + XHTTPOptions V2RayXHTTPOptions `json:"-"` } type V2RayTransportOptions _V2RayTransportOptions @@ -32,6 +38,8 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) { v = o.GRPCOptions case C.V2RayTransportTypeHTTPUpgrade: v = o.HTTPUpgradeOptions + case C.V2RayTransportTypeXHTTP: + v = o.XHTTPOptions case "": return nil, E.New("missing transport type") default: @@ -57,6 +65,8 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { v = &o.GRPCOptions case C.V2RayTransportTypeHTTPUpgrade: v = &o.HTTPUpgradeOptions + case C.V2RayTransportTypeXHTTP: + v = &o.XHTTPOptions default: return E.New("unknown transport type: " + o.Type) } @@ -98,3 +108,145 @@ type V2RayHTTPUpgradeOptions struct { Path string `json:"path,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"` } + +type V2RayXHTTPBaseOptions struct { + Mode string `json:"mode"` + Host string `json:"host,omitempty"` + Path string `json:"path,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + XPaddingBytes Xbadoption.Range `json:"x_padding_bytes"` + NoGRPCHeader bool `json:"no_grpc_header,omitempty"` + NoSSEHeader bool `json:"no_sse_header,omitempty"` + ScMaxEachPostBytes Xbadoption.Range `json:"sc_max_each_post_bytes"` + ScMinPostsIntervalMs Xbadoption.Range `json:"sc_min_posts_interval_ms"` + ScMaxBufferedPosts int64 `json:"sc_max_buffered_posts,omitempty"` + ScStreamUpServerSecs Xbadoption.Range `json:"sc_stream_up_server_secs"` + Xmux *V2RayXHTTPXmuxOptions `json:"xmux"` +} + +type V2RayXHTTPOptions struct { + V2RayXHTTPBaseOptions + Download *V2RayXHTTPDownloadOptions `json:"download"` +} + +type V2RayXHTTPDownloadOptions struct { + V2RayXHTTPBaseOptions + ServerOptions + OutboundTLSOptionsContainer + Detour string `json:"detour,omitempty"` +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedPath() string { + pathAndQuery := strings.SplitN(c.Path, "?", 2) + path := pathAndQuery[0] + if path == "" || path[0] != '/' { + path = "/" + path + } + if path[len(path)-1] != '/' { + path = path + "/" + } + return path +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedQuery() string { + pathAndQuery := strings.SplitN(c.Path, "?", 2) + query := "" + if len(pathAndQuery) > 1 { + query = pathAndQuery[1] + } + return query +} + +func (c *V2RayXHTTPBaseOptions) GetRequestHeader(rawURL string) http.Header { + header := http.Header{} + for k, v := range c.Headers { + header.Add(k, v) + } + u, _ := url.Parse(rawURL) + // https://www.rfc-editor.org/rfc/rfc7541.html#appendix-B + // h2's HPACK Header Compression feature employs a huffman encoding using a static table. + // 'X' is assigned an 8 bit code, so HPACK compression won't change actual padding length on the wire. + // https://www.rfc-editor.org/rfc/rfc9204.html#section-4.1.2-2 + // h3's similar QPACK feature uses the same huffman table. + u.RawQuery = "x_padding=" + strings.Repeat("X", int(c.GetNormalizedXPaddingBytes().Rand())) + header.Set("Referer", u.String()) + return header +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedXPaddingBytes() Xbadoption.Range { + if c.XPaddingBytes.To == 0 { + return Xbadoption.Range{ + From: 100, + To: 1000, + } + } + return c.XPaddingBytes +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedScMaxEachPostBytes() Xbadoption.Range { + if c.ScMaxEachPostBytes.To == 0 { + return Xbadoption.Range{ + From: 1000000, + To: 1000000, + } + } + return c.ScMaxEachPostBytes +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedScMinPostsIntervalMs() Xbadoption.Range { + if c.ScMinPostsIntervalMs.To == 0 { + return Xbadoption.Range{ + From: 30, + To: 30, + } + } + return c.ScMinPostsIntervalMs +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedScMaxBufferedPosts() int { + if c.ScMaxBufferedPosts == 0 { + return 30 + } + + return int(c.ScMaxBufferedPosts) +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedScStreamUpServerSecs() Xbadoption.Range { + if c.ScStreamUpServerSecs.To == 0 { + return Xbadoption.Range{ + From: 20, + To: 80, + } + } + return c.ScStreamUpServerSecs +} + +type V2RayXHTTPXmuxOptions struct { + MaxConcurrency Xbadoption.Range `json:"max_concurrency"` + MaxConnections Xbadoption.Range `json:"max_connections"` + CMaxReuseTimes Xbadoption.Range `json:"c_max_reuse_times"` + HMaxRequestTimes Xbadoption.Range `json:"h_max_request_times"` + HMaxReusableSecs Xbadoption.Range `json:"h_max_reusable_secs"` + HKeepAlivePeriod int64 `json:"h_keep_alive_period"` +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedMaxConcurrency() Xbadoption.Range { + return m.MaxConcurrency +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedMaxConnections() Xbadoption.Range { + return m.MaxConnections +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedCMaxReuseTimes() Xbadoption.Range { + return m.CMaxReuseTimes +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedHMaxRequestTimes() Xbadoption.Range { + return m.HMaxRequestTimes +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedHMaxReusableSecs() Xbadoption.Range { + return m.HMaxReusableSecs +} diff --git a/route/conn.go b/route/conn.go index 319e463c35..ffa5311fe4 100644 --- a/route/conn.go +++ b/route/conn.go @@ -7,6 +7,7 @@ import ( "net" "net/netip" "os" + "strings" "sync" "sync/atomic" "time" @@ -264,7 +265,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, if !direction { if err == nil { m.logger.DebugContext(ctx, "connection upload finished") - } else if !E.IsClosedOrCanceled(err) { + } else if !E.IsClosedOrCanceled(err) && !strings.Contains(err.Error(), "NO_ERROR") { m.logger.ErrorContext(ctx, "connection upload closed: ", err) } else { m.logger.TraceContext(ctx, "connection upload closed") @@ -272,7 +273,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, } else { if err == nil { m.logger.DebugContext(ctx, "connection download finished") - } else if !E.IsClosedOrCanceled(err) { + } else if !E.IsClosedOrCanceled(err) && !strings.Contains(err.Error(), "NO_ERROR") { m.logger.ErrorContext(ctx, "connection download closed: ", err) } else { m.logger.TraceContext(ctx, "connection download closed") diff --git a/transport/v2ray/transport.go b/transport/v2ray/transport.go index ab52f55e8e..e739fe3f49 100644 --- a/transport/v2ray/transport.go +++ b/transport/v2ray/transport.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing-box/transport/v2rayhttpupgrade" "github.com/sagernet/sing-box/transport/v2raywebsocket" + xhttp "github.com/sagernet/sing-box/transport/v2rayxhttp" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" @@ -39,6 +40,8 @@ func NewServerTransport(ctx context.Context, logger logger.ContextLogger, option return NewGRPCServer(ctx, logger, options.GRPCOptions, tlsConfig, handler) case C.V2RayTransportTypeHTTPUpgrade: return v2rayhttpupgrade.NewServer(ctx, logger, options.HTTPUpgradeOptions, tlsConfig, handler) + case C.V2RayTransportTypeXHTTP: + return xhttp.NewServer(ctx, logger, options.XHTTPOptions, tlsConfig, handler) default: return nil, E.New("unknown transport type: " + options.Type) } @@ -62,6 +65,8 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks return NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig) case C.V2RayTransportTypeHTTPUpgrade: return v2rayhttpupgrade.NewClient(ctx, dialer, serverAddr, options.HTTPUpgradeOptions, tlsConfig) + case C.V2RayTransportTypeXHTTP: + return xhttp.NewClient(ctx, dialer, serverAddr, options.XHTTPOptions, tlsConfig) default: return nil, E.New("unknown transport type: " + options.Type) } diff --git a/transport/v2rayxhttp/client.go b/transport/v2rayxhttp/client.go new file mode 100644 index 0000000000..a0582bb511 --- /dev/null +++ b/transport/v2rayxhttp/client.go @@ -0,0 +1,400 @@ +package xhttp + +import ( + "context" + gotls "crypto/tls" + "io" + "net/http" + "net/http/httptrace" + "net/url" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/xray/buf" + "github.com/sagernet/sing-box/common/xray/net" + "github.com/sagernet/sing-box/common/xray/pipe" + "github.com/sagernet/sing-box/common/xray/signal/done" + "github.com/sagernet/sing-box/common/xray/uuid" + "github.com/sagernet/sing-box/option" + dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHTTP "github.com/sagernet/sing/protocol/http" + "github.com/sagernet/sing/service" + "golang.org/x/net/http2" +) + +type Client struct { + ctx context.Context + options *option.V2RayXHTTPOptions + getRequestURL func(sessionId string) url.URL + getRequestURL2 func(sessionId string) url.URL + getHTTPClient func() (DialerClient, *XmuxClient) + getHTTPClient2 func() (DialerClient, *XmuxClient) +} + +func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayXHTTPOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { + if options.Mode == "" { + return nil, E.New("mode is not set") + } + router := service.FromContext[adapter.Router](ctx) + dest := serverAddr + var gotlsConfig *gotls.Config + if tlsConfig != nil { + var err error + gotlsConfig, err = tlsConfig.Config() + if err != nil { + return nil, err + } + } + baseRequestURL, err := getBaseRequestURL( + &options.V2RayXHTTPBaseOptions, dest, tlsConfig, + ) + if err != nil { + return nil, err + } + getRequestURL := func(sessionId string) url.URL { + requestURL := baseRequestURL + requestURL.Path += sessionId + return requestURL + } + if dest.IsFqdn() { + addresses, err := router.Lookup(ctx, dest.AddrString(), dns.DomainStrategy(options.DomainStrategy)) + if err != nil { + return nil, err + } + dest.Addr = addresses[0] + } + var xmuxOptions option.V2RayXHTTPXmuxOptions + if options.Xmux != nil { + xmuxOptions = *options.Xmux + } + xmuxManager := NewXmuxManager(xmuxOptions, func() XmuxConn { + return createHTTPClient(dest, dialer, &options.V2RayXHTTPBaseOptions, tlsConfig, gotlsConfig) + }) + getHTTPClient := func() (DialerClient, *XmuxClient) { + xmuxClient := xmuxManager.GetXmuxClient(ctx) + return xmuxClient.XmuxConn.(DialerClient), xmuxClient + } + getRequestURL2 := getRequestURL + getHTTPClient2 := func() (DialerClient, *XmuxClient) { + return nil, nil + } + if options.Download != nil { + options2 := options.Download + dialer2 := dialer + if options2.Detour != "" { + var ok bool + dialer2, ok = service.FromContext[adapter.OutboundManager](ctx).Outbound(options2.Detour) + if !ok { + return nil, E.New("outbound detour not found: ", options2.Detour) + } + } + dest2 := options2.ServerOptions.Build() + var tlsConfig2 tls.Config + var gotlsConfig2 *gotls.Config + if options2.TLS != nil { + tlsConfig2, err = tls.NewClient(ctx, options2.Server, common.PtrValueOrDefault(options2.TLS)) + if err != nil { + return nil, err + } + gotlsConfig2, err = tlsConfig2.Config() + if err != nil { + return nil, err + } + } + baseRequestURL2, err := getBaseRequestURL(&options2.V2RayXHTTPBaseOptions, dest2, tlsConfig2) + if err != nil { + return nil, err + } + getRequestURL2 = func(sessionId string) url.URL { + requestURL2 := baseRequestURL2 + requestURL2.Path += sessionId + return requestURL2 + } + if dest2.IsFqdn() { + addresses2, err := router.Lookup(ctx, dest2.AddrString(), dns.DomainStrategy(options2.DomainStrategy)) + if err != nil { + return nil, err + } + dest2.Addr = addresses2[0] + } + var xmuxOptions2 option.V2RayXHTTPXmuxOptions + if options2.Xmux != nil { + xmuxOptions2 = *options2.Xmux + } + xmuxManager2 := NewXmuxManager(xmuxOptions2, func() XmuxConn { + return createHTTPClient(dest2, dialer2, &options2.V2RayXHTTPBaseOptions, tlsConfig2, gotlsConfig2) + }) + getHTTPClient2 = func() (DialerClient, *XmuxClient) { + xmuxClient2 := xmuxManager2.GetXmuxClient(ctx) + return xmuxClient2.XmuxConn.(DialerClient), xmuxClient2 + } + } + return &Client{ + ctx: ctx, + options: &options, + getHTTPClient: getHTTPClient, + getHTTPClient2: getHTTPClient2, + getRequestURL: getRequestURL, + getRequestURL2: getRequestURL2, + }, nil +} + +func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { + options := c.options + mode := c.options.Mode + sessionIdUuid := uuid.New() + requestURL := c.getRequestURL(sessionIdUuid.String()) + requestURL2 := c.getRequestURL(sessionIdUuid.String()) + httpClient, xmuxClient := c.getHTTPClient() + httpClient2, xmuxClient2 := c.getHTTPClient2() + if xmuxClient != nil { + xmuxClient.OpenUsage.Add(1) + } + if xmuxClient2 != nil && xmuxClient2 != xmuxClient { + xmuxClient2.OpenUsage.Add(1) + } + var closed atomic.Int32 + reader, writer := io.Pipe() + conn := splitConn{ + writer: writer, + onClose: func() { + if closed.Add(1) > 1 { + return + } + if xmuxClient != nil { + xmuxClient.OpenUsage.Add(-1) + } + if xmuxClient2 != nil && xmuxClient2 != xmuxClient { + xmuxClient2.OpenUsage.Add(-1) + } + }, + } + var err error + if mode == "stream-one" { + requestURL.Path = options.GetNormalizedPath() + if xmuxClient != nil { + xmuxClient.LeftRequests.Add(-1) + } + conn.reader, conn.remoteAddr, conn.localAddr, err = httpClient.OpenStream(ctx, requestURL.String(), reader, false) + if err != nil { // browser dialer only + return nil, err + } + return &conn, nil + } else { // stream-down + if xmuxClient2 != nil { + xmuxClient2.LeftRequests.Add(-1) + } + conn.reader, conn.remoteAddr, conn.localAddr, err = httpClient2.OpenStream(ctx, requestURL2.String(), nil, false) + if err != nil { // browser dialer only + return nil, err + } + } + if mode == "stream-up" { + if xmuxClient != nil { + xmuxClient.LeftRequests.Add(-1) + } + _, _, _, err = httpClient.OpenStream(ctx, requestURL.String(), reader, true) + if err != nil { // browser dialer only + return nil, err + } + return &conn, nil + } + scMaxEachPostBytes := options.GetNormalizedScMaxEachPostBytes() + scMinPostsIntervalMs := options.GetNormalizedScMinPostsIntervalMs() + if scMaxEachPostBytes.From <= buf.Size { + panic("`scMaxEachPostBytes` should be bigger than " + strconv.Itoa(buf.Size)) + } + maxUploadSize := scMaxEachPostBytes.Rand() + // WithSizeLimit(0) will still allow single bytes to pass, and a lot of + // code relies on this behavior. Subtract 1 so that together with + // uploadWriter wrapper, exact size limits can be enforced + // uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize - 1)) + uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize - buf.Size)) + conn.writer = uploadWriter{ + uploadPipeWriter, + maxUploadSize, + } + go func() { + var seq int64 + var lastWrite time.Time + for { + wroteRequest := done.New() + ctx := httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + WroteRequest: func(httptrace.WroteRequestInfo) { + wroteRequest.Close() + }, + }) + // this intentionally makes a shallow-copy of the struct so we + // can reassign Path (potentially concurrently) + url := requestURL + url.Path += "/" + strconv.FormatInt(seq, 10) + seq += 1 + if scMinPostsIntervalMs.From > 0 { + time.Sleep(time.Duration(scMinPostsIntervalMs.Rand())*time.Millisecond - time.Since(lastWrite)) + } + // by offloading the uploads into a buffered pipe, multiple conn.Write + // calls get automatically batched together into larger POST requests. + // without batching, bandwidth is extremely limited. + chunk, err := uploadPipeReader.ReadMultiBuffer() + if err != nil { + break + } + lastWrite = time.Now() + if xmuxClient != nil && (xmuxClient.LeftRequests.Add(-1) <= 0 || + (xmuxClient.UnreusableAt != time.Time{} && lastWrite.After(xmuxClient.UnreusableAt))) { + httpClient, xmuxClient = c.getHTTPClient() + } + go func() { + err := httpClient.PostPacket( + ctx, + url.String(), + &buf.MultiBufferContainer{MultiBuffer: chunk}, + int64(chunk.Len()), + ) + wroteRequest.Close() + if err != nil { + uploadPipeReader.Interrupt() + } + }() + if _, ok := httpClient.(*DefaultDialerClient); ok { + <-wroteRequest.Wait() + } + } + }() + return &conn, nil +} + +func (c *Client) Close() error { + return nil +} + +func decideHTTPVersion(gotlsConfig *gotls.Config) string { + if gotlsConfig == nil || len(gotlsConfig.NextProtos) == 0 || gotlsConfig.NextProtos[0] == "http/1.1" { + return "1.1" + } + if gotlsConfig.NextProtos[0] == "h3" { + return "3" + } + return "2" +} + +func getBaseRequestURL(options *option.V2RayXHTTPBaseOptions, dest M.Socksaddr, tlsConfig tls.Config) (url.URL, error) { + var requestURL url.URL + if tlsConfig == nil { + requestURL.Scheme = "http" + } else { + requestURL.Scheme = "https" + } + requestURL.Host = options.Host + if requestURL.Host == "" && tlsConfig != nil { + requestURL.Host = tlsConfig.ServerName() + } + if requestURL.Host == "" { + requestURL.Host = dest.AddrString() + } + requestURL.Path = options.Path + if err := sHTTP.URLSetPath(&requestURL, options.Path); err != nil { + return requestURL, E.New(err, "parse path") + } + if !strings.HasPrefix(requestURL.Path, "/") { + requestURL.Path = "/" + requestURL.Path + } + requestURL.Path = options.GetNormalizedPath() + requestURL.RawQuery = options.GetNormalizedQuery() + return requestURL, nil +} + +func createHTTPClient(dest M.Socksaddr, dialer N.Dialer, options *option.V2RayXHTTPBaseOptions, tlsConfig tls.Config, gotlsConfig *gotls.Config) DialerClient { + httpVersion := decideHTTPVersion(gotlsConfig) + dialContext := func(ctxInner context.Context) (net.Conn, error) { + conn, err := dialer.DialContext(ctxInner, "tcp", dest) + if err != nil { + return nil, err + } + if httpVersion == "2" { + return tls.ClientHandshake(ctxInner, conn, tlsConfig) + } + return conn, nil + } + var keepAlivePeriod time.Duration + if options.Xmux != nil { + keepAlivePeriod = time.Duration(options.Xmux.HKeepAlivePeriod) * time.Second + } + var transport http.RoundTripper + switch httpVersion { + case "3": + if keepAlivePeriod == 0 { + keepAlivePeriod = net.QuicgoH3KeepAlivePeriod + } + if keepAlivePeriod < 0 { + keepAlivePeriod = 0 + } + quicConfig := &quic.Config{ + MaxIdleTimeout: net.ConnIdleTimeout, + // these two are defaults of quic-go/http3. the default of quic-go (no + // http3) is different, so it is hardcoded here for clarity. + // https://github.com/quic-go/quic-go/blob/b8ea5c798155950fb5bbfdd06cad1939c9355878/http3/client.go#L36-L39 + MaxIncomingStreams: -1, + KeepAlivePeriod: keepAlivePeriod, + } + transport = &http3.Transport{ + QUICConfig: quicConfig, + TLSClientConfig: gotlsConfig.Clone(), + Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, dest) + if dErr != nil { + return nil, dErr + } + return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg) + }, + } + case "2": + if keepAlivePeriod == 0 { + keepAlivePeriod = net.ChromeH2KeepAlivePeriod + } + if keepAlivePeriod < 0 { + keepAlivePeriod = 0 + } + transport = &http2.Transport{ + DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) { + return dialContext(ctxInner) + }, + IdleConnTimeout: net.ConnIdleTimeout, + ReadIdleTimeout: keepAlivePeriod, + } + default: + httpDialContext := func(ctxInner context.Context, network string, addr string) (net.Conn, error) { + return dialContext(ctxInner) + } + transport = &http.Transport{ + DialTLSContext: httpDialContext, + DialContext: httpDialContext, + IdleConnTimeout: net.ConnIdleTimeout, + // chunked transfer download with KeepAlives is buggy with + // http.Client and our custom dial context. + DisableKeepAlives: true, + } + } + client := &DefaultDialerClient{ + options: options, + client: &http.Client{ + Transport: transport, + }, + httpVersion: httpVersion, + uploadRawPool: &sync.Pool{}, + dialUploadConn: dialContext, + } + return client +} diff --git a/transport/v2rayxhttp/conn.go b/transport/v2rayxhttp/conn.go new file mode 100644 index 0000000000..7da8e916c1 --- /dev/null +++ b/transport/v2rayxhttp/conn.go @@ -0,0 +1,108 @@ +package xhttp + +import ( + "bufio" + "io" + "net" + "net/http" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray/signal/done" +) + +type splitConn struct { + writer io.WriteCloser + reader io.ReadCloser + remoteAddr net.Addr + localAddr net.Addr + onClose func() +} + +func (c *splitConn) Write(b []byte) (int, error) { + return c.writer.Write(b) +} + +func (c *splitConn) Read(b []byte) (int, error) { + return c.reader.Read(b) +} + +func (c *splitConn) Close() error { + if c.onClose != nil { + c.onClose() + } + + err := c.writer.Close() + err2 := c.reader.Close() + if err != nil { + return err + } + + if err2 != nil { + return err + } + + return nil +} + +func (c *splitConn) LocalAddr() net.Addr { + return c.localAddr +} + +func (c *splitConn) RemoteAddr() net.Addr { + return c.remoteAddr +} + +func (c *splitConn) SetDeadline(t time.Time) error { + // TODO cannot do anything useful + return nil +} + +func (c *splitConn) SetReadDeadline(t time.Time) error { + // TODO cannot do anything useful + return nil +} + +func (c *splitConn) SetWriteDeadline(t time.Time) error { + // TODO cannot do anything useful + return nil +} + +type H1Conn struct { + UnreadedResponsesCount int + RespBufReader *bufio.Reader + net.Conn +} + +func NewH1Conn(conn net.Conn) *H1Conn { + return &H1Conn{ + RespBufReader: bufio.NewReader(conn), + Conn: conn, + } +} + +type httpServerConn struct { + sync.Mutex + *done.Instance + io.Reader // no need to Close request.Body + http.ResponseWriter +} + +func (c *httpServerConn) Write(b []byte) (int, error) { + c.Lock() + defer c.Unlock() + if c.Done() { + return 0, io.ErrClosedPipe + } + n, err := c.ResponseWriter.Write(b) + if err == nil { + c.ResponseWriter.(http.Flusher).Flush() + } + return n, err +} + +func (c *httpServerConn) Close() error { + c.Lock() + defer c.Unlock() + return c.Instance.Close() +} diff --git a/transport/v2rayxhttp/dialer.go b/transport/v2rayxhttp/dialer.go new file mode 100644 index 0000000000..13dcdd9ad2 --- /dev/null +++ b/transport/v2rayxhttp/dialer.go @@ -0,0 +1,192 @@ +package xhttp + +import ( + "bytes" + "context" + "fmt" + "io" + "net" + "net/http" + "net/http/httptrace" + "sync" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/signal/done" + "github.com/sagernet/sing-box/option" +) + +// interface to abstract between use of browser dialer, vs net/http +type DialerClient interface { + IsClosed() bool + + // ctx, url, body, uploadOnly + OpenStream(context.Context, string, io.Reader, bool) (io.ReadCloser, net.Addr, net.Addr, error) + + // ctx, url, body, contentLength + PostPacket(context.Context, string, io.Reader, int64) error +} + +// implements xhttp.DialerClient in terms of direct network connections +type DefaultDialerClient struct { + options *option.V2RayXHTTPBaseOptions + client *http.Client + closed bool + httpVersion string + // pool of net.Conn, created using dialUploadConn + uploadRawPool *sync.Pool + dialUploadConn func(ctxInner context.Context) (net.Conn, error) +} + +func (c *DefaultDialerClient) IsClosed() bool { + return c.closed +} + +func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, body io.Reader, uploadOnly bool) (wrc io.ReadCloser, remoteAddr, localAddr net.Addr, err error) { + // this is done when the TCP/UDP connection to the server was established, + // and we can unblock the Dial function and print correct net addresses in + // logs + gotConn := done.New() + ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + GotConn: func(connInfo httptrace.GotConnInfo) { + remoteAddr = connInfo.Conn.RemoteAddr() + localAddr = connInfo.Conn.LocalAddr() + gotConn.Close() + }, + }) + method := "GET" // stream-down + if body != nil { + method = "POST" // stream-up/one + } + req, _ := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body) + req.Header = c.options.GetRequestHeader(url) + if method == "POST" && !c.options.NoGRPCHeader { + req.Header.Set("Content-Type", "application/grpc") + } + wrc = &WaitReadCloser{Wait: make(chan struct{})} + go func() { + resp, err := c.client.Do(req) + if err != nil { + if !uploadOnly { // stream-down is enough + c.closed = true + } + gotConn.Close() + wrc.Close() + return + } + if resp.StatusCode != 200 || uploadOnly { // stream-up + io.Copy(io.Discard, resp.Body) + resp.Body.Close() // if it is called immediately, the upload will be interrupted also + wrc.Close() + return + } + wrc.(*WaitReadCloser).Set(resp.Body) + }() + <-gotConn.Wait() + return +} + +func (c *DefaultDialerClient) PostPacket(ctx context.Context, url string, body io.Reader, contentLength int64) error { + req, err := http.NewRequestWithContext(context.WithoutCancel(ctx), "POST", url, body) + if err != nil { + return err + } + req.ContentLength = contentLength + req.Header = c.options.GetRequestHeader(url) + if c.httpVersion != "1.1" { + resp, err := c.client.Do(req) + if err != nil { + c.closed = true + return err + } + io.Copy(io.Discard, resp.Body) + defer resp.Body.Close() + } else { + // stringify the entire HTTP/1.1 request so it can be + // safely retried. if instead req.Write is called multiple + // times, the body is already drained after the first + // request + requestBuff := new(bytes.Buffer) + common.Must(req.Write(requestBuff)) + var uploadConn any + var h1UploadConn *H1Conn + for { + uploadConn = c.uploadRawPool.Get() + newConnection := uploadConn == nil + if newConnection { + newConn, err := c.dialUploadConn(context.WithoutCancel(ctx)) + if err != nil { + return err + } + h1UploadConn = NewH1Conn(newConn) + uploadConn = h1UploadConn + } else { + h1UploadConn = uploadConn.(*H1Conn) + + // TODO: Replace 0 here with a config value later + // Or add some other condition for optimization purposes + if h1UploadConn.UnreadedResponsesCount > 0 { + resp, err := http.ReadResponse(h1UploadConn.RespBufReader, req) + if err != nil { + c.closed = true + return fmt.Errorf("error while reading response: %s", err.Error()) + } + io.Copy(io.Discard, resp.Body) + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("got non-200 error response code: %d", resp.StatusCode) + } + } + } + _, err := h1UploadConn.Write(requestBuff.Bytes()) + // if the write failed, we try another connection from + // the pool, until the write on a new connection fails. + // failed writes to a pooled connection are normal when + // the connection has been closed in the meantime. + if err == nil { + break + } else if newConnection { + return err + } + } + c.uploadRawPool.Put(uploadConn) + } + + return nil +} + +type WaitReadCloser struct { + Wait chan struct{} + io.ReadCloser +} + +func (w *WaitReadCloser) Set(rc io.ReadCloser) { + w.ReadCloser = rc + defer func() { + if recover() != nil { + rc.Close() + } + }() + close(w.Wait) +} + +func (w *WaitReadCloser) Read(b []byte) (int, error) { + if w.ReadCloser == nil { + if <-w.Wait; w.ReadCloser == nil { + return 0, io.ErrClosedPipe + } + } + return w.ReadCloser.Read(b) +} + +func (w *WaitReadCloser) Close() error { + if w.ReadCloser != nil { + return w.ReadCloser.Close() + } + defer func() { + if recover() != nil && w.ReadCloser != nil { + w.ReadCloser.Close() + } + }() + close(w.Wait) + return nil +} diff --git a/transport/v2rayxhttp/http.go b/transport/v2rayxhttp/http.go new file mode 100644 index 0000000000..03a47da71c --- /dev/null +++ b/transport/v2rayxhttp/http.go @@ -0,0 +1,41 @@ +package xhttp + +import ( + "net/http" + "strings" + + "github.com/sagernet/sing-box/common/xray/net" + "github.com/sagernet/sing-box/common/xray/signal/done" +) + +type httpSession struct { + uploadQueue *uploadQueue + // for as long as the GET request is not opened by the client, this will be + // open ("undone"), and the session may be expired within a certain TTL. + // after the client connects, this becomes "done" and the session lives as + // long as the GET request. + isFullyConnected *done.Instance +} + +func parseXForwardedFor(header http.Header) []net.Address { + xff := header.Get("X-Forwarded-For") + if xff == "" { + return nil + } + list := strings.Split(xff, ",") + addrs := make([]net.Address, 0, len(list)) + for _, proxy := range list { + addrs = append(addrs, net.ParseAddress(proxy)) + } + return addrs +} + +func isValidHTTPHost(request string, config string) bool { + r := strings.ToLower(request) + c := strings.ToLower(config) + if strings.Contains(r, ":") { + h, _, _ := net.SplitHostPort(r) + return h == c + } + return r == c +} diff --git a/transport/v2rayxhttp/mux.go b/transport/v2rayxhttp/mux.go new file mode 100644 index 0000000000..f753aed5ef --- /dev/null +++ b/transport/v2rayxhttp/mux.go @@ -0,0 +1,104 @@ +package xhttp + +import ( + "context" + "crypto/rand" + "math" + "math/big" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/option" +) + +type XmuxConn interface { + IsClosed() bool +} + +type XmuxClient struct { + XmuxConn XmuxConn + OpenUsage atomic.Int32 + leftUsage int32 + LeftRequests atomic.Int32 + UnreusableAt time.Time +} + +type XmuxManager struct { + options option.V2RayXHTTPXmuxOptions + concurrency int32 + connections int32 + newConnFunc func() XmuxConn + xmuxClients []*XmuxClient + mtx sync.Mutex +} + +func NewXmuxManager(options option.V2RayXHTTPXmuxOptions, newConnFunc func() XmuxConn) *XmuxManager { + return &XmuxManager{ + options: options, + concurrency: options.GetNormalizedMaxConcurrency().Rand(), + connections: options.GetNormalizedMaxConnections().Rand(), + newConnFunc: newConnFunc, + xmuxClients: make([]*XmuxClient, 0), + } +} + +func (m *XmuxManager) newXmuxClient() *XmuxClient { + xmuxClient := &XmuxClient{ + XmuxConn: m.newConnFunc(), + leftUsage: -1, + } + if x := m.options.GetNormalizedCMaxReuseTimes().Rand(); x > 0 { + xmuxClient.leftUsage = x - 1 + } + xmuxClient.LeftRequests.Store(math.MaxInt32) + if x := m.options.GetNormalizedHMaxRequestTimes().Rand(); x > 0 { + xmuxClient.LeftRequests.Store(x) + } + if x := m.options.GetNormalizedHMaxReusableSecs().Rand(); x > 0 { + xmuxClient.UnreusableAt = time.Now().Add(time.Duration(x) * time.Second) + } + m.xmuxClients = append(m.xmuxClients, xmuxClient) + return xmuxClient +} + +func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { + m.mtx.Lock() + defer m.mtx.Unlock() + for i := 0; i < len(m.xmuxClients); { + xmuxClient := m.xmuxClients[i] + if xmuxClient.XmuxConn.IsClosed() || + xmuxClient.leftUsage == 0 || + xmuxClient.LeftRequests.Load() <= 0 || + (xmuxClient.UnreusableAt != time.Time{} && time.Now().After(xmuxClient.UnreusableAt)) { + m.xmuxClients = append(m.xmuxClients[:i], m.xmuxClients[i+1:]...) + } else { + i++ + } + } + if len(m.xmuxClients) == 0 { + return m.newXmuxClient() + } + if m.connections > 0 && len(m.xmuxClients) < int(m.connections) { + return m.newXmuxClient() + } + xmuxClients := make([]*XmuxClient, 0) + if m.concurrency > 0 { + for _, xmuxClient := range m.xmuxClients { + if xmuxClient.OpenUsage.Load() < m.concurrency { + xmuxClients = append(xmuxClients, xmuxClient) + } + } + } else { + xmuxClients = m.xmuxClients + } + if len(xmuxClients) == 0 { + return m.newXmuxClient() + } + i, _ := rand.Int(rand.Reader, big.NewInt(int64(len(xmuxClients)))) + xmuxClient := xmuxClients[i.Int64()] + if xmuxClient.leftUsage > 0 { + xmuxClient.leftUsage -= 1 + } + return xmuxClient +} diff --git a/transport/v2rayxhttp/server.go b/transport/v2rayxhttp/server.go new file mode 100644 index 0000000000..1454539c38 --- /dev/null +++ b/transport/v2rayxhttp/server.go @@ -0,0 +1,354 @@ +package xhttp + +import ( + "bytes" + "context" + "io" + "net" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + qtls "github.com/sagernet/sing-quic" + + // qtls "github.com/sagernet/sing-quic" + "github.com/sagernet/sing-box/common/xray/signal/done" + "github.com/sagernet/sing/common" + "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" + sHttp "github.com/sagernet/sing/protocol/http" +) + +var _ adapter.V2RayServerTransport = (*Server)(nil) + +type Server struct { + ctx context.Context + logger logger.ContextLogger + tlsConfig tls.ServerConfig + quicConfig *quic.Config + handler adapter.V2RayServerTransportHandler + httpServer *http.Server + http3Server *http3.Server + localAddr net.Addr + options *option.V2RayXHTTPOptions + host string + path string + sessionMu sync.Mutex + sessions sync.Map +} + +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayXHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { + server := &Server{ + ctx: ctx, + logger: logger, + tlsConfig: tlsConfig, + handler: handler, + options: &options, + host: options.Host, + path: options.GetNormalizedPath(), + } + if server.network() == N.NetworkTCP { + protocols := new(http.Protocols) + protocols.SetHTTP1(true) + protocols.SetUnencryptedHTTP2(true) + server.httpServer = &http.Server{ + Handler: server, + ReadHeaderTimeout: time.Second * 4, + MaxHeaderBytes: 8192, + Protocols: protocols, + BaseContext: func(net.Listener) context.Context { + return ctx + }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, + } + } else { + server.quicConfig = &quic.Config{ + DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows, + } + server.http3Server = &http3.Server{ + Handler: server, + } + } + return server, nil +} + +func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + if len(s.host) > 0 && !isValidHTTPHost(request.Host, s.host) { + s.logger.ErrorContext(request.Context(), "failed to validate host, request:", request.Host, ", config:", s.host) + writer.WriteHeader(http.StatusNotFound) + return + } + if !strings.HasPrefix(request.URL.Path, s.path) { + s.logger.ErrorContext(request.Context(), "failed to validate path, request:", request.URL.Path, ", config:", s.path) + writer.WriteHeader(http.StatusNotFound) + return + } + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Set("Access-Control-Allow-Methods", "GET, POST") + writer.Header().Set("X-Padding", strings.Repeat("X", int(s.options.GetNormalizedXPaddingBytes().Rand()))) + validRange := s.options.GetNormalizedXPaddingBytes() + paddingLength := 0 + referrer := request.Header.Get("Referer") + if referrer != "" { + if referrerURL, err := url.Parse(referrer); err == nil { + // Browser dialer cannot control the host part of referrer header, so only check the query + paddingLength = len(referrerURL.Query().Get("x_padding")) + } + } else { + paddingLength = len(request.URL.Query().Get("x_padding")) + } + if int32(paddingLength) < validRange.From || int32(paddingLength) > validRange.To { + s.logger.ErrorContext(request.Context(), "invalid x_padding length:", int32(paddingLength)) + writer.WriteHeader(http.StatusBadRequest) + return + } + sessionId := "" + subpath := strings.Split(request.URL.Path[len(s.path):], "/") + if len(subpath) > 0 { + sessionId = subpath[0] + } + if sessionId == "" && s.options.Mode != "" && s.options.Mode != "auto" && s.options.Mode != "stream-one" && s.options.Mode != "stream-up" { + s.logger.ErrorContext(request.Context(), "stream-one mode is not allowed") + writer.WriteHeader(http.StatusBadRequest) + return + } + forwardedAddrs := parseXForwardedFor(request.Header) + var remoteAddr net.Addr + var err error + remoteAddr, err = net.ResolveTCPAddr("tcp", request.RemoteAddr) + if err != nil { + remoteAddr = &net.TCPAddr{ + IP: []byte{0, 0, 0, 0}, + Port: 0, + } + } + if request.ProtoMajor == 3 { + remoteAddr = &net.UDPAddr{ + IP: remoteAddr.(*net.TCPAddr).IP, + Port: remoteAddr.(*net.TCPAddr).Port, + } + } + if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() { + remoteAddr = &net.TCPAddr{ + IP: forwardedAddrs[0].IP(), + Port: 0, + } + } + var currentSession *httpSession + if sessionId != "" { + currentSession = s.upsertSession(sessionId) + } + scMaxEachPostBytes := int(s.options.GetNormalizedScMaxEachPostBytes().To) + if request.Method == "POST" && sessionId != "" { // stream-up, packet-up + seq := "" + if len(subpath) > 1 { + seq = subpath[1] + } + if seq == "" { + if s.options.Mode != "" && s.options.Mode != "auto" && s.options.Mode != "stream-up" { + s.logger.ErrorContext(request.Context(), "stream-up mode is not allowed") + writer.WriteHeader(http.StatusBadRequest) + return + } + httpSC := &httpServerConn{ + Instance: done.New(), + Reader: request.Body, + ResponseWriter: writer, + } + err = currentSession.uploadQueue.Push(Packet{ + Reader: httpSC, + }) + if err != nil { + s.logger.InfoContext(request.Context(), err, "failed to upload (PushReader)") + writer.WriteHeader(http.StatusConflict) + } else { + writer.Header().Set("X-Accel-Buffering", "no") + writer.Header().Set("Cache-Control", "no-store") + writer.WriteHeader(http.StatusOK) + scStreamUpServerSecs := s.options.GetNormalizedScStreamUpServerSecs() + if referrer != "" && scStreamUpServerSecs.To > 0 { + go func() { + for { + _, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(s.options.GetNormalizedXPaddingBytes().Rand()))) + if err != nil { + break + } + time.Sleep(time.Duration(scStreamUpServerSecs.Rand()) * time.Second) + } + }() + } + select { + case <-request.Context().Done(): + case <-httpSC.Wait(): + } + } + httpSC.Close() + return + } + if s.options.Mode != "" && s.options.Mode != "auto" && s.options.Mode != "packet-up" { + s.logger.ErrorContext(request.Context(), "packet-up mode is not allowed") + writer.WriteHeader(http.StatusBadRequest) + return + } + payload, err := io.ReadAll(io.LimitReader(request.Body, int64(scMaxEachPostBytes)+1)) + if len(payload) > scMaxEachPostBytes { + s.logger.ErrorContext(request.Context(), "Too large upload. scMaxEachPostBytes is set to ", scMaxEachPostBytes, "but request size exceed it. Adjust scMaxEachPostBytes on the server to be at least as large as client.") + writer.WriteHeader(http.StatusRequestEntityTooLarge) + return + } + if err != nil { + s.logger.InfoContext(request.Context(), err, "failed to upload (ReadAll)") + writer.WriteHeader(http.StatusInternalServerError) + return + } + seqInt, err := strconv.ParseUint(seq, 10, 64) + if err != nil { + s.logger.InfoContext(request.Context(), err, "failed to upload (ParseUint)") + writer.WriteHeader(http.StatusInternalServerError) + return + } + err = currentSession.uploadQueue.Push(Packet{ + Payload: payload, + Seq: seqInt, + }) + if err != nil { + s.logger.InfoContext(request.Context(), err, "failed to upload (PushPayload)") + writer.WriteHeader(http.StatusInternalServerError) + return + } + writer.WriteHeader(http.StatusOK) + } else if request.Method == "GET" || sessionId == "" { // stream-down, stream-one + if sessionId != "" { + // after GET is done, the connection is finished. disable automatic + // session reaping, and handle it in defer + currentSession.isFullyConnected.Close() + defer s.sessions.Delete(sessionId) + } + // magic header instructs nginx + apache to not buffer response body + writer.Header().Set("X-Accel-Buffering", "no") + // A web-compliant header telling all middleboxes to disable caching. + // Should be able to prevent overloading the cache, or stop CDNs from + // teeing the response stream into their cache, causing slowdowns. + writer.Header().Set("Cache-Control", "no-store") + if !s.options.NoSSEHeader { + // magic header to make the HTTP middle box consider this as SSE to disable buffer + writer.Header().Set("Content-Type", "text/event-stream") + } + writer.WriteHeader(http.StatusOK) + writer.(http.Flusher).Flush() + httpSC := &httpServerConn{ + Instance: done.New(), + Reader: request.Body, + ResponseWriter: writer, + } + conn := splitConn{ + writer: httpSC, + reader: httpSC, + remoteAddr: remoteAddr, + localAddr: s.localAddr, + } + if sessionId != "" { // if not stream-one + conn.reader = currentSession.uploadQueue + } + s.handler.NewConnectionEx(request.Context(), &conn, sHttp.SourceAddress(request), M.Socksaddr{}, func(it error) {}) + // "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned." + select { + case <-request.Context().Done(): + case <-httpSC.Wait(): + } + conn.Close() + } else { + s.logger.ErrorContext(request.Context(), "unsupported method: ", request.Method) + writer.WriteHeader(http.StatusMethodNotAllowed) + } +} + +func (s *Server) Network() []string { + return []string{s.network()} +} + +func (s *Server) Serve(listener net.Listener) error { + if s.network() == N.NetworkTCP { + if s.tlsConfig != nil { + listener = aTLS.NewListener(listener, s.tlsConfig) + } + s.localAddr = listener.Addr() + return s.httpServer.Serve(listener) + } + return os.ErrInvalid +} + +func (s *Server) ServePacket(listener net.PacketConn) error { + if s.network() == N.NetworkUDP { + quicListener, err := qtls.ListenEarly(listener, s.tlsConfig, s.quicConfig) + if err != nil { + return err + } + s.localAddr = quicListener.Addr() + return s.http3Server.ServeListener(quicListener) + } + return os.ErrInvalid +} + +func (s *Server) Close() error { + if s.network() == N.NetworkTCP { + return common.Close(s.httpServer) + } + return common.Close(s.http3Server) +} + +func (s *Server) network() string { + if s.tlsConfig != nil && len(s.tlsConfig.NextProtos()) == 1 && s.tlsConfig.NextProtos()[0] == "h3" { + return N.NetworkUDP + } + return N.NetworkTCP +} + +func (s *Server) upsertSession(sessionId string) *httpSession { + // fast path + currentSessionAny, ok := s.sessions.Load(sessionId) + if ok { + return currentSessionAny.(*httpSession) + } + // slow path + s.sessionMu.Lock() + defer s.sessionMu.Unlock() + currentSessionAny, ok = s.sessions.Load(sessionId) + if ok { + return currentSessionAny.(*httpSession) + } + session := &httpSession{ + uploadQueue: NewUploadQueue(s.options.GetNormalizedScMaxBufferedPosts()), + isFullyConnected: done.New(), + } + s.sessions.Store(sessionId, session) + shouldReap := done.New() + go func() { + time.Sleep(30 * time.Second) + shouldReap.Close() + }() + go func() { + select { + case <-shouldReap.Wait(): + s.sessions.Delete(sessionId) + session.uploadQueue.Close() + case <-session.isFullyConnected.Wait(): + } + }() + return session +} diff --git a/transport/v2rayxhttp/upload_queue.go b/transport/v2rayxhttp/upload_queue.go new file mode 100644 index 0000000000..32259a73be --- /dev/null +++ b/transport/v2rayxhttp/upload_queue.go @@ -0,0 +1,157 @@ +package xhttp + +// upload_queue is a specialized priorityqueue + channel to reorder generic +// packets by a sequence number + +import ( + "container/heap" + "io" + "runtime" + "sync" + + E "github.com/sagernet/sing/common/exceptions" +) + +type Packet struct { + Reader io.ReadCloser + Payload []byte + Seq uint64 +} + +type uploadQueue struct { + reader io.ReadCloser + nomore bool + pushedPackets chan Packet + writeCloseMutex sync.Mutex + heap uploadHeap + nextSeq uint64 + closed bool + maxPackets int +} + +func NewUploadQueue(maxPackets int) *uploadQueue { + return &uploadQueue{ + pushedPackets: make(chan Packet, maxPackets), + heap: uploadHeap{}, + nextSeq: 0, + closed: false, + maxPackets: maxPackets, + } +} + +func (h *uploadQueue) Push(p Packet) error { + h.writeCloseMutex.Lock() + defer h.writeCloseMutex.Unlock() + if h.closed { + return E.New("packet queue closed") + } + if h.nomore { + return E.New("h.reader already exists") + } + if p.Reader != nil { + h.nomore = true + } + h.pushedPackets <- p + return nil +} + +func (h *uploadQueue) Close() error { + h.writeCloseMutex.Lock() + defer h.writeCloseMutex.Unlock() + if !h.closed { + h.closed = true + runtime.Gosched() // hope Read() gets the packet + f: + for { + select { + case p := <-h.pushedPackets: + if p.Reader != nil { + h.reader = p.Reader + } + default: + break f + } + } + close(h.pushedPackets) + } + if h.reader != nil { + return h.reader.Close() + } + return nil +} + +func (h *uploadQueue) Read(b []byte) (int, error) { + if h.reader != nil { + return h.reader.Read(b) + } + if h.closed { + return 0, io.EOF + } + if len(h.heap) == 0 { + packet, more := <-h.pushedPackets + if !more { + return 0, io.EOF + } + if packet.Reader != nil { + h.reader = packet.Reader + return h.reader.Read(b) + } + heap.Push(&h.heap, packet) + } + for len(h.heap) > 0 { + packet := heap.Pop(&h.heap).(Packet) + n := 0 + + if packet.Seq == h.nextSeq { + copy(b, packet.Payload) + n = min(len(b), len(packet.Payload)) + + if n < len(packet.Payload) { + // partial read + packet.Payload = packet.Payload[n:] + heap.Push(&h.heap, packet) + } else { + h.nextSeq = packet.Seq + 1 + } + + return n, nil + } + // misordered packet + if packet.Seq > h.nextSeq { + if len(h.heap) > h.maxPackets { + // the "reassembly buffer" is too large, and we want to + // constrain memory usage somehow. let's tear down the + // connection, and hope the application retries. + return 0, E.New("packet queue is too large") + } + heap.Push(&h.heap, packet) + packet2, more := <-h.pushedPackets + if !more { + return 0, io.EOF + } + heap.Push(&h.heap, packet2) + } + } + return 0, nil +} + +// heap code directly taken from https://pkg.go.dev/container/heap +type uploadHeap []Packet + +func (h uploadHeap) Len() int { return len(h) } +func (h uploadHeap) Less(i, j int) bool { return h[i].Seq < h[j].Seq } +func (h uploadHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +func (h *uploadHeap) Push(x any) { + // Push and Pop use pointer receivers because they modify the slice's length, + // not just its contents. + *h = append(*h, x.(Packet)) +} + +func (h *uploadHeap) Pop() any { + old := *h + n := len(old) + x := old[n-1] + *h = old[0 : n-1] + return x +} diff --git a/transport/v2rayxhttp/writer.go b/transport/v2rayxhttp/writer.go new file mode 100644 index 0000000000..e39139e30e --- /dev/null +++ b/transport/v2rayxhttp/writer.go @@ -0,0 +1,38 @@ +package xhttp + +import ( + "github.com/sagernet/sing-box/common/xray/buf" + "github.com/sagernet/sing-box/common/xray/pipe" +) + +// A wrapper around pipe that ensures the size limit is exactly honored. +// +// The MultiBuffer pipe accepts any single WriteMultiBuffer call even if that +// single MultiBuffer exceeds the size limit, and then starts blocking on the +// next WriteMultiBuffer call. This means that ReadMultiBuffer can return more +// bytes than the size limit. We work around this by splitting a potentially +// too large write up into multiple. +type uploadWriter struct { + *pipe.Writer + maxLen int32 +} + +func (w uploadWriter) Write(b []byte) (int, error) { + /* + capacity := int(w.maxLen - w.Len()) + if capacity > 0 && capacity < len(b) { + b = b[:capacity] + } + */ + buffer := buf.New() + n, err := buffer.Write(b) + if err != nil { + return 0, err + } + + err = w.WriteMultiBuffer([]*buf.Buffer{buffer}) + if err != nil { + return 0, err + } + return n, nil +} From 4e74a4108d490a11f357b2531da15049ac9129c2 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 8 Jun 2025 19:51:17 +0300 Subject: [PATCH 0329/1272] refactor: WARP --- adapter/experimental.go | 6 +- common/cloudflare/api.go | 38 +++-- common/cloudflare/option.go | 4 +- common/cloudflare/profile.go | 21 ++- constant/warp.go | 20 +++ experimental/cachefile/cache.go | 44 ++--- go.mod | 45 +++-- go.sum | 109 ++++++------ include/wireguard.go | 2 +- option/experimental.go | 13 +- option/wireguard.go | 25 +-- protocol/wireguard/endpoint_warp.go | 252 ++++++++++++++++------------ 12 files changed, 336 insertions(+), 243 deletions(-) create mode 100644 constant/warp.go diff --git a/adapter/experimental.go b/adapter/experimental.go index fc00b78ab9..09fb8da796 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -33,6 +33,8 @@ type CacheFile interface { StoreRDRC() bool dns.RDRCStore + StoreWARPConfig() bool + LoadMode() string StoreMode(mode string) error LoadSelected(group string) string @@ -41,8 +43,8 @@ type CacheFile interface { StoreGroupExpand(group string, expand bool) error LoadRuleSet(tag string) *SavedBinary SaveRuleSet(tag string, set *SavedBinary) error - LoadCloudflareProfile(tag string) *SavedBinary - SaveCloudflareProfile(tag string, set *SavedBinary) error + LoadWARPConfig(tag string) *SavedBinary + SaveWARPConfig(tag string, set *SavedBinary) error } type SavedBinary struct { diff --git a/common/cloudflare/api.go b/common/cloudflare/api.go index 136ed1921c..2bd6334335 100644 --- a/common/cloudflare/api.go +++ b/common/cloudflare/api.go @@ -10,31 +10,26 @@ import ( "time" "github.com/tidwall/gjson" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -type CloudeflareApi struct { +type CloudflareApi struct { client http.Client } -func NewCloudeflareApi(opts ...CloudflareApiOption) *CloudeflareApi { - api := &CloudeflareApi{http.Client{Timeout: 30 * time.Second}} +func NewCloudflareApi(opts ...CloudflareApiOption) *CloudflareApi { + api := &CloudflareApi{http.Client{Timeout: 30 * time.Second}} for _, opt := range opts { opt(api) } return api } -func (api *CloudeflareApi) CreateProfile(ctx context.Context) (*CloudflareProfile, error) { - privateKey, err := wgtypes.GeneratePrivateKey() - if err != nil { - return nil, err - } +func (api *CloudflareApi) CreateProfile(ctx context.Context, publicKey string) (*CloudflareProfile, error) { request, err := http.NewRequest("POST", "https://api.cloudflareclient.com/v0i1909051800/reg", strings.NewReader( fmt.Sprintf( "{\"install_id\":\"\",\"tos\":\"%s\",\"key\":\"%s\",\"fcm_token\":\"\",\"type\":\"ios\",\"locale\":\"en_US\"}", time.Now().Format("2006-01-02T15:04:05.000Z"), - privateKey.PublicKey().String(), + publicKey, ), )) if err != nil { @@ -53,6 +48,27 @@ func (api *CloudeflareApi) CreateProfile(ctx context.Context) (*CloudflareProfil return nil, err } profile := new(CloudflareProfile) - profile.Config.PrivateKey = privateKey.String() + return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile) +} + +func (api *CloudflareApi) GetProfile(ctx context.Context, authToken string, id string) (*CloudflareProfile, error) { + request, err := http.NewRequest("GET", "https://api.cloudflareclient.com/v0i1909051800/reg/"+id, nil) + if err != nil { + return nil, err + } + request.Header.Set("Authorization", "Bearer "+authToken) + response, err := api.client.Do(request.WithContext(ctx)) + if err != nil { + return nil, err + } + defer response.Body.Close() + if response.StatusCode != 200 { + return nil, fmt.Errorf("status code is not 200") + } + content, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + profile := new(CloudflareProfile) return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile) } diff --git a/common/cloudflare/option.go b/common/cloudflare/option.go index 00c0d6d4d5..929e91b7ea 100644 --- a/common/cloudflare/option.go +++ b/common/cloudflare/option.go @@ -6,10 +6,10 @@ import ( "net/http" ) -type CloudflareApiOption func(api *CloudeflareApi) +type CloudflareApiOption func(api *CloudflareApi) func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) CloudflareApiOption { - return func(api *CloudeflareApi) { + return func(api *CloudflareApi) { api.client.Transport = &http.Transport{ DialContext: dialContext, } diff --git a/common/cloudflare/profile.go b/common/cloudflare/profile.go index f9cf76d277..bc011e0682 100644 --- a/common/cloudflare/profile.go +++ b/common/cloudflare/profile.go @@ -15,7 +15,7 @@ type CloudflareProfile struct { PremiumData int `json:"premium_data"` Quota int `json:"quota"` Usage int `json:"usage"` - WarpPlus bool `json:"warp_plus"` + WARPPlus bool `json:"warp_plus"` ReferralCount int `json:"referral_count"` ReferralRenewalCountdown int `json:"referral_renewal_countdown"` Role string `json:"role"` @@ -23,9 +23,14 @@ type CloudflareProfile struct { TTL time.Time `json:"ttl"` } `json:"account"` Config struct { - ClientID string `json:"client_id"` - PrivateKey string `json:"private_key"` - Peers []struct { + ClientID string `json:"client_id"` + Interface struct { + Addresses struct { + V4 string `json:"v4"` + V6 string `json:"v6"` + } `json:"addresses"` + } `json:"interface"` + Peers []struct { PublicKey string `json:"public_key"` Endpoint struct { V4 string `json:"v4"` @@ -34,12 +39,6 @@ type CloudflareProfile struct { Ports []int `json:"ports"` } `json:"endpoint"` } `json:"peers"` - Interface struct { - Addresses struct { - V4 string `json:"v4"` - V6 string `json:"v6"` - } `json:"addresses"` - } `json:"interface"` Services struct { HTTPProxy string `json:"http_proxy"` } `json:"services"` @@ -49,7 +48,7 @@ type CloudflareProfile struct { } `json:"metrics"` } `json:"config"` Token string `json:"token"` - WarpEnabled bool `json:"warp_enabled"` + WARPEnabled bool `json:"warp_enabled"` WaitlistEnabled bool `json:"waitlist_enabled"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` diff --git a/constant/warp.go b/constant/warp.go new file mode 100644 index 0000000000..038ce346ea --- /dev/null +++ b/constant/warp.go @@ -0,0 +1,20 @@ +package constant + +type WARPConfig struct { + PrivateKey string `json:"private_key"` + Interface struct { + Addresses struct { + V4 string `json:"v4"` + V6 string `json:"v6"` + } `json:"addresses"` + } `json:"interface"` + Peers []struct { + PublicKey string `json:"public_key"` + Endpoint struct { + V4 string `json:"v4"` + V6 string `json:"v6"` + Host string `json:"host"` + Ports []int `json:"ports"` + } `json:"endpoint"` + } `json:"peers"` +} diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 7ac324e205..3d9177d45e 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -43,6 +43,7 @@ type CacheFile struct { cacheID []byte storeFakeIP bool storeRDRC bool + storeWARPConfig bool rdrcTimeout time.Duration DB *bbolt.DB saveMetadataTimer *time.Timer @@ -80,16 +81,17 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { } } return &CacheFile{ - ctx: ctx, - path: filemanager.BasePath(ctx, path), - cacheID: cacheIDBytes, - storeFakeIP: options.StoreFakeIP, - storeRDRC: options.StoreRDRC, - rdrcTimeout: rdrcTimeout, - saveDomain: make(map[netip.Addr]string), - saveAddress4: make(map[string]netip.Addr), - saveAddress6: make(map[string]netip.Addr), - saveRDRC: make(map[saveRDRCCacheKey]bool), + ctx: ctx, + path: filemanager.BasePath(ctx, path), + cacheID: cacheIDBytes, + storeFakeIP: options.StoreFakeIP, + storeRDRC: options.StoreRDRC, + storeWARPConfig: options.StoreWARPConfig, + rdrcTimeout: rdrcTimeout, + saveDomain: make(map[netip.Addr]string), + saveAddress4: make(map[string]netip.Addr), + saveAddress6: make(map[string]netip.Addr), + saveRDRC: make(map[saveRDRCCacheKey]bool), } } @@ -317,35 +319,39 @@ func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error { }) } -func (c *CacheFile) LoadCloudflareProfile(tag string) *adapter.SavedBinary { - var savedProfile adapter.SavedBinary +func (c *CacheFile) StoreWARPConfig() bool { + return c.storeWARPConfig +} + +func (c *CacheFile) LoadWARPConfig(tag string) *adapter.SavedBinary { + var savedConfig adapter.SavedBinary err := c.DB.View(func(t *bbolt.Tx) error { bucket := c.bucket(t, bucketRuleSet) if bucket == nil { return os.ErrNotExist } - profileBinary := bucket.Get([]byte(tag)) - if len(profileBinary) == 0 { + configBinary := bucket.Get([]byte(tag)) + if len(configBinary) == 0 { return os.ErrInvalid } - return savedProfile.UnmarshalBinary(profileBinary) + return savedConfig.UnmarshalBinary(configBinary) }) if err != nil { return nil } - return &savedProfile + return &savedConfig } -func (c *CacheFile) SaveCloudflareProfile(tag string, set *adapter.SavedBinary) error { +func (c *CacheFile) SaveWARPConfig(tag string, set *adapter.SavedBinary) error { return c.DB.Batch(func(t *bbolt.Tx) error { bucket, err := c.createBucket(t, bucketRuleSet) if err != nil { return err } - profileBinary, err := set.MarshalBinary() + configBinary, err := set.MarshalBinary() if err != nil { return err } - return bucket.Put([]byte(tag), profileBinary) + return bucket.Put([]byte(tag), configBinary) }) } diff --git a/go.mod b/go.mod index 4daa087a97..29b4554d84 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/sagernet/sing-box -go 1.23.6 +go 1.24 toolchain go1.24.3 require ( github.com/caddyserver/certmagic v0.20.0 - github.com/cloudflare/circl v1.3.7 + github.com/cloudflare/circl v1.6.1 github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/render v1.0.3 @@ -17,7 +17,7 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 github.com/mholt/acmez v1.2.0 - github.com/miekg/dns v1.1.63 + github.com/miekg/dns v1.1.66 github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a @@ -45,14 +45,14 @@ require ( github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.32.0 + golang.org/x/crypto v0.38.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/mod v0.20.0 - golang.org/x/net v0.34.0 - golang.org/x/sys v0.30.0 + golang.org/x/mod v0.24.0 + golang.org/x/net v0.40.0 + golang.org/x/sys v0.33.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 - google.golang.org/grpc v1.63.2 - google.golang.org/protobuf v1.33.0 + google.golang.org/grpc v1.72.1 + google.golang.org/protobuf v1.36.6 howett.net/plist v1.0.1 ) @@ -65,32 +65,29 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect - github.com/andybalholm/brotli v1.0.6 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // 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/go-querystring v1.1.0 // indirect - github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/libdns/libdns v0.2.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect @@ -98,18 +95,18 @@ require ( github.com/tevino/abool/v2 v2.1.0 // indirect github.com/tidwall/gjson v1.18.0 github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect - github.com/vishvananda/netns v0.0.4 // indirect + github.com/vishvananda/netns v0.0.5 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.7.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.7.0 + golang.org/x/tools v0.32.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.3.0 // indirect + lukechampine.com/blake3 v1.4.1 // indirect ) replace github.com/sagernet/wireguard-go => github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec diff --git a/go.sum b/go.sum index 09c49c619c..e6aa774e29 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= 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/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= @@ -24,12 +24,12 @@ 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/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/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/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 v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -41,12 +41,12 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 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.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -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/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= -github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -57,11 +57,11 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -83,22 +83,18 @@ github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVr github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= -github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= +github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +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/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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -156,7 +152,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -174,14 +169,26 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -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/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +go.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.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 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= @@ -191,18 +198,18 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 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.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -210,32 +217,32 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -245,5 +252,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= -lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= +lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= diff --git a/include/wireguard.go b/include/wireguard.go index ead47ced07..1e2b92a036 100644 --- a/include/wireguard.go +++ b/include/wireguard.go @@ -14,5 +14,5 @@ func registerWireGuardOutbound(registry *outbound.Registry) { func registerWireGuardEndpoint(registry *endpoint.Registry) { wireguard.RegisterEndpoint(registry) - wireguard.RegisterWarpEndpoint(registry) + wireguard.RegisterWARPEndpoint(registry) } diff --git a/option/experimental.go b/option/experimental.go index bf0df9e78c..730740a488 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -10,12 +10,13 @@ type ExperimentalOptions struct { } type CacheFileOptions struct { - Enabled bool `json:"enabled,omitempty"` - Path string `json:"path,omitempty"` - CacheID string `json:"cache_id,omitempty"` - StoreFakeIP bool `json:"store_fakeip,omitempty"` - StoreRDRC bool `json:"store_rdrc,omitempty"` - RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Path string `json:"path,omitempty"` + CacheID string `json:"cache_id,omitempty"` + StoreFakeIP bool `json:"store_fakeip,omitempty"` + StoreRDRC bool `json:"store_rdrc,omitempty"` + StoreWARPConfig bool `json:"store_warp_config,omitempty"` + RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"` } type ClashAPIOptions struct { diff --git a/option/wireguard.go b/option/wireguard.go index f819ca8ffa..f0b4918174 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -30,20 +30,23 @@ type WireGuardPeer struct { Reserved []uint8 `json:"reserved,omitempty"` } -type WireGuardWarpEndpointOptions struct { - System bool `json:"system,omitempty"` - Name string `json:"name,omitempty"` - ListenPort uint16 `json:"listen_port,omitempty"` - UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` - Workers int `json:"workers,omitempty"` - Amnezia *WireGuardAmnezia `json:"amnezia,omitempty"` - Profile *WireGuardWarpProfile `json:"profile,omitempty"` +type WireGuardWARPEndpointOptions struct { + System bool `json:"system,omitempty"` + Name string `json:"name,omitempty"` + ListenPort uint16 `json:"listen_port,omitempty"` + UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` + Workers int `json:"workers,omitempty"` + Amnezia *WireGuardAmnezia `json:"amnezia,omitempty"` + Profile *WireGuardCloudflareProfile `json:"profile,omitempty"` DialerOptions } -type WireGuardWarpProfile struct { - Detour string `json:"detour,omitempty"` - Recreate bool `json:"recreate,omitempty"` +type WireGuardCloudflareProfile struct { + ID string `json:"id,omitempty"` + PrivateKey string `json:"private_key,omitempty"` + AuthToken string `json:"auth_token,omitempty"` + Recreate bool `json:"recreate,omitempty"` + Detour string `json:"detour,omitempty"` } type LegacyWireGuardOutboundOptions struct { diff --git a/protocol/wireguard/endpoint_warp.go b/protocol/wireguard/endpoint_warp.go index 8b4d02812e..9773cd01cc 100644 --- a/protocol/wireguard/endpoint_warp.go +++ b/protocol/wireguard/endpoint_warp.go @@ -7,6 +7,7 @@ import ( "net" "net/netip" "strings" + "sync" "time" "github.com/sagernet/sing-box/adapter" @@ -20,23 +21,22 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -func RegisterWarpEndpoint(registry *endpoint.Registry) { - endpoint.Register[option.WireGuardWarpEndpointOptions](registry, C.TypeWARP, NewWarpEndpoint) +func RegisterWARPEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.WireGuardWARPEndpointOptions](registry, C.TypeWARP, NewWARPEndpoint) } -type WarpEndpoint struct { +type WARPEndpoint struct { endpoint.Adapter - endpoint adapter.Endpoint - ctx context.Context - router adapter.Router - logger log.ContextLogger - tag string - options option.WireGuardWarpEndpointOptions + endpoint adapter.Endpoint + startHandler func() + + mtx sync.Mutex } -func NewWarpEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardWarpEndpointOptions) (adapter.Endpoint, error) { +func NewWARPEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardWARPEndpointOptions) (adapter.Endpoint, error) { var dependencies []string if options.Detour != "" { dependencies = append(dependencies, options.Detour) @@ -46,128 +46,170 @@ func NewWarpEndpoint(ctx context.Context, router adapter.Router, logger log.Cont dependencies = append(dependencies, options.Profile.Detour) } } - return &WarpEndpoint{ + warpEndpoint := &WARPEndpoint{ Adapter: endpoint.NewAdapter(C.TypeWARP, tag, []string{N.NetworkTCP, N.NetworkUDP}, dependencies), - ctx: ctx, - router: router, - logger: logger, - tag: tag, - options: options, - }, nil -} - -func (w *WarpEndpoint) Start(stage adapter.StartStage) error { - if stage != adapter.StartStatePostStart { - return nil } - cacheFile := service.FromContext[adapter.CacheFile](w.ctx) - var profile *cloudflare.CloudflareProfile - var err error - if !w.options.Profile.Recreate { - if cacheFile != nil { - savedProfile := cacheFile.LoadCloudflareProfile(w.tag) + warpEndpoint.mtx.Lock() + warpEndpoint.startHandler = func() { + defer warpEndpoint.mtx.Unlock() + cacheFile := service.FromContext[adapter.CacheFile](ctx) + var config *C.WARPConfig + var err error + if !options.Profile.Recreate && cacheFile != nil && cacheFile.StoreWARPConfig() { + savedProfile := cacheFile.LoadWARPConfig(tag) if savedProfile != nil { - err := json.Unmarshal(savedProfile.Content, &profile) - if err != nil { - return err + if err = json.Unmarshal(savedProfile.Content, &config); err != nil { + logger.ErrorContext(ctx, err) + return } } } - } - if profile == nil { - opts := make([]cloudflare.CloudflareApiOption, 0, 1) - if w.options.Profile != nil { - if w.options.Profile.Detour != "" { - detour, ok := service.FromContext[adapter.OutboundManager](w.ctx).Outbound(w.options.Profile.Detour) - if !ok { - return E.New("outbound detour not found: ", w.options.Profile.Detour) + if config == nil { + var privateKey wgtypes.Key + if options.Profile.PrivateKey != "" { + privateKey, err = wgtypes.ParseKey(options.Profile.PrivateKey) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + } else { + privateKey, err = wgtypes.GeneratePrivateKey() + if err != nil { + logger.ErrorContext(ctx, err) + return } - opts = append(opts, cloudflare.WithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) { - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) - })) } - } - api := cloudflare.NewCloudeflareApi(opts...) - profile, err = api.CreateProfile(w.ctx) - if err != nil { - return err - } - if cacheFile != nil { - content, err := json.Marshal(profile) - if err != nil { - return err + opts := make([]cloudflare.CloudflareApiOption, 0, 1) + if options.Profile != nil { + if options.Profile.Detour != "" { + detour, ok := service.FromContext[adapter.OutboundManager](ctx).Outbound(options.Profile.Detour) + if !ok { + logger.ErrorContext(ctx, E.New("outbound detour not found: ", options.Profile.Detour)) + return + } + opts = append(opts, cloudflare.WithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) { + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) + })) + } + } + api := cloudflare.NewCloudflareApi(opts...) + var profile *cloudflare.CloudflareProfile + if options.Profile.AuthToken != "" && options.Profile.ID != "" { + profile, err = api.GetProfile(ctx, options.Profile.AuthToken, options.Profile.ID) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + } else { + profile, err = api.CreateProfile(ctx, privateKey.PublicKey().String()) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + } + config = &C.WARPConfig{ + PrivateKey: privateKey.String(), + Interface: profile.Config.Interface, + Peers: profile.Config.Peers, + } + if cacheFile != nil && cacheFile.StoreWARPConfig() { + content, err := json.Marshal(config) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + cacheFile.SaveWARPConfig(tag, &adapter.SavedBinary{ + LastUpdated: time.Now(), + Content: content, + LastEtag: "", + }) } - cacheFile.SaveCloudflareProfile(w.tag, &adapter.SavedBinary{ - LastUpdated: time.Now(), - Content: content, - LastEtag: "", - }) } - } - peer := profile.Config.Peers[0] - hostParts := strings.Split(peer.Endpoint.Host, ":") - w.endpoint, err = NewEndpoint( - w.ctx, - w.router, - w.logger, - w.tag, - option.WireGuardEndpointOptions{ - System: w.options.System, - Name: w.options.Name, - ListenPort: w.options.ListenPort, - UDPTimeout: w.options.UDPTimeout, - Workers: w.options.Workers, - Amnezia: w.options.Amnezia, - DialerOptions: w.options.DialerOptions, + peer := config.Peers[0] + hostParts := strings.Split(peer.Endpoint.Host, ":") + warpEndpoint.endpoint, err = NewEndpoint( + ctx, + router, + logger, + tag, + option.WireGuardEndpointOptions{ + System: options.System, + Name: options.Name, + ListenPort: options.ListenPort, + UDPTimeout: options.UDPTimeout, + Workers: options.Workers, + Amnezia: options.Amnezia, + DialerOptions: options.DialerOptions, - Address: badoption.Listable[netip.Prefix]{ - netip.MustParsePrefix(profile.Config.Interface.Addresses.V4 + "/32"), - netip.MustParsePrefix(profile.Config.Interface.Addresses.V6 + "/128"), - }, - PrivateKey: profile.Config.PrivateKey, - Peers: []option.WireGuardPeer{ - { - Address: hostParts[0], - Port: uint16(peer.Endpoint.Ports[rand.Intn(len(peer.Endpoint.Ports))]), - PublicKey: peer.PublicKey, - AllowedIPs: badoption.Listable[netip.Prefix]{ - netip.MustParsePrefix("0.0.0.0/0"), - netip.MustParsePrefix("::/0"), + Address: badoption.Listable[netip.Prefix]{ + netip.MustParsePrefix(config.Interface.Addresses.V4 + "/32"), + netip.MustParsePrefix(config.Interface.Addresses.V6 + "/128"), + }, + PrivateKey: config.PrivateKey, + Peers: []option.WireGuardPeer{ + { + Address: hostParts[0], + Port: uint16(peer.Endpoint.Ports[rand.Intn(len(peer.Endpoint.Ports))]), + PublicKey: peer.PublicKey, + AllowedIPs: badoption.Listable[netip.Prefix]{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, }, }, + MTU: 1280, }, - MTU: 1280, - }, - ) - if err != nil { - return err - } - if err := w.endpoint.Start(adapter.StartStateStart); err != nil { - return err + ) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + if err = warpEndpoint.endpoint.Start(adapter.StartStateStart); err != nil { + logger.ErrorContext(ctx, err) + return + } + if err = warpEndpoint.endpoint.Start(adapter.StartStatePostStart); err != nil { + logger.ErrorContext(ctx, err) + return + } } - if err := w.endpoint.Start(adapter.StartStatePostStart); err != nil { - return err + return warpEndpoint, nil +} + +func (w *WARPEndpoint) Start(stage adapter.StartStage) error { + if stage != adapter.StartStatePostStart { + return nil } + go w.startHandler() return nil } -func (w *WarpEndpoint) Close() error { - if w.endpoint == nil { - return E.New("endpoint not initialized") +func (w *WARPEndpoint) Close() error { + if err := w.isEndpointInitialized(); err != nil { + return err } return w.endpoint.Close() } -func (w *WarpEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - if w.endpoint == nil { - return nil, E.New("endpoint not initialized") +func (w *WARPEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if err := w.isEndpointInitialized(); err != nil { + return nil, err } return w.endpoint.DialContext(ctx, network, destination) } -func (w *WarpEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - if w.endpoint == nil { - return nil, E.New("endpoint not initialized") +func (w *WARPEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if err := w.isEndpointInitialized(); err != nil { + return nil, err } return w.endpoint.ListenPacket(ctx, destination) } + +func (w *WARPEndpoint) isEndpointInitialized() error { + w.mtx.Lock() + defer w.mtx.Unlock() + if w.endpoint == nil { + return E.New("endpoint not initialized") + } + return nil +} From 0dff811977e18633e5c822aae14d9c837de29674 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 8 Jun 2025 22:38:32 +0300 Subject: [PATCH 0330/1272] Add examples --- examples/amnezia/client.json | 84 +++++++++++++++++++++++++++++++ examples/warp/client.json | 79 +++++++++++++++++++++++++++++ examples/xhttp/client.json | 97 ++++++++++++++++++++++++++++++++++++ examples/xhttp/server.json | 65 ++++++++++++++++++++++++ 4 files changed, 325 insertions(+) create mode 100644 examples/amnezia/client.json create mode 100644 examples/warp/client.json create mode 100644 examples/xhttp/client.json create mode 100644 examples/xhttp/server.json diff --git a/examples/amnezia/client.json b/examples/amnezia/client.json new file mode 100644 index 0000000000..797fa0594a --- /dev/null +++ b/examples/amnezia/client.json @@ -0,0 +1,84 @@ +{ + "log":{ + "level":"error" + }, + "dns":{ + "servers":[ + { + "address":"local", + "detour":"direct" + } + ] + }, + "endpoints":[ + { + "type":"wireguard", + "tag":"wireguard-out", + "system":false, + "name":"", + "mtu":1408, + "address":[], + "private_key":"", + "listen_port":10000, + "peers":[ + { + "address":"example.com", + "port":10001, + "public_key":"", + "pre_shared_key":"", + "allowed_ips":[], + "persistent_keepalive_interval":0, + "reserved":[ + 0, + 0, + 0 + ] + } + ], + "udp_timeout":"", + "workers":0, + "amnezia":{ + "jc":120, + "jmin":23, + "jmax":911, + "s1":0, + "s2":0, + "h1":1, + "h2":2, + "h3":3, + "h4":4 + } + } + ], + "inbounds":[ + { + "type":"mixed", + "tag":"mixed-in", + "listen_port":7897 + } + ], + "outbounds":[ + { + "type":"direct", + "tag":"direct" + }, + { + "type":"dns", + "tag":"dns-out" + } + ], + "route":{ + "rules":[ + { + "protocol":"dns", + "outbound":"dns-out" + }, + { + "port":53, + "outbound":"dns-out" + } + ], + "final":"wireguard-out", + "auto_detect_interface":true + } +} \ No newline at end of file diff --git a/examples/warp/client.json b/examples/warp/client.json new file mode 100644 index 0000000000..c32b0d830d --- /dev/null +++ b/examples/warp/client.json @@ -0,0 +1,79 @@ +{ + "log":{ + "level":"error" + }, + "dns":{ + "servers":[ + { + "address":"local", + "detour":"direct" + } + ] + }, + "endpoints":[ + { + "type":"warp", + "tag":"warp-out", + "system":false, + "name":"", + "listen_port":10000, + "udp_timeout":"", + "workers":0, + "profile":{ + "detour":"direct", + "recreate":false, + // for getting existing WARP device profile + "id":"", + "private_key":"", + "auth_token":"" + }, + "amnezia":{ + "jc":120, + "jmin":23, + "jmax":911, + "h1":1, + "h2":2, + "h3":3, + "h4":4 + } + // Dial Fields + } + ], + "inbounds":[ + { + "type":"mixed", + "tag":"mixed-in", + "listen_port":7897 + } + ], + "outbounds":[ + { + "type":"direct", + "tag":"direct" + }, + { + "type":"dns", + "tag":"dns-out" + } + ], + "route":{ + "rules":[ + { + "protocol":"dns", + "outbound":"dns-out" + }, + { + "port":53, + "outbound":"dns-out" + } + ], + "final":"warp-out", + "auto_detect_interface":true + }, + "experimental":{ + "cache_file":{ + "enabled":true, + "store_warp_config":true + } + } +} \ No newline at end of file diff --git a/examples/xhttp/client.json b/examples/xhttp/client.json new file mode 100644 index 0000000000..0c5fcce013 --- /dev/null +++ b/examples/xhttp/client.json @@ -0,0 +1,97 @@ +{ + "log":{ + "level":"error" + }, + "dns":{ + "servers":[ + { + "address":"local", + "detour":"direct" + } + ] + }, + "inbounds":[ + { + "type":"mixed", + "tag":"mixed-in", + "listen_port":7897 + } + ], + "outbounds":[ + { + "type":"direct", + "tag":"direct" + }, + { + "type":"vless", + "tag":"vless-out", + "server":"example.com", + "server_port":443, + "uuid":"3179dce2-2ff9-413c-85b4-c1d53ed41668", + "tls":{ + "enabled":true, + "server_name":"example.com", + "alpn":[ + "h2" // h3 for QUIC + ] + }, + "packet_encoding":"", + "transport":{ + "type":"xhttp", + "mode":"stream-up", + "host":"example.com", + "path":"/xhttp", + "domain_strategy":"prefer_ipv4", + "xmux":{ + "max_concurrency":"0-1", + "max_connections":"0-1", + "c_max_reuse_times":"0-1", + "h_max_request_times":"0-1", + "h_max_reusable_secs":"0-1", + "h_keep_alive_period":60 + }, + "download":{ + "server":"example.com", + "server_port":443, + "host":"example.com", + "path":"/xhttp", + "domain_strategy":"prefer_ipv4", + "detour":"direct", + "tls":{ + "enabled":true, + "server_name":"example.com", + "alpn":[ + "h2" // h3 for QUIC + ] + }, + "xmux":{ + "max_concurrency":"0-1", + "max_connections":"0-1", + "c_max_reuse_times":"0-1", + "h_max_request_times":"0-1", + "h_max_reusable_secs":"0-1", + "h_keep_alive_period":60 + } + } + } + }, + { + "type":"dns", + "tag":"dns-out" + } + ], + "route":{ + "rules":[ + { + "protocol":"dns", + "outbound":"dns-out" + }, + { + "port":53, + "outbound":"dns-out" + } + ], + "final":"vless-out", + "auto_detect_interface":true + } +} \ No newline at end of file diff --git a/examples/xhttp/server.json b/examples/xhttp/server.json new file mode 100644 index 0000000000..0df228d37a --- /dev/null +++ b/examples/xhttp/server.json @@ -0,0 +1,65 @@ +{ + "log":{ + "level":"error" + }, + "dns":{ + "servers":[ + { + "address":"local", + "detour":"direct" + } + ] + }, + "inbounds":[ + { + "type":"vless", + "tag":"vless-out", + "listen":"0.0.0.0", + "listen_port":443, + "tls":{ + "enabled":true, + "server_name":"example.com", + "certificate_path":"/path/to/fullchain.pem", + "key_path":"/path/to/privkey.pem", + "alpn":[ + "h2" // h3 for QUIC + ] + }, + "users":[ + { + "name":"user", + "uuid":"3179dce2-2ff9-413c-85b4-c1d53ed41668" + } + ], + "transport":{ + "type":"xhttp", + "mode":"stream-up", + "path":"/xhttp" + } + } + ], + "outbounds":[ + { + "type":"direct", + "tag":"direct" + }, + { + "type":"dns", + "tag":"dns-out" + } + ], + "route":{ + "rules":[ + { + "protocol":"dns", + "outbound":"dns-out" + }, + { + "port":53, + "outbound":"dns-out" + } + ], + "final":"direct", + "auto_detect_interface":true + } +} \ No newline at end of file From cb435ea232e399fb2e487b6d6dfb1ae6deb31c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 6 Jun 2025 14:50:38 +0800 Subject: [PATCH 0331/1272] Fix default network strategy --- common/dialer/default.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index bd8d901861..ff524d083e 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -100,10 +100,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial } else if networkManager.AutoDetectInterface() { if platformInterface != nil { networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy) - if networkStrategy == nil { - networkStrategy = common.Ptr(C.NetworkStrategyDefault) - defaultNetworkStrategy = true - } networkType = common.Map(options.NetworkType, option.InterfaceType.Build) fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build) if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 { @@ -115,6 +111,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 { networkFallbackDelay = defaultOptions.FallbackDelay } + if networkStrategy == nil { + networkStrategy = common.Ptr(C.NetworkStrategyDefault) + defaultNetworkStrategy = true + } bindFunc := networkManager.ProtectFunc() dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) From d511698f3f6f7d1a684e740202be202a5e65c1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 6 Jun 2025 14:39:40 +0800 Subject: [PATCH 0332/1272] Fix slowOpenConn --- common/dialer/tfo.go | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go index 9f72208dba..8ea59ca62b 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -10,9 +10,7 @@ import ( "sync" "time" - "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -26,7 +24,9 @@ type slowOpenConn struct { destination M.Socksaddr conn net.Conn create chan struct{} + done chan struct{} access sync.Mutex + closeOnce sync.Once err error } @@ -45,6 +45,7 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des network: network, destination: destination, create: make(chan struct{}), + done: make(chan struct{}), }, nil } @@ -55,8 +56,8 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) { if c.err != nil { return 0, c.err } - case <-c.ctx.Done(): - return 0, c.ctx.Err() + case <-c.done: + return 0, os.ErrClosed } } return c.conn.Read(b) @@ -74,12 +75,15 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) { return 0, c.err } return c.conn.Write(b) + case <-c.done: + return 0, os.ErrClosed default: } - c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b) + conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b) if err != nil { - c.conn = nil - c.err = E.Cause(err, "dial tcp fast open") + c.err = err + } else { + c.conn = conn } n = len(b) close(c.create) @@ -87,7 +91,13 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) { } func (c *slowOpenConn) Close() error { - return common.Close(c.conn) + c.closeOnce.Do(func() { + close(c.done) + if c.conn != nil { + c.conn.Close() + } + }) + return nil } func (c *slowOpenConn) LocalAddr() net.Addr { @@ -152,8 +162,8 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) { if c.err != nil { return 0, c.err } - case <-c.ctx.Done(): - return 0, c.ctx.Err() + case <-c.done: + return 0, c.err } } return bufio.Copy(w, c.conn) From 7c4c2d5ca8b37513288466e7931777525c56d6c1 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 15 Jun 2025 18:04:27 +0300 Subject: [PATCH 0333/1272] Add mieru protocol --- constant/proxy.go | 3 + docs/configuration/outbound/index.md | 1 + docs/configuration/outbound/index.zh.md | 1 + docs/configuration/outbound/mieru.md | 71 +++++++ docs/configuration/outbound/mieru.zh.md | 71 +++++++ go.mod | 1 + go.sum | 2 + include/registry.go | 2 + option/mieru.go | 13 ++ protocol/mieru/outbound.go | 245 ++++++++++++++++++++++++ 10 files changed, 410 insertions(+) create mode 100644 docs/configuration/outbound/mieru.md create mode 100644 docs/configuration/outbound/mieru.zh.md create mode 100644 option/mieru.go create mode 100644 protocol/mieru/outbound.go diff --git a/constant/proxy.go b/constant/proxy.go index 753eb0bf0c..8708604a83 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -20,6 +20,7 @@ const ( TypeTor = "tor" TypeSSH = "ssh" TypeShadowTLS = "shadowtls" + TypeMieru = "mieru" TypeShadowsocksR = "shadowsocksr" TypeVLESS = "vless" TypeTUIC = "tuic" @@ -79,6 +80,8 @@ func ProxyDisplayName(proxyType string) string { return "TUIC" case TypeHysteria2: return "Hysteria2" + case TypeMieru: + return "Mieru" case TypeSelector: return "Selector" case TypeURLTest: diff --git a/docs/configuration/outbound/index.md b/docs/configuration/outbound/index.md index c5dc291769..9087253abf 100644 --- a/docs/configuration/outbound/index.md +++ b/docs/configuration/outbound/index.md @@ -30,6 +30,7 @@ | `shadowtls` | [ShadowTLS](./shadowtls/) | | `tuic` | [TUIC](./tuic/) | | `hysteria2` | [Hysteria2](./hysteria2/) | +| `mieru` | [Mieru](./mieru/) | | `tor` | [Tor](./tor/) | | `ssh` | [SSH](./ssh/) | | `dns` | [DNS](./dns/) | diff --git a/docs/configuration/outbound/index.zh.md b/docs/configuration/outbound/index.zh.md index c7ee59e91d..05afb7ea21 100644 --- a/docs/configuration/outbound/index.zh.md +++ b/docs/configuration/outbound/index.zh.md @@ -30,6 +30,7 @@ | `shadowtls` | [ShadowTLS](./shadowtls/) | | `tuic` | [TUIC](./tuic/) | | `hysteria2` | [Hysteria2](./hysteria2/) | +| `mieru` | [Mieru](./mieru/) | | `tor` | [Tor](./tor/) | | `ssh` | [SSH](./ssh/) | | `dns` | [DNS](./dns/) | diff --git a/docs/configuration/outbound/mieru.md b/docs/configuration/outbound/mieru.md new file mode 100644 index 0000000000..135403f04d --- /dev/null +++ b/docs/configuration/outbound/mieru.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +### Structure + +```json +{ + "type": "mieru", + "tag": "mieru-out", + + "server": "127.0.0.1", + "server_port": 1080, + "server_ports": [ + "9000-9010", + "9020-9030" + ], + "transport": "TCP", + "username": "asdf", + "password": "hjkl", + "multiplexing": "MULTIPLEXING_LOW", + + ... // Dial Fields +} +``` + +### Fields + +#### server + +==Required== + +The server address. + +#### server_port + +The server port. + +Must set at least one field between `server_port` and `server_ports`. + +#### server_ports + +Server port range list. + +Must set at least one field between `server_port` and `server_ports`. + +#### transport + +==Required== + +Transmission protocol. The only allowed value is `TCP`. + +#### username + +==Required== + +mieru user name. + +#### password + +==Required== + +mieru password. + +#### multiplexing + +Multiplexing level. Supported values are `MULTIPLEXING_OFF`, `MULTIPLEXING_LOW`, `MULTIPLEXING_MIDDLE`, `MULTIPLEXING_HIGH`. `MULTIPLEXING_OFF` disables multiplexing. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/mieru.zh.md b/docs/configuration/outbound/mieru.zh.md new file mode 100644 index 0000000000..fe9b37b83b --- /dev/null +++ b/docs/configuration/outbound/mieru.zh.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +### 结构 + +```json +{ + "type": "mieru", + "tag": "mieru-out", + + "server": "127.0.0.1", + "server_port": 1080, + "server_ports": [ + "9000-9010", + "9020-9030" + ], + "transport": "TCP", + "username": "asdf", + "password": "hjkl", + "multiplexing": "MULTIPLEXING_LOW", + + ... // 拨号字段 +} +``` + +### 字段 + +#### server + +==必填== + +服务器地址。 + +#### server_port + +服务器端口。 + +必须填写 `server_port` 和 `server_ports` 中至少一项。 + +#### server_ports + +服务器端口范围列表。 + +必须填写 `server_port` 和 `server_ports` 中至少一项。 + +#### transport + +==必填== + +通信协议。仅可设为 `TCP`。 + +#### username + +==必填== + +mieru 用户名。 + +#### password + +==必填== + +mieru 密码。 + +#### multiplexing + +多路复用设置。可以设为 `MULTIPLEXING_OFF`,`MULTIPLEXING_LOW`,`MULTIPLEXING_MIDDLE`,`MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/go.mod b/go.mod index 3720ce6ad2..6fad77b34f 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.6.1 github.com/cretz/bine v0.2.0 + github.com/enfein/mieru/v3 v3.13.0 github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.3.2 diff --git a/go.sum b/go.sum index f5fcf5c186..42a41636c7 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98= +github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= 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/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec h1:jXekDkSozctYj5JQlV1mCsIW+qHpIkgSFhOIpgRSB1I= diff --git a/include/registry.go b/include/registry.go index e71ffb0c82..f4b8b61d82 100644 --- a/include/registry.go +++ b/include/registry.go @@ -15,6 +15,7 @@ import ( "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/mieru" "github.com/sagernet/sing-box/protocol/mixed" "github.com/sagernet/sing-box/protocol/naive" "github.com/sagernet/sing-box/protocol/redirect" @@ -75,6 +76,7 @@ func OutboundRegistry() *outbound.Registry { ssh.RegisterOutbound(registry) shadowtls.RegisterOutbound(registry) vless.RegisterOutbound(registry) + mieru.RegisterOutbound(registry) registerQUICOutbounds(registry) registerWireGuardOutbound(registry) diff --git a/option/mieru.go b/option/mieru.go new file mode 100644 index 0000000000..fe5a407ba6 --- /dev/null +++ b/option/mieru.go @@ -0,0 +1,13 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +type MieruOutboundOptions struct { + DialerOptions + ServerOptions + ServerPortRanges badoption.Listable[string] `json:"server_ports,omitempty"` + Transport string `json:"transport,omitempty"` + UserName string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Multiplexing string `json:"multiplexing,omitempty"` +} diff --git a/protocol/mieru/outbound.go b/protocol/mieru/outbound.go new file mode 100644 index 0000000000..670b8d8067 --- /dev/null +++ b/protocol/mieru/outbound.go @@ -0,0 +1,245 @@ +package mieru + +import ( + "context" + "fmt" + "net" + "os" + + "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" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mieruclient "github.com/enfein/mieru/v3/apis/client" + mierucommon "github.com/enfein/mieru/v3/apis/common" + mierumodel "github.com/enfein/mieru/v3/apis/model" + mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb" + "google.golang.org/protobuf/proto" +) + +type Outbound struct { + outbound.Adapter + dialer N.Dialer + logger log.ContextLogger + client mieruclient.Client +} + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register(registry, C.TypeMieru, NewOutbound) +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MieruOutboundOptions) (adapter.Outbound, error) { + outboundDialer, err := dialer.New(ctx, options.DialerOptions) + if err != nil { + return nil, err + } + + config, err := buildMieruClientConfig(options, mieruDialer{dialer: outboundDialer}) + if err != nil { + return nil, fmt.Errorf("failed to build mieru client config: %w", err) + } + c := mieruclient.NewClient() + if err := c.Store(config); err != nil { + return nil, fmt.Errorf("failed to store mieru client config: %w", err) + } + if err := c.Start(); err != nil { + return nil, fmt.Errorf("failed to start mieru client: %w", err) + } + logger.InfoContext(ctx, "mieru client is started") + + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMieru, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + dialer: outboundDialer, + logger: logger, + client: c, + }, 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 + switch N.NetworkName(network) { + case N.NetworkTCP: + o.logger.InfoContext(ctx, "outbound connection to ", destination) + d, err := socksAddrToNetAddrSpec(destination, "tcp") + if err != nil { + return nil, E.Cause(err, "failed to convert destination address") + } + return o.client.DialContext(ctx, d) + case N.NetworkUDP: + o.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + d, err := socksAddrToNetAddrSpec(destination, "udp") + if err != nil { + return nil, E.Cause(err, "failed to convert destination address") + } + streamConn, err := o.client.DialContext(ctx, d) + if err != nil { + return nil, err + } + return &streamer{ + PacketConn: mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(streamConn)), + Remote: destination, + }, nil + default: + return nil, os.ErrInvalid + } +} + +func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = o.Tag() + metadata.Destination = destination + o.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + d, err := socksAddrToNetAddrSpec(destination, "udp") + if err != nil { + return nil, E.Cause(err, "failed to convert destination address") + } + streamConn, err := o.client.DialContext(ctx, d) + if err != nil { + return nil, err + } + return mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(streamConn)), nil +} + +func (o *Outbound) Close() error { + return common.Close(o.client) +} + +// mieruDialer is an adapter to mieru dialer interface. +type mieruDialer struct { + dialer N.Dialer +} + +func (md mieruDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + addr := M.ParseSocksaddr(address) + return md.dialer.DialContext(ctx, network, addr) +} + +var _ mierucommon.Dialer = (*mieruDialer)(nil) + +// streamer converts a net.PacketConn to a net.Conn. +type streamer struct { + net.PacketConn + Remote net.Addr +} + +var _ net.Conn = (*streamer)(nil) + +func (s *streamer) Read(b []byte) (n int, err error) { + n, _, err = s.PacketConn.ReadFrom(b) + return +} + +func (s *streamer) Write(b []byte) (n int, err error) { + return s.WriteTo(b, s.Remote) +} + +func (s *streamer) RemoteAddr() net.Addr { + return s.Remote +} + +// socksAddrToNetAddrSpec converts a Socksaddr object to NetAddrSpec, and overrides the network. +func socksAddrToNetAddrSpec(sa M.Socksaddr, network string) (mierumodel.NetAddrSpec, error) { + var nas mierumodel.NetAddrSpec + if err := nas.From(sa); err != nil { + return nas, err + } + nas.Net = network + return nas, nil +} + +func buildMieruClientConfig(options option.MieruOutboundOptions, dialer mieruDialer) (*mieruclient.ClientConfig, error) { + if err := validateMieruOptions(options); err != nil { + return nil, fmt.Errorf("failed to validate mieru options: %w", err) + } + + transportProtocol := mierupb.TransportProtocol_TCP.Enum() + server := &mierupb.ServerEndpoint{} + if options.ServerPort != 0 { + server.PortBindings = append(server.PortBindings, &mierupb.PortBinding{ + Port: proto.Int32(int32(options.ServerPort)), + Protocol: transportProtocol, + }) + } + for _, pr := range options.ServerPortRanges { + server.PortBindings = append(server.PortBindings, &mierupb.PortBinding{ + PortRange: proto.String(pr), + Protocol: transportProtocol, + }) + } + if M.IsDomainName(options.Server) { + server.DomainName = proto.String(options.Server) + } else { + server.IpAddress = proto.String(options.Server) + } + config := &mieruclient.ClientConfig{ + Profile: &mierupb.ClientProfile{ + ProfileName: proto.String("sing-box"), + User: &mierupb.User{ + Name: proto.String(options.UserName), + Password: proto.String(options.Password), + }, + Servers: []*mierupb.ServerEndpoint{server}, + }, + Dialer: dialer, + } + if multiplexing, ok := mierupb.MultiplexingLevel_value[options.Multiplexing]; ok { + config.Profile.Multiplexing = &mierupb.MultiplexingConfig{ + Level: mierupb.MultiplexingLevel(multiplexing).Enum(), + } + } + return config, nil +} + +func validateMieruOptions(options option.MieruOutboundOptions) error { + if options.Server == "" { + return fmt.Errorf("server is empty") + } + if options.ServerPort == 0 && len(options.ServerPortRanges) == 0 { + return fmt.Errorf("either server_port or server_ports must be set") + } + for _, pr := range options.ServerPortRanges { + begin, end, err := beginAndEndPortFromPortRange(pr) + if err != nil { + return fmt.Errorf("invalid server_ports format") + } + if begin < 1 || begin > 65535 { + return fmt.Errorf("begin port must be between 1 and 65535") + } + if end < 1 || end > 65535 { + return fmt.Errorf("end port must be between 1 and 65535") + } + if begin > end { + return fmt.Errorf("begin port must be less than or equal to end port") + } + } + if options.Transport != "TCP" { + return fmt.Errorf("transport must be TCP") + } + if options.UserName == "" { + return fmt.Errorf("username is empty") + } + if options.Password == "" { + return fmt.Errorf("password is empty") + } + if options.Multiplexing != "" { + if _, ok := mierupb.MultiplexingLevel_value[options.Multiplexing]; !ok { + return fmt.Errorf("invalid multiplexing level: %s", options.Multiplexing) + } + } + return nil +} + +func beginAndEndPortFromPortRange(portRange string) (int, int, error) { + var begin, end int + _, err := fmt.Sscanf(portRange, "%d-%d", &begin, &end) + return begin, end, err +} From 6272596ebc74e1f0cfd81f8f5ece70c4d14133ac Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 15 Jun 2025 19:24:09 +0300 Subject: [PATCH 0334/1272] Add unified delay --- box.go | 4 ++++ common/urltest/context.go | 13 +++++++++++++ common/urltest/urltest.go | 9 +++++++++ experimental/libbox/service.go | 4 ++++ option/experimental.go | 13 +++++++++---- 5 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 common/urltest/context.go diff --git a/box.go b/box.go index b9f04c8782..e7813d9540 100644 --- a/box.go +++ b/box.go @@ -15,6 +15,7 @@ import ( "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/cachefile" @@ -114,6 +115,9 @@ func New(options Options) (*Box, error) { if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { needV2RayAPI = true } + if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled { + ctx = urltest.ContextWithIsUnifiedDelay(ctx) + } platformInterface := service.FromContext[platform.Interface](ctx) var defaultLogWriter io.Writer if platformInterface != nil { diff --git a/common/urltest/context.go b/common/urltest/context.go new file mode 100644 index 0000000000..002ff26553 --- /dev/null +++ b/common/urltest/context.go @@ -0,0 +1,13 @@ +package urltest + +import "context" + +type contextKeyIsUnifiedDelay struct{} + +func ContextWithIsUnifiedDelay(ctx context.Context) context.Context { + return context.WithValue(ctx, contextKeyIsUnifiedDelay{}, true) +} + +func IsUnifiedDelayFromContext(ctx context.Context) bool { + return ctx.Value(contextKeyIsUnifiedDelay{}) != nil +} diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index 9efd040420..3ddb747e01 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -122,6 +122,15 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e return } resp.Body.Close() + if IsUnifiedDelayFromContext(ctx) { + second := time.Now() + resp, err = client.Do(req) + if err != nil { + return + } + resp.Body.Close() + start = second + } t = uint16(time.Since(start) / time.Millisecond) return } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 16c04a1f5e..d4ab3b7531 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -69,6 +69,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box cancel() return nil, E.Cause(err, "create service") } + experimentalOptions := common.PtrValueOrDefault(options.Experimental) + if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled { + ctx = urltest.ContextWithIsUnifiedDelay(ctx) + } runtimeDebug.FreeOSMemory() return &BoxService{ ctx: ctx, diff --git a/option/experimental.go b/option/experimental.go index 730740a488..0487881b9b 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -3,10 +3,11 @@ package option import "github.com/sagernet/sing/common/json/badoption" type ExperimentalOptions struct { - CacheFile *CacheFileOptions `json:"cache_file,omitempty"` - ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"` - V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"` - Debug *DebugOptions `json:"debug,omitempty"` + CacheFile *CacheFileOptions `json:"cache_file,omitempty"` + ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"` + V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"` + UnifiedDelay *UnifiedDelayOptions `json:"unified_delay,omitempty"` + Debug *DebugOptions `json:"debug,omitempty"` } type CacheFileOptions struct { @@ -53,3 +54,7 @@ type V2RayStatsServiceOptions struct { Outbounds []string `json:"outbounds,omitempty"` Users []string `json:"users,omitempty"` } + +type UnifiedDelayOptions struct { + Enabled bool `json:"enabled,omitempty"` +} From 6801b58d965b896ebdb05d8c8ecb789fe0183fd1 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 15 Jun 2025 19:32:14 +0300 Subject: [PATCH 0335/1272] Fix interrupt_exist_connections --- common/interrupt/conn.go | 30 ++++++++++++++++++++++++++++++ common/interrupt/group.go | 8 ++++++++ protocol/group/selector.go | 2 ++ protocol/group/urltest.go | 2 ++ 4 files changed, 42 insertions(+) diff --git a/common/interrupt/conn.go b/common/interrupt/conn.go index 6a6d31c68b..b8235d6af4 100644 --- a/common/interrupt/conn.go +++ b/common/interrupt/conn.go @@ -3,6 +3,7 @@ package interrupt import ( "net" + N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" ) @@ -73,3 +74,32 @@ func (c *PacketConn) WriterReplaceable() bool { func (c *PacketConn) Upstream() any { return c.PacketConn } + +type SingPacketConn struct { + N.PacketConn + group *Group + element *list.Element[*groupConnItem] +} + +/*func (c *SingPacketConn) MarkAsInternal() { + c.element.Value.internal = true +}*/ + +func (c *SingPacketConn) Close() error { + c.group.access.Lock() + defer c.group.access.Unlock() + c.group.connections.Remove(c.element) + return c.PacketConn.Close() +} + +func (c *SingPacketConn) ReaderReplaceable() bool { + return true +} + +func (c *SingPacketConn) WriterReplaceable() bool { + return true +} + +func (c *SingPacketConn) Upstream() any { + return c.PacketConn +} diff --git a/common/interrupt/group.go b/common/interrupt/group.go index ba2e7f739b..bd3fbb0a2a 100644 --- a/common/interrupt/group.go +++ b/common/interrupt/group.go @@ -5,6 +5,7 @@ import ( "net" "sync" + N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" ) @@ -36,6 +37,13 @@ func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketCo return &PacketConn{PacketConn: conn, group: g, element: item} } +func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool) N.PacketConn { + g.access.Lock() + defer g.access.Unlock() + item := g.connections.PushBack(&groupConnItem{conn, isExternal}) + return &SingPacketConn{PacketConn: conn, group: g, element: item} +} + func (g *Group) Interrupt(interruptExternalConnections bool) { g.access.Lock() defer g.access.Unlock() diff --git a/protocol/group/selector.go b/protocol/group/selector.go index 9806e03354..23526d1991 100644 --- a/protocol/group/selector.go +++ b/protocol/group/selector.go @@ -157,6 +157,7 @@ func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (n func (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) selected := s.selected.Load() + conn = s.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx)) if outboundHandler, isHandler := selected.(adapter.ConnectionHandlerEx); isHandler { outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose) } else { @@ -167,6 +168,7 @@ func (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata func (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) selected := s.selected.Load() + conn = s.interruptGroup.NewSingPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)) if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandlerEx); isHandler { outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose) } else { diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index c1a5c597e7..5746e0cfcb 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -162,11 +162,13 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne func (s *URLTest) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) + conn = s.group.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx)) s.connection.NewConnection(ctx, s, conn, metadata, onClose) } func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) + conn = s.group.interruptGroup.NewSingPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)) s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose) } From 307c4297154e4b0a66d5f871c6c26a0465a9e184 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 15 Jun 2025 19:40:42 +0300 Subject: [PATCH 0336/1272] Fix WARP endpoint error --- protocol/wireguard/endpoint_warp.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/protocol/wireguard/endpoint_warp.go b/protocol/wireguard/endpoint_warp.go index 9773cd01cc..24e129ccad 100644 --- a/protocol/wireguard/endpoint_warp.go +++ b/protocol/wireguard/endpoint_warp.go @@ -185,31 +185,28 @@ func (w *WARPEndpoint) Start(stage adapter.StartStage) error { } func (w *WARPEndpoint) Close() error { - if err := w.isEndpointInitialized(); err != nil { - return err + if ok := w.isEndpointInitialized(); !ok { + return E.New("endpoint not initialized") } return w.endpoint.Close() } func (w *WARPEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - if err := w.isEndpointInitialized(); err != nil { - return nil, err + if ok := w.isEndpointInitialized(); !ok { + return nil, E.New("endpoint not initialized") } return w.endpoint.DialContext(ctx, network, destination) } func (w *WARPEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - if err := w.isEndpointInitialized(); err != nil { - return nil, err + if ok := w.isEndpointInitialized(); !ok { + return nil, E.New("endpoint not initialized") } return w.endpoint.ListenPacket(ctx, destination) } -func (w *WARPEndpoint) isEndpointInitialized() error { +func (w *WARPEndpoint) isEndpointInitialized() bool { w.mtx.Lock() defer w.mtx.Unlock() - if w.endpoint == nil { - return E.New("endpoint not initialized") - } - return nil + return w.endpoint != nil } From 5d0467378343076db1dd9ed1c9e212c84f37d1ce Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 15 Jun 2025 19:41:22 +0300 Subject: [PATCH 0337/1272] Fix XHTTP Fqdn --- transport/v2rayxhttp/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transport/v2rayxhttp/client.go b/transport/v2rayxhttp/client.go index a0582bb511..5ad3b86d97 100644 --- a/transport/v2rayxhttp/client.go +++ b/transport/v2rayxhttp/client.go @@ -69,7 +69,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt return requestURL } if dest.IsFqdn() { - addresses, err := router.Lookup(ctx, dest.AddrString(), dns.DomainStrategy(options.DomainStrategy)) + addresses, err := router.Lookup(ctx, dest.Fqdn, dns.DomainStrategy(options.DomainStrategy)) if err != nil { return nil, err } @@ -123,7 +123,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt return requestURL2 } if dest2.IsFqdn() { - addresses2, err := router.Lookup(ctx, dest2.AddrString(), dns.DomainStrategy(options2.DomainStrategy)) + addresses2, err := router.Lookup(ctx, dest2.Fqdn, dns.DomainStrategy(options2.DomainStrategy)) if err != nil { return nil, err } From 52b71c6f00de4c73912456104a4b81d4f32c36ab Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 15 Jun 2025 20:47:05 +0300 Subject: [PATCH 0338/1272] Add sdns transport --- go.mod | 15 +++++++++++---- go.sum | 38 ++++++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 6fad77b34f..a497ee9be8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sagernet/sing-box -go 1.24 +go 1.24.1 toolchain go1.24.3 @@ -48,11 +48,11 @@ require ( go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.38.0 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 golang.org/x/mod v0.24.0 golang.org/x/net v0.40.0 golang.org/x/sys v0.33.0 - golang.org/x/time v0.7.0 + golang.org/x/time v0.11.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.72.1 google.golang.org/protobuf v1.36.6 @@ -60,6 +60,9 @@ require ( ) require ( + github.com/AdguardTeam/golibs v0.32.7 // indirect + github.com/ameshkov/dnscrypt/v2 v2.4.0 // indirect + github.com/ameshkov/dnsstamps v1.0.3 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect @@ -109,10 +112,14 @@ require ( github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect ) replace github.com/sagernet/wireguard-go => github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec + +replace github.com/sagernet/sing-dns => github.com/shtorm-7/sing-dns v0.4.5-extended-1.0.0 + +replace github.com/ameshkov/dnscrypt/v2 => github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 diff --git a/go.sum b/go.sum index 42a41636c7..b041508446 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ +github.com/AdguardTeam/golibs v0.32.7 h1:3dmGlAVgmvquCCwHsvEl58KKcRAK3z1UnjMnwSIeDH4= +github.com/AdguardTeam/golibs v0.32.7/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= +github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= @@ -135,8 +139,6 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4= github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.4.5 h1:D9REN14qx2FTrZRBrtFLL99f2CuFzQ9S7mIf8uV5hZI= -github.com/sagernet/sing-dns v0.4.5/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.4.3 h1:OZ/kGvSzjtYg+t0DY3F606hlT5LeiQQXDxfBopcRryQ= @@ -157,6 +159,10 @@ 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/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/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 h1:e5s7RKBd2rIPR0StbvZ2vTVtJ5jDTsTk5wtIIapZTRg= +github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI= +github.com/shtorm-7/sing-dns v0.4.5-extended-1.0.0 h1:iR0BTDEYwuYfQHwiySBhDOUHaipcizGb54OmC1C6Pz0= +github.com/shtorm-7/sing-dns v0.4.5-extended-1.0.0/go.mod h1:HWuvH9L2ZwuffhzX5J5ZOjrhLQ8DB5qT9ie/YTXW1nU= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -193,16 +199,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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +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.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +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.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= @@ -216,8 +222,8 @@ 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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -242,8 +248,8 @@ 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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= @@ -253,8 +259,8 @@ 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/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From ae9e7aa5f4b1ed98d6befce8f1b0c264bddbc271 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 15 Jun 2025 22:21:58 +0300 Subject: [PATCH 0339/1272] Fix Range --- common/xray/json/badoption/range.go | 41 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/common/xray/json/badoption/range.go b/common/xray/json/badoption/range.go index 6390a158ea..645fda82bf 100644 --- a/common/xray/json/badoption/range.go +++ b/common/xray/json/badoption/range.go @@ -24,27 +24,36 @@ func (c *Range) MarshalJSON() ([]byte, error) { } func (c *Range) UnmarshalJSON(content []byte) error { + var rangeValue struct { + From int32 `json:"from"` + To int32 `json:"to"` + } var stringValue string err := json.Unmarshal(content, &stringValue) - if err != nil { - return err - } - parts := strings.Split(stringValue, "-") - if len(parts) != 2 { - return E.New("invalid length of range parts") - } - from, err := strconv.ParseInt(parts[0], 10, 32) - if err != nil { - return err - } - to, err := strconv.ParseInt(parts[1], 10, 32) - if err != nil { - return err + if err == nil { + parts := strings.Split(stringValue, "-") + if len(parts) != 2 { + return E.New("invalid length of range parts") + } + from, err := strconv.ParseInt(parts[0], 10, 32) + if err != nil { + return err + } + to, err := strconv.ParseInt(parts[1], 10, 32) + if err != nil { + return err + } + rangeValue.From, rangeValue.To = int32(from), int32(to) + } else { + err := json.Unmarshal(content, &rangeValue) + if err != nil { + return err + } } - if from > to { + if rangeValue.From > rangeValue.To { return E.New("invalid range") } - *c = Range{int32(from), int32(to)} + *c = Range{rangeValue.From, rangeValue.To} return nil } From 85a1a8a53b1125bc4172d342eeec59788d1748cb Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 15 Jun 2025 22:23:25 +0300 Subject: [PATCH 0340/1272] Format examples --- examples/amnezia/client.json | 99 +++++++++++------------- examples/warp/client.json | 96 ++++++++++++------------ examples/xhttp/client.json | 141 ++++++++++++++++++----------------- examples/xhttp/server.json | 80 ++++++++++---------- 4 files changed, 199 insertions(+), 217 deletions(-) diff --git a/examples/amnezia/client.json b/examples/amnezia/client.json index 797fa0594a..25274befe7 100644 --- a/examples/amnezia/client.json +++ b/examples/amnezia/client.json @@ -1,84 +1,71 @@ { - "log":{ - "level":"error" + "log": { + "level": "error" }, - "dns":{ - "servers":[ + "dns": { + "servers": [ { - "address":"local", - "detour":"direct" + "address": "local", + "detour": "direct" } ] }, - "endpoints":[ + "endpoints": [ { - "type":"wireguard", - "tag":"wireguard-out", - "system":false, - "name":"", - "mtu":1408, - "address":[], - "private_key":"", - "listen_port":10000, - "peers":[ + "type": "wireguard", + "tag": "wireguard-out", + "mtu": 1408, + "address": null, + "private_key": "", + "listen_port": 10000, + "peers": [ { - "address":"example.com", - "port":10001, - "public_key":"", - "pre_shared_key":"", - "allowed_ips":[], - "persistent_keepalive_interval":0, - "reserved":[ - 0, - 0, - 0 - ] + "address": "example.com", + "port": 10001, + "reserved": "AAAA" } ], - "udp_timeout":"", - "workers":0, - "amnezia":{ - "jc":120, - "jmin":23, - "jmax":911, - "s1":0, - "s2":0, - "h1":1, - "h2":2, - "h3":3, - "h4":4 + "udp_timeout": "5m0s", + "amnezia": { + "jc": 120, + "jmin": 23, + "jmax": 911, + "h1": 1, + "h2": 2, + "h3": 3, + "h4": 4 } } ], - "inbounds":[ + "inbounds": [ { - "type":"mixed", - "tag":"mixed-in", - "listen_port":7897 + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 } ], - "outbounds":[ + "outbounds": [ { - "type":"direct", - "tag":"direct" + "type": "direct", + "tag": "direct" }, { - "type":"dns", - "tag":"dns-out" + "type": "dns", + "tag": "dns-out" } ], - "route":{ - "rules":[ + "route": { + "rules": [ { - "protocol":"dns", - "outbound":"dns-out" + "protocol": "dns", + "outbound": "dns-out" }, { - "port":53, - "outbound":"dns-out" + "port": 53, + "outbound": "dns-out" } ], - "final":"wireguard-out", - "auto_detect_interface":true + "final": "wireguard-out", + "auto_detect_interface": true } } \ No newline at end of file diff --git a/examples/warp/client.json b/examples/warp/client.json index c32b0d830d..22e40bda15 100644 --- a/examples/warp/client.json +++ b/examples/warp/client.json @@ -1,79 +1,75 @@ { - "log":{ - "level":"error" + "log": { + "level": "error" }, - "dns":{ - "servers":[ + "dns": { + "servers": [ { - "address":"local", - "detour":"direct" + "address": "local", + "detour": "direct" } ] }, - "endpoints":[ + "endpoints": [ { - "type":"warp", - "tag":"warp-out", - "system":false, - "name":"", - "listen_port":10000, - "udp_timeout":"", - "workers":0, - "profile":{ - "detour":"direct", - "recreate":false, - // for getting existing WARP device profile - "id":"", - "private_key":"", - "auth_token":"" + "type": "warp", + "tag": "warp-out", + "listen_port": 10000, + "udp_timeout": "5m0s", + "amnezia": { + "jc": 120, + "jmin": 23, + "jmax": 911, + "h1": 1, + "h2": 2, + "h3": 3, + "h4": 4 }, - "amnezia":{ - "jc":120, - "jmin":23, - "jmax":911, - "h1":1, - "h2":2, - "h3":3, - "h4":4 + "profile": { + "detour": "direct", + // for getting existing WARP device profile + "id": "", + "private_key": "", + "auth_token": "" } // Dial Fields } ], - "inbounds":[ + "inbounds": [ { - "type":"mixed", - "tag":"mixed-in", - "listen_port":7897 + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 } ], - "outbounds":[ + "outbounds": [ { - "type":"direct", - "tag":"direct" + "type": "direct", + "tag": "direct" }, { - "type":"dns", - "tag":"dns-out" + "type": "dns", + "tag": "dns-out" } ], - "route":{ - "rules":[ + "route": { + "rules": [ { - "protocol":"dns", - "outbound":"dns-out" + "protocol": "dns", + "outbound": "dns-out" }, { - "port":53, - "outbound":"dns-out" + "port": 53, + "outbound": "dns-out" } ], - "final":"warp-out", - "auto_detect_interface":true + "final": "warp-out", + "auto_detect_interface": true }, - "experimental":{ - "cache_file":{ - "enabled":true, - "store_warp_config":true + "experimental": { + "cache_file": { + "enabled": true, + "store_warp_config": true } } } \ No newline at end of file diff --git a/examples/xhttp/client.json b/examples/xhttp/client.json index 0c5fcce013..77590c5264 100644 --- a/examples/xhttp/client.json +++ b/examples/xhttp/client.json @@ -1,97 +1,98 @@ { - "log":{ - "level":"error" + "log": { + "level": "error" }, - "dns":{ - "servers":[ + "dns": { + "servers": [ { - "address":"local", - "detour":"direct" + "address": "local", + "detour": "direct" } ] }, - "inbounds":[ + "inbounds": [ { - "type":"mixed", - "tag":"mixed-in", - "listen_port":7897 + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 } ], - "outbounds":[ + "outbounds": [ { - "type":"direct", - "tag":"direct" + "type": "direct", + "tag": "direct" }, { - "type":"vless", - "tag":"vless-out", - "server":"example.com", - "server_port":443, - "uuid":"3179dce2-2ff9-413c-85b4-c1d53ed41668", - "tls":{ - "enabled":true, - "server_name":"example.com", - "alpn":[ - "h2" // h3 for QUIC - ] + "type": "vless", + "tag": "vless-out", + "server": "example.com", + "server_port": 443, + "uuid": "3179dce2-2ff9-413c-85b4-c1d53ed41668", + "tls": { + "enabled": true, + "server_name": "example.com", + "alpn": "h2" // h3 for QUIC }, - "packet_encoding":"", - "transport":{ - "type":"xhttp", - "mode":"stream-up", - "host":"example.com", - "path":"/xhttp", - "domain_strategy":"prefer_ipv4", - "xmux":{ - "max_concurrency":"0-1", - "max_connections":"0-1", - "c_max_reuse_times":"0-1", - "h_max_request_times":"0-1", - "h_max_reusable_secs":"0-1", - "h_keep_alive_period":60 + "transport": { + "type": "xhttp", + "mode": "stream-up", + "host": "example.com", + "path": "/xhttp", + "domain_strategy": "prefer_ipv4", + "xmux": { + "max_concurrency": "0-1", + "max_connections": "0-1", + "c_max_reuse_times": "0-1", + "h_max_request_times": "0-1", + "h_max_reusable_secs": "0-1", + "h_keep_alive_period": 60 }, - "download":{ - "server":"example.com", - "server_port":443, - "host":"example.com", - "path":"/xhttp", - "domain_strategy":"prefer_ipv4", - "detour":"direct", - "tls":{ - "enabled":true, - "server_name":"example.com", - "alpn":[ - "h2" // h3 for QUIC - ] + "download": { + "mode": "", + "host": "example.com", + "path": "/xhttp", + "domain_strategy": "prefer_ipv4", + "x_padding_bytes": "0-0", + "sc_max_each_post_bytes": "0-0", + "sc_min_posts_interval_ms": "0-0", + "sc_stream_up_server_secs": "0-0", + "xmux": { + "max_concurrency": "0-1", + "max_connections": "0-1", + "c_max_reuse_times": "0-1", + "h_max_request_times": "0-1", + "h_max_reusable_secs": "0-1", + "h_keep_alive_period": 60 }, - "xmux":{ - "max_concurrency":"0-1", - "max_connections":"0-1", - "c_max_reuse_times":"0-1", - "h_max_request_times":"0-1", - "h_max_reusable_secs":"0-1", - "h_keep_alive_period":60 - } + "server": "example.com", + "server_port": 443, + "tls": { + "enabled": true, + "server_name": "example.com", + "alpn": "h2" // h3 for QUIC + }, + "detour": "direct" } - } + }, + "packet_encoding": "" }, { - "type":"dns", - "tag":"dns-out" + "type": "dns", + "tag": "dns-out" } ], - "route":{ - "rules":[ + "route": { + "rules": [ { - "protocol":"dns", - "outbound":"dns-out" + "protocol": "dns", + "outbound": "dns-out" }, { - "port":53, - "outbound":"dns-out" + "port": 53, + "outbound": "dns-out" } ], - "final":"vless-out", - "auto_detect_interface":true + "final": "vless-out", + "auto_detect_interface": true } } \ No newline at end of file diff --git a/examples/xhttp/server.json b/examples/xhttp/server.json index 0df228d37a..5775933db9 100644 --- a/examples/xhttp/server.json +++ b/examples/xhttp/server.json @@ -1,65 +1,63 @@ { - "log":{ - "level":"error" + "log": { + "level": "error" }, - "dns":{ - "servers":[ + "dns": { + "servers": [ { - "address":"local", - "detour":"direct" + "address": "local", + "detour": "direct" } ] }, - "inbounds":[ + "inbounds": [ { - "type":"vless", - "tag":"vless-out", - "listen":"0.0.0.0", - "listen_port":443, - "tls":{ - "enabled":true, - "server_name":"example.com", - "certificate_path":"/path/to/fullchain.pem", - "key_path":"/path/to/privkey.pem", - "alpn":[ - "h2" // h3 for QUIC - ] - }, - "users":[ + "type": "vless", + "tag": "vless-out", + "listen": "0.0.0.0", + "listen_port": 443, + "users": [ { - "name":"user", - "uuid":"3179dce2-2ff9-413c-85b4-c1d53ed41668" + "name": "user", + "uuid": "3179dce2-2ff9-413c-85b4-c1d53ed41668" } ], - "transport":{ - "type":"xhttp", - "mode":"stream-up", - "path":"/xhttp" + "tls": { + "enabled": true, + "server_name": "example.com", + "alpn": "h2", // h3 for QUIC + "certificate_path": "/path/to/fullchain.pem", + "key_path": "/path/to/privkey.pem" + }, + "transport": { + "type": "xhttp", + "mode": "stream-up", + "path": "/xhttp", } } ], - "outbounds":[ + "outbounds": [ { - "type":"direct", - "tag":"direct" + "type": "direct", + "tag": "direct" }, { - "type":"dns", - "tag":"dns-out" + "type": "dns", + "tag": "dns-out" } ], - "route":{ - "rules":[ + "route": { + "rules": [ { - "protocol":"dns", - "outbound":"dns-out" + "protocol": "dns", + "outbound": "dns-out" }, { - "port":53, - "outbound":"dns-out" + "port": 53, + "outbound": "dns-out" } ], - "final":"direct", - "auto_detect_interface":true + "final": "direct", + "auto_detect_interface": true } -} \ No newline at end of file +} From 824eac453b5084a49d1d0c64a09258d6d704c4ec Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 15 Jun 2025 22:23:42 +0300 Subject: [PATCH 0341/1272] Add new examples --- examples/mieru/client.json | 56 +++++++++++++++++++++ examples/sdns/client.json | 44 +++++++++++++++++ examples/unified_delay/client.json | 79 ++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 examples/mieru/client.json create mode 100644 examples/sdns/client.json create mode 100644 examples/unified_delay/client.json diff --git a/examples/mieru/client.json b/examples/mieru/client.json new file mode 100644 index 0000000000..ca7b11c8d6 --- /dev/null +++ b/examples/mieru/client.json @@ -0,0 +1,56 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct" + }, + { + "type": "mieru", + "tag": "mieru-out", + "server": "example.com", + "server_port": 27017, + "server_ports": "27017-27019", + "transport": "TCP", + "username": "username", + "password": "password", + "multiplexing": "MULTIPLEXING_LOW" + // Dial Fields + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + } + ], + "final": "mieru-out", + "auto_detect_interface": true + } +} diff --git a/examples/sdns/client.json b/examples/sdns/client.json new file mode 100644 index 0000000000..585e9f1b7e --- /dev/null +++ b/examples/sdns/client.json @@ -0,0 +1,44 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "sdns://AQMAAAAAAAAAETk0LjE0MC4xNS4xNTo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", + "detour": "direct" + } + ] + }, + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + } + ], + "final": "direct", + "auto_detect_interface": true + } +} diff --git a/examples/unified_delay/client.json b/examples/unified_delay/client.json new file mode 100644 index 0000000000..357527932e --- /dev/null +++ b/examples/unified_delay/client.json @@ -0,0 +1,79 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "warp", + "tag": "warp-out", + "listen_port": 10000, + "udp_timeout": "5m0s", + "amnezia": { + "jc": 120, + "jmin": 23, + "jmax": 911, + "h1": 1, + "h2": 2, + "h3": 3, + "h4": 4 + }, + "profile": { + "detour": "direct" + } + } + ], + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct" + }, + { + "type": "selector", + "tag": "select-out", + "outbounds": [ + "direct", + "warp-out" + ], + "default": "direct", + "interrupt_exist_connections": true + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + } + ], + "final": "direct", + "auto_detect_interface": true + }, + "experimental": { + "unified_delay": { + "enabled": true + } + } +} \ No newline at end of file From 2d1b824b62d622b2d40478259be98d0b33c7e2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 14 Jun 2025 16:11:15 +0800 Subject: [PATCH 0342/1272] Fix gLazyConn race --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f44affaed5..fca179a44d 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,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.0 - github.com/sagernet/sing-tun v0.6.5 + github.com/sagernet/sing-tun v0.6.8 github.com/sagernet/sing-vmess v0.2.3 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 8d01ac3ba8..55f165d435 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,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.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= -github.com/sagernet/sing-tun v0.6.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffssg= -github.com/sagernet/sing-tun v0.6.5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-tun v0.6.8 h1:tr+LKHe09C2I9GfNuB2vnzaZm+ekoNlAhLLrdiLjtAA= +github.com/sagernet/sing-tun v0.6.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44= github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= From 47f18e823aede34f08e0365935d92740261937e3 Mon Sep 17 00:00:00 2001 From: dyhkwong <50692134+dyhkwong@users.noreply.github.com> Date: Wed, 18 Jun 2025 08:34:59 +0800 Subject: [PATCH 0343/1272] Fix: macOS udp find process should use unspecified fallback https://github.com/Dreamacro/clash/commit/be8d63ba8f70a6f4e7b240899cd2903a77243c24 --- common/process/searcher_darwin.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/common/process/searcher_darwin.go b/common/process/searcher_darwin.go index 4344c7061b..16e2c87a53 100644 --- a/common/process/searcher_darwin.go +++ b/common/process/searcher_darwin.go @@ -76,6 +76,8 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) { // rup8(sizeof(xtcpcb_n)) itemSize += 208 } + + var fallbackUDPProcess string // skip the first xinpgen(24 bytes) block for i := 24; i+itemSize <= len(buf); i += itemSize { // offset of xinpcb_n and xsocket_n @@ -90,10 +92,12 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) { flag := buf[inp+44] var srcIP netip.Addr + srcIsIPv4 := false switch { case flag&0x1 > 0 && isIPv4: // ipv4 srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80])) + srcIsIPv4 = true case flag&0x2 > 0 && !isIPv4: // ipv6 srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80])) @@ -101,13 +105,21 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) { continue } - if ip != srcIP { - continue + if ip == srcIP { + // xsocket_n.so_last_pid + pid := readNativeUint32(buf[so+68 : so+72]) + return getExecPathFromPID(pid) } - // xsocket_n.so_last_pid - pid := readNativeUint32(buf[so+68 : so+72]) - return getExecPathFromPID(pid) + // udp packet connection may be not equal with srcIP + if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 { + pid := readNativeUint32(buf[so+68 : so+72]) + fallbackUDPProcess, _ = getExecPathFromPID(pid) + } + } + + if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 { + return fallbackUDPProcess, nil } return "", ErrNotFound From 9b8ab3e61e5b3a199fce5575bc6a04373f222d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 18 Jun 2025 21:39:12 +0800 Subject: [PATCH 0344/1272] Bump version --- clients/android | 2 +- docs/changelog.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 320170a107..eb2e13a6f9 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 320170a1077ea5c93872b3e055b96b8836615ef0 +Subproject commit eb2e13a6f9a8c03a35ae672395ccab0a6bdcd954 diff --git a/docs/changelog.md b/docs/changelog.md index 71f261c9d7..8f995cfd1f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,13 @@ icon: material/alert-decagram --- +### 1.11.14 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + ### 1.11.13 * Fixes and improvements From cba0e46aba1265f1aaa8e9a7acd987c27a89ef22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Jun 2025 13:12:49 +0800 Subject: [PATCH 0345/1272] Fix log for rejected connections --- go.mod | 2 +- go.sum | 4 ++-- route/dns.go | 5 ++--- route/route.go | 40 +++++++++++++++++++-------------------- route/route_dns.go | 4 ++-- route/rule/rule_action.go | 24 ++++++++++++++++++++--- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index fca179a44d..5ebdd34cb2 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,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.0 - github.com/sagernet/sing-tun v0.6.8 + github.com/sagernet/sing-tun v0.6.9 github.com/sagernet/sing-vmess v0.2.3 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/utls v1.6.7 diff --git a/go.sum b/go.sum index 55f165d435..55b2f9802c 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,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.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= -github.com/sagernet/sing-tun v0.6.8 h1:tr+LKHe09C2I9GfNuB2vnzaZm+ekoNlAhLLrdiLjtAA= -github.com/sagernet/sing-tun v0.6.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-tun v0.6.9 h1:uP8O4Q7U9QesjWumgxd2S9fjT3c6aEPWl5RB6uBdVB8= +github.com/sagernet/sing-tun v0.6.9/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44= github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= diff --git a/route/dns.go b/route/dns.go index 8d57c64675..6545ce04e7 100644 --- a/route/dns.go +++ b/route/dns.go @@ -2,15 +2,14 @@ package route import ( "context" - "errors" "net" "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" dnsOutbound "github.com/sagernet/sing-box/protocol/dns" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -58,7 +57,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) { err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination) - if err != nil && !errors.Is(err, tun.ErrDrop) && !E.IsClosedOrCanceled(err) { + if err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) { router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) } } diff --git a/route/route.go b/route/route.go index d9bf263821..b4c00444ba 100644 --- a/route/route.go +++ b/route/route.go @@ -16,7 +16,7 @@ import ( "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/route/rule" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" "github.com/sagernet/sing-mux" "github.com/sagernet/sing-vmess" @@ -51,7 +51,7 @@ func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata err := r.routeConnection(ctx, conn, metadata, onClose) if err != nil { N.CloseOnHandshakeFailure(conn, onClose, err) - if E.IsClosedOrCanceled(err) { + if E.IsClosedOrCanceled(err) || R.IsRejected(err) { r.logger.DebugContext(ctx, "connection closed: ", err) } else { r.logger.ErrorContext(ctx, err) @@ -101,7 +101,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad var selectedOutbound adapter.Outbound if selectedRule != nil { switch action := selectedRule.Action().(type) { - case *rule.RuleActionRoute: + case *R.RuleActionRoute: var loaded bool selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) if !loaded { @@ -112,11 +112,11 @@ 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 *rule.RuleActionReject: + case *R.RuleActionReject: buf.ReleaseMulti(buffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) return nil - case *rule.RuleActionHijackDNS: + case *R.RuleActionHijackDNS: for _, buffer := range buffers { conn = bufio.NewCachedConn(conn, buffer) } @@ -154,7 +154,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m })) if err != nil { conn.Close() - if E.IsClosedOrCanceled(err) { + if E.IsClosedOrCanceled(err) || R.IsRejected(err) { r.logger.DebugContext(ctx, "connection closed: ", err) } else { r.logger.ErrorContext(ctx, err) @@ -171,7 +171,7 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, err := r.routePacketConnection(ctx, conn, metadata, onClose) if err != nil { N.CloseOnHandshakeFailure(conn, onClose, err) - if E.IsClosedOrCanceled(err) { + if E.IsClosedOrCanceled(err) || R.IsRejected(err) { r.logger.DebugContext(ctx, "connection closed: ", err) } else { r.logger.ErrorContext(ctx, err) @@ -217,7 +217,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m var selectReturn bool if selectedRule != nil { switch action := selectedRule.Action().(type) { - case *rule.RuleActionRoute: + case *R.RuleActionRoute: var loaded bool selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) if !loaded { @@ -228,11 +228,11 @@ 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 *rule.RuleActionReject: + case *R.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx)) return nil - case *rule.RuleActionHijackDNS: + case *R.RuleActionHijackDNS: r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose) return nil } @@ -271,7 +271,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error { if selectedRule == nil { return nil } - rejectAction, isReject := selectedRule.Action().(*rule.RuleActionReject) + rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject) if !isReject { return nil } @@ -346,7 +346,7 @@ func (r *Router) matchRule( //nolint:staticcheck if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() { if !preMatch && metadata.InboundOptions.SniffEnabled { - newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ + newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{ OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), }, inputConn, inputPacketConn, nil) @@ -361,7 +361,7 @@ func (r *Router) matchRule( } } if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { - fatalErr = r.actionResolve(ctx, metadata, &rule.RuleActionResolve{ + fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{ Strategy: dns.DomainStrategy(metadata.InboundOptions.DomainStrategy), }) if fatalErr != nil { @@ -398,11 +398,11 @@ match: } } } - var routeOptions *rule.RuleActionRouteOptions + var routeOptions *R.RuleActionRouteOptions switch action := currentRule.Action().(type) { - case *rule.RuleActionRoute: + case *R.RuleActionRoute: routeOptions = &action.RuleActionRouteOptions - case *rule.RuleActionRouteOptions: + case *R.RuleActionRouteOptions: routeOptions = action } if routeOptions != nil { @@ -448,7 +448,7 @@ match: } } switch action := currentRule.Action().(type) { - case *rule.RuleActionSniff: + case *R.RuleActionSniff: if !preMatch { newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers) if newErr != nil { @@ -465,7 +465,7 @@ match: selectedRuleIndex = currentRuleIndex break match } - case *rule.RuleActionResolve: + case *R.RuleActionResolve: fatalErr = r.actionResolve(ctx, metadata, action) if fatalErr != nil { return @@ -485,7 +485,7 @@ match: } func (r *Router) actionSniff( - ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff, + ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff, inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, ) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) { if sniff.Skip(metadata) { @@ -645,7 +645,7 @@ func (r *Router) actionSniff( return } -func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error { +func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionResolve) error { if metadata.Destination.IsFqdn() { metadata.DNSServer = action.Server addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy) diff --git a/route/route_dns.go b/route/route_dns.go index 0e22cdfa89..c5aced0992 100644 --- a/route/route_dns.go +++ b/route/route_dns.go @@ -170,7 +170,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er Question: []mDNS.Question{message.Question[0]}, }, nil case C.RuleActionRejectMethodDrop: - return nil, tun.ErrDrop + return nil, &R.RejectedError{Cause: tun.ErrDrop} } } } @@ -289,7 +289,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS case C.RuleActionRejectMethodDefault: return nil, nil case C.RuleActionRejectMethodDrop: - return nil, tun.ErrDrop + return nil, &R.RejectedError{Cause: tun.ErrDrop} } } } diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index d4c6625d29..ef71e232cb 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -2,6 +2,7 @@ package rule import ( "context" + "errors" "net/netip" "strings" "sync" @@ -250,6 +251,23 @@ func (r *RuleActionDirect) String() string { return "direct" + r.description } +type RejectedError struct { + Cause error +} + +func (r *RejectedError) Error() string { + return "rejected" +} + +func (r *RejectedError) Unwrap() error { + return r.Cause +} + +func IsRejected(err error) bool { + var rejected *RejectedError + return errors.As(err, &rejected) +} + type RuleActionReject struct { Method string NoDrop bool @@ -273,9 +291,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error { var returnErr error switch r.Method { case C.RuleActionRejectMethodDefault: - returnErr = syscall.ECONNREFUSED + returnErr = &RejectedError{syscall.ECONNREFUSED} case C.RuleActionRejectMethodDrop: - return tun.ErrDrop + return &RejectedError{tun.ErrDrop} default: panic(F.ToString("unknown reject method: ", r.Method)) } @@ -293,7 +311,7 @@ func (r *RuleActionReject) Error(ctx context.Context) error { if ctx != nil { r.logger.DebugContext(ctx, "dropped due to flooding") } - return tun.ErrDrop + return &RejectedError{tun.ErrDrop} } return returnErr } From 7d3ee29bd02c57e5b69053d21a8178f3ca7bf23f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 21 Jun 2025 12:57:09 +0800 Subject: [PATCH 0346/1272] Also skip duplicate sniff for TCP --- adapter/inbound.go | 10 +++++----- route/route.go | 12 ++++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index 173dd0ee92..3e93120b8d 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -53,11 +53,11 @@ type InboundContext struct { // sniffer - Protocol string - Domain string - Client string - SniffContext any - PacketSniffError error + Protocol string + Domain string + Client string + SniffContext any + SniffError error // cache diff --git a/route/route.go b/route/route.go index b4c00444ba..f50950b46e 100644 --- a/route/route.go +++ b/route/route.go @@ -498,6 +498,9 @@ func (r *Router) actionSniff( if inputConn != nil { if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 { return + } else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) { + r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError) + return } var streamSniffers []sniff.StreamSniffer if len(action.StreamSniffers) > 0 { @@ -522,6 +525,7 @@ func (r *Router) actionSniff( action.Timeout, streamSniffers..., ) + metadata.SniffError = err if err == nil { //goland:noinspection GoDeprecation if action.OverrideDestination && M.IsDomainName(metadata.Domain) { @@ -546,8 +550,8 @@ func (r *Router) actionSniff( } else if inputPacketConn != nil { if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 { return - } else if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) { - r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError) + } else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) { + r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError) return } var packetSniffers []sniff.PacketSniffer @@ -594,7 +598,7 @@ func (r *Router) actionSniff( return } } else { - if len(packetBuffers) > 0 || metadata.PacketSniffError != nil { + if len(packetBuffers) > 0 || metadata.SniffError != nil { err = sniff.PeekPacket( ctx, metadata, @@ -614,7 +618,7 @@ func (r *Router) actionSniff( Destination: destination, } packetBuffers = append(packetBuffers, packetBuffer) - metadata.PacketSniffError = err + metadata.SniffError = err if errors.Is(err, sniff.ErrNeedMoreData) { // TODO: replace with generic message when there are more multi-packet protocols r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") From 2cf989d3064db551e70b227b38039ae74486c2fb Mon Sep 17 00:00:00 2001 From: dyhkwong <50692134+dyhkwong@users.noreply.github.com> Date: Wed, 25 Jun 2025 13:20:00 +0800 Subject: [PATCH 0347/1272] Fix inbound with v2ray transport missing InboundOptions --- protocol/vless/inbound.go | 4 ++++ protocol/vmess/inbound.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/protocol/vless/inbound.go b/protocol/vless/inbound.go index a977f73952..f9e505a860 100644 --- a/protocol/vless/inbound.go +++ b/protocol/vless/inbound.go @@ -205,6 +205,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net. var metadata adapter.InboundContext metadata.Source = source metadata.Destination = destination + //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 0b8c96c697..706beb17bd 100644 --- a/protocol/vmess/inbound.go +++ b/protocol/vmess/inbound.go @@ -219,6 +219,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net. var metadata adapter.InboundContext metadata.Source = source metadata.Destination = destination + //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) } From 832eb4458d53c4b17b07e1844ea9fb46c4e45970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 23 Jun 2025 20:48:37 +0800 Subject: [PATCH 0348/1272] release: Fix xcode version --- .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 025fe84530..921929fe23 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -468,11 +468,11 @@ jobs: - name: Setup Xcode stable if: matrix.if && github.ref == 'refs/heads/main-next' run: |- - sudo xcode-select -s /Applications/Xcode_16.2.app + sudo xcode-select -s /Applications/Xcode_16.4.app - name: Setup Xcode beta if: matrix.if && github.ref == 'refs/heads/dev-next' run: |- - sudo xcode-select -s /Applications/Xcode_16.2.app + sudo xcode-select -s /Applications/Xcode_16.4.app - name: Set tag if: matrix.if run: |- From 4dbbf59c82e887613face99bf8480db2427b9625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 29 Jun 2025 18:44:11 +0800 Subject: [PATCH 0349/1272] Fix logger for acme --- Makefile | 2 +- common/tls/acme.go | 49 +++++++++++++++++++++++++++++++++------- common/tls/acme_stub.go | 3 ++- common/tls/std_server.go | 2 +- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index ea63367378..0561fa3c65 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls +TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_acme TAGS_GO121 = with_ech TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121) TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server diff --git a/common/tls/acme.go b/common/tls/acme.go index 08b24ed22c..52172c9cf6 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -5,13 +5,13 @@ package tls import ( "context" "crypto/tls" - "os" "strings" "github.com/sagernet/sing-box/adapter" 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/caddyserver/certmagic" "github.com/libdns/alidns" @@ -37,7 +37,38 @@ func (w *acmeWrapper) Close() error { return nil } -func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) { +type acmeLogWriter struct { + logger logger.Logger +} + +func (w *acmeLogWriter) Write(p []byte) (n int, err error) { + logLine := strings.ReplaceAll(string(p), " ", ": ") + switch { + case strings.HasPrefix(logLine, "error: "): + w.logger.Error(logLine[7:]) + case strings.HasPrefix(logLine, "warn: "): + w.logger.Warn(logLine[6:]) + case strings.HasPrefix(logLine, "info: "): + w.logger.Info(logLine[6:]) + case strings.HasPrefix(logLine, "debug: "): + w.logger.Debug(logLine[7:]) + default: + w.logger.Debug(logLine) + } + return len(p), nil +} + +func (w *acmeLogWriter) Sync() error { + return nil +} + +func encoderConfig() zapcore.EncoderConfig { + config := zap.NewProductionEncoderConfig() + config.TimeKey = zapcore.OmitKey + return config +} + +func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) { var acmeServer string switch options.Provider { case "", "letsencrypt": @@ -58,14 +89,15 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con } else { storage = certmagic.Default.Storage } + zapLogger := zap.New(zapcore.NewCore( + zapcore.NewConsoleEncoder(encoderConfig()), + &acmeLogWriter{logger: logger}, + zap.DebugLevel, + )) config := &certmagic.Config{ DefaultServerName: options.DefaultServerName, Storage: storage, - Logger: zap.New(zapcore.NewCore( - zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()), - os.Stderr, - zap.InfoLevel, - )), + Logger: zapLogger, } acmeConfig := certmagic.ACMEIssuer{ CA: acmeServer, @@ -75,7 +107,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con DisableTLSALPNChallenge: options.DisableTLSALPNChallenge, AltHTTPPort: int(options.AlternativeHTTPPort), AltTLSALPNPort: int(options.AlternativeTLSPort), - Logger: config.Logger, + Logger: zapLogger, } if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" { var solver certmagic.DNS01Solver @@ -103,6 +135,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) { return config, nil }, + Logger: zapLogger, }) config = certmagic.New(cache, *config) var tlsConfig *tls.Config diff --git a/common/tls/acme_stub.go b/common/tls/acme_stub.go index d97d05409d..f32f9e8ddf 100644 --- a/common/tls/acme_stub.go +++ b/common/tls/acme_stub.go @@ -9,8 +9,9 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" ) -func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) { +func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) { return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`) } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 949521d7dc..27986003bb 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -157,7 +157,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound var err error if options.ACME != nil && len(options.ACME.Domain) > 0 { //nolint:staticcheck - tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME)) + tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME)) if err != nil { return nil, err } From 36db31c55a7b5b9969c298ba7bae2cd713d29832 Mon Sep 17 00:00:00 2001 From: Kyson Date: Sun, 29 Jun 2025 16:42:28 +0800 Subject: [PATCH 0350/1272] documentation: Fix typo Co-authored-by: chenqixin --- docs/configuration/experimental/clash-api.md | 2 +- docs/configuration/experimental/clash-api.zh.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/experimental/clash-api.md b/docs/configuration/experimental/clash-api.md index 7425143eb3..d471ea876d 100644 --- a/docs/configuration/experimental/clash-api.md +++ b/docs/configuration/experimental/clash-api.md @@ -63,7 +63,7 @@ icon: material/new-box { "external_controller": "0.0.0.0:9090", "external_ui": "dashboard" - // external_ui_download_detour: "direct" + // "external_ui_download_detour": "direct" } ``` diff --git a/docs/configuration/experimental/clash-api.zh.md b/docs/configuration/experimental/clash-api.zh.md index b3d8aeaf99..8193cd8781 100644 --- a/docs/configuration/experimental/clash-api.zh.md +++ b/docs/configuration/experimental/clash-api.zh.md @@ -63,7 +63,7 @@ icon: material/new-box { "external_controller": "0.0.0.0:9090", "external_ui": "dashboard" - // external_ui_download_detour: "direct" + // "external_ui_download_detour": "direct" } ``` From 6f804adf390476c7c97cce3c5d735d2f310fd359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 3 Jul 2025 21:48:10 +0800 Subject: [PATCH 0351/1272] Fix v2rayhttp crash --- transport/v2rayhttp/conn.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/transport/v2rayhttp/conn.go b/transport/v2rayhttp/conn.go index e066503d7c..b339a75309 100644 --- a/transport/v2rayhttp/conn.go +++ b/transport/v2rayhttp/conn.go @@ -31,6 +31,9 @@ type HTTPConn struct { } func NewHTTP1Conn(conn net.Conn, request *http.Request) *HTTPConn { + if request.Header.Get("Host") == "" { + request.Header.Set("Host", request.Host) + } return &HTTPConn{ Conn: conn, request: request, @@ -89,9 +92,6 @@ func (c *HTTPConn) writeRequest(payload []byte) error { if err != nil { return err } - if c.request.Header.Get("Host") == "" { - c.request.Header.Set("Host", c.request.Host) - } for key, value := range c.request.Header { _, err = writer.Write([]byte(F.ToString(key, ": ", strings.Join(value, ", "), CRLF))) if err != nil { From 209b89a4a32e7b49213e3c6735214549c4674592 Mon Sep 17 00:00:00 2001 From: Sergei Maklagin Date: Sun, 6 Jul 2025 18:31:06 +0300 Subject: [PATCH 0352/1272] Add tunnel --- adapter/inbound.go | 18 +- constant/proxy.go | 6 + examples/tunnel/client->server/client.json | 64 ++++++ examples/tunnel/client->server/server.json | 62 ++++++ .../client1->server->client2/client1.json | 64 ++++++ .../client1->server->client2/client2.json | 53 +++++ .../client1->server->client2/server.json | 77 +++++++ .../proxy_client.json | 52 +++++ .../server.json | 67 ++++++ .../tunnel_client.json | 53 +++++ examples/tunnel/server->client/client.json | 53 +++++ examples/tunnel/server->client/server.json | 73 +++++++ include/registry.go | 4 + option/rule.go | 2 + option/rule_action.go | 5 +- option/rule_dns.go | 2 + option/rule_set.go | 2 + option/tunnel.go | 21 ++ protocol/tunnel/client.go | 151 +++++++++++++ protocol/tunnel/protocol.go | 91 ++++++++ protocol/tunnel/router.go | 41 ++++ protocol/tunnel/server.go | 203 ++++++++++++++++++ route/route.go | 3 + route/rule/rule_action.go | 5 + route/rule/rule_default.go | 10 + route/rule/rule_dns.go | 10 + route/rule/rule_headless.go | 10 + route/rule/rule_item_tunnel_destination.go | 35 +++ route/rule/rule_item_tunnel_source.go | 35 +++ 29 files changed, 1262 insertions(+), 10 deletions(-) create mode 100644 examples/tunnel/client->server/client.json create mode 100644 examples/tunnel/client->server/server.json create mode 100644 examples/tunnel/client1->server->client2/client1.json create mode 100644 examples/tunnel/client1->server->client2/client2.json create mode 100644 examples/tunnel/client1->server->client2/server.json create mode 100644 examples/tunnel/proxy_client->server->tunnel_client/proxy_client.json create mode 100644 examples/tunnel/proxy_client->server->tunnel_client/server.json create mode 100644 examples/tunnel/proxy_client->server->tunnel_client/tunnel_client.json create mode 100644 examples/tunnel/server->client/client.json create mode 100644 examples/tunnel/server->client/server.json create mode 100644 option/tunnel.go create mode 100644 protocol/tunnel/client.go create mode 100644 protocol/tunnel/protocol.go create mode 100644 protocol/tunnel/router.go create mode 100644 protocol/tunnel/server.go create mode 100644 route/rule/rule_item_tunnel_destination.go create mode 100644 route/rule/rule_item_tunnel_source.go diff --git a/adapter/inbound.go b/adapter/inbound.go index 173dd0ee92..2ba8205ccf 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -42,14 +42,16 @@ type InboundManager interface { } type InboundContext struct { - Inbound string - InboundType string - IPVersion uint8 - Network string - Source M.Socksaddr - Destination M.Socksaddr - User string - Outbound string + Inbound string + InboundType string + IPVersion uint8 + Network string + Source M.Socksaddr + Destination M.Socksaddr + TunnelSource string + TunnelDestination string + User string + Outbound string // sniffer diff --git a/constant/proxy.go b/constant/proxy.go index 8708604a83..d85e5c089f 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -25,6 +25,8 @@ const ( TypeVLESS = "vless" TypeTUIC = "tuic" TypeHysteria2 = "hysteria2" + TypeTunnelClient = "tunnel_client" + TypeTunnelServer = "tunnel_server" ) const ( @@ -86,6 +88,10 @@ func ProxyDisplayName(proxyType string) string { return "Selector" case TypeURLTest: return "URLTest" + case TypeTunnelClient: + return "Tunnel Client" + case TypeTunnelServer: + return "Tunnel Server" default: return "Unknown" } diff --git a/examples/tunnel/client->server/client.json b/examples/tunnel/client->server/client.json new file mode 100644 index 0000000000..b81c85c897 --- /dev/null +++ b/examples/tunnel/client->server/client.json @@ -0,0 +1,64 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "tunnel_client", + "tag": "tunnel", + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe", + "outbound": { + "type": "vless", + "tag": "vless-out", + "server": "0.0.0.0", + "server_port": 8000, + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "network": "tcp" + } + } + ], + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + }, + { + "outbound": "tunnel", + "override_tunnel_destination": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/tunnel/client->server/server.json b/examples/tunnel/client->server/server.json new file mode 100644 index 0000000000..5d3c7d5176 --- /dev/null +++ b/examples/tunnel/client->server/server.json @@ -0,0 +1,62 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "tunnel_server", + "tag": "tunnel", + "uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13", + "users": [ + { + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe" + } + ], + "inbound": { + "type": "vless", + "tag": "vless-in", + "listen": "0.0.0.0", + "listen_port": 8000, + "users": [ + { + "name": "vless", + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937" + } + ] + } + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/tunnel/client1->server->client2/client1.json b/examples/tunnel/client1->server->client2/client1.json new file mode 100644 index 0000000000..afc06229e9 --- /dev/null +++ b/examples/tunnel/client1->server->client2/client1.json @@ -0,0 +1,64 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "tunnel_client", + "tag": "tunnel", + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe", + "outbound": { + "type": "vless", + "tag": "vless-out", + "server": "0.0.0.0", + "server_port": 8000, + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "network": "tcp" + } + } + ], + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + }, + { + "outbound": "tunnel", + "override_tunnel_destination": "487f6073-3300-4819-a07d-39652e45fb4d" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/tunnel/client1->server->client2/client2.json b/examples/tunnel/client1->server->client2/client2.json new file mode 100644 index 0000000000..18830c4dec --- /dev/null +++ b/examples/tunnel/client1->server->client2/client2.json @@ -0,0 +1,53 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "tunnel_client", + "tag": "tunnel", + "uuid": "487f6073-3300-4819-a07d-39652e45fb4d", + "key": "3d74d616-2502-4c17-9cc3-92c366550f4f", + "outbound": { + "type": "vless", + "tag": "vless-out", + "server": "0.0.0.0", + "server_port": 8000, + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "network": "tcp" + } + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/tunnel/client1->server->client2/server.json b/examples/tunnel/client1->server->client2/server.json new file mode 100644 index 0000000000..2167ffe35d --- /dev/null +++ b/examples/tunnel/client1->server->client2/server.json @@ -0,0 +1,77 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "tunnel_server", + "tag": "tunnel", + "uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13", + "users": [ + { + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe" + }, + { + "uuid": "487f6073-3300-4819-a07d-39652e45fb4d", + "key": "3d74d616-2502-4c17-9cc3-92c366550f4f" + } + ], + "inbound": { + "type": "vless", + "tag": "vless-in", + "listen": "0.0.0.0", + "listen_port": 8000, + "users": [ + { + "name": "vless", + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937" + } + ] + } + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + }, + { + "tunnel_source": [ + "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "487f6073-3300-4819-a07d-39652e45fb4d" + ], + "tunnel_destination": [ + "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "487f6073-3300-4819-a07d-39652e45fb4d" + ], + "outbound": "tunnel" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/tunnel/proxy_client->server->tunnel_client/proxy_client.json b/examples/tunnel/proxy_client->server->tunnel_client/proxy_client.json new file mode 100644 index 0000000000..7cf461de51 --- /dev/null +++ b/examples/tunnel/proxy_client->server->tunnel_client/proxy_client.json @@ -0,0 +1,52 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "vless", + "tag": "vless-out", + "server": "0.0.0.0", + "server_port": 8000, + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "network": "tcp" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + } + ], + "final": "vless-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/tunnel/proxy_client->server->tunnel_client/server.json b/examples/tunnel/proxy_client->server->tunnel_client/server.json new file mode 100644 index 0000000000..e24d3c7468 --- /dev/null +++ b/examples/tunnel/proxy_client->server->tunnel_client/server.json @@ -0,0 +1,67 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "tunnel_server", + "tag": "tunnel", + "uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13", + "users": [ + { + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe" + } + ], + "inbound": { + "type": "vless", + "tag": "vless-in", + "listen": "0.0.0.0", + "listen_port": 8000, + "users": [ + { + "name": "vless", + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937" + } + ] + } + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + }, + { + "inbound": "vless-in", + "outbound": "tunnel", + "override_tunnel_destination": "9b65b7e1-04c8-4717-8f45-2aa61fd25937" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/tunnel/proxy_client->server->tunnel_client/tunnel_client.json b/examples/tunnel/proxy_client->server->tunnel_client/tunnel_client.json new file mode 100644 index 0000000000..aebee2e458 --- /dev/null +++ b/examples/tunnel/proxy_client->server->tunnel_client/tunnel_client.json @@ -0,0 +1,53 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "tunnel_client", + "tag": "tunnel", + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe", + "outbound": { + "type": "vless", + "tag": "vless-out", + "server": "0.0.0.0", + "server_port": 8000, + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "network": "tcp" + } + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/tunnel/server->client/client.json b/examples/tunnel/server->client/client.json new file mode 100644 index 0000000000..aebee2e458 --- /dev/null +++ b/examples/tunnel/server->client/client.json @@ -0,0 +1,53 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "tunnel_client", + "tag": "tunnel", + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe", + "outbound": { + "type": "vless", + "tag": "vless-out", + "server": "0.0.0.0", + "server_port": 8000, + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "network": "tcp" + } + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/tunnel/server->client/server.json b/examples/tunnel/server->client/server.json new file mode 100644 index 0000000000..c224b9dfb4 --- /dev/null +++ b/examples/tunnel/server->client/server.json @@ -0,0 +1,73 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "address": "local", + "detour": "direct" + } + ] + }, + "endpoints": [ + { + "type": "tunnel_server", + "tag": "tunnel", + "uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13", + "users": [ + { + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe" + } + ], + "inbound": { + "type": "vless", + "tag": "vless-in", + "listen": "0.0.0.0", + "listen_port": 8000, + "users": [ + { + "name": "vless", + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937" + } + ] + } + } + ], + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "port": 53, + "outbound": "dns-out" + }, + { + "outbound": "tunnel", + "override_tunnel_destination": "9b65b7e1-04c8-4717-8f45-2aa61fd25937" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/include/registry.go b/include/registry.go index f4b8b61d82..eaa2598d92 100644 --- a/include/registry.go +++ b/include/registry.go @@ -26,6 +26,7 @@ import ( "github.com/sagernet/sing-box/protocol/tor" "github.com/sagernet/sing-box/protocol/trojan" "github.com/sagernet/sing-box/protocol/tun" + "github.com/sagernet/sing-box/protocol/tunnel" "github.com/sagernet/sing-box/protocol/vless" "github.com/sagernet/sing-box/protocol/vmess" E "github.com/sagernet/sing/common/exceptions" @@ -88,6 +89,9 @@ func OutboundRegistry() *outbound.Registry { func EndpointRegistry() *endpoint.Registry { registry := endpoint.NewRegistry() + tunnel.RegisterServerEndpoint(registry) + tunnel.RegisterClientEndpoint(registry) + registerWireGuardEndpoint(registry) return registry diff --git a/option/rule.go b/option/rule.go index b769dab831..a596295616 100644 --- a/option/rule.go +++ b/option/rule.go @@ -88,6 +88,8 @@ type RawDefaultRule struct { SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"` PortRange badoption.Listable[string] `json:"port_range,omitempty"` + TunnelSource badoption.Listable[string] `json:"tunnel_source,omitempty"` + TunnelDestination badoption.Listable[string] `json:"tunnel_destination,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"` diff --git a/option/rule_action.go b/option/rule_action.go index 35f334d63d..38a7d02655 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -142,8 +142,9 @@ type RouteActionOptions struct { } type RawRouteOptionsActionOptions struct { - OverrideAddress string `json:"override_address,omitempty"` - OverridePort uint16 `json:"override_port,omitempty"` + OverrideAddress string `json:"override_address,omitempty"` + OverridePort uint16 `json:"override_port,omitempty"` + OverrideTunnelDestination string `json:"override_tunnel_destination,omitempty"` NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` FallbackDelay uint32 `json:"fallback_delay,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index b437eb54d3..cdfc3afb50 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -89,6 +89,8 @@ type RawDefaultDNSRule struct { SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"` PortRange badoption.Listable[string] `json:"port_range,omitempty"` + TunnelSource badoption.Listable[string] `json:"tunnel_source,omitempty"` + TunnelDestination badoption.Listable[string] `json:"tunnel_destination,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"` diff --git a/option/rule_set.go b/option/rule_set.go index 610d7ba2a2..276b785634 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -194,6 +194,8 @@ type DefaultHeadlessRule struct { SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"` PortRange badoption.Listable[string] `json:"port_range,omitempty"` + TunnelSource badoption.Listable[string] `json:"tunnel_source,omitempty"` + TunnelDestination badoption.Listable[string] `json:"tunnel_destination,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"` diff --git a/option/tunnel.go b/option/tunnel.go new file mode 100644 index 0000000000..cc1df36b66 --- /dev/null +++ b/option/tunnel.go @@ -0,0 +1,21 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +type TunnelClientEndpointOptions struct { + UUID string `json:"uuid"` + Key string `json:"key"` + Outbound Outbound `json:"outbound"` +} + +type TunnelServerEndpointOptions struct { + UUID string `json:"uuid"` + Users []TunnelUser `json:"users"` + Inbound Inbound `json:"inbound"` + ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` +} + +type TunnelUser struct { + UUID string `json:"uuid"` + Key string `json:"key"` +} diff --git a/protocol/tunnel/client.go b/protocol/tunnel/client.go new file mode 100644 index 0000000000..4e4f9c2f07 --- /dev/null +++ b/protocol/tunnel/client.go @@ -0,0 +1,151 @@ +package tunnel + +import ( + "context" + "net" + "os" + "time" + + "github.com/gofrs/uuid/v5" + "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" + "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" +) + +func RegisterClientEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.TunnelClientEndpointOptions](registry, C.TypeTunnelClient, NewClientEndpoint) +} + +type ClientEndpoint struct { + outbound.Adapter + ctx context.Context + outbound adapter.Outbound + router adapter.ConnectionRouterEx + logger logger.ContextLogger + uuid uuid.UUID + key uuid.UUID +} + +func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelClientEndpointOptions) (adapter.Endpoint, error) { + clientUUID, err := uuid.FromString(options.UUID) + if err != nil { + return nil, err + } + clientKey, err := uuid.FromString(options.Key) + if err != nil { + return nil, err + } + client := &ClientEndpoint{ + Adapter: outbound.NewAdapter(C.TypeTunnelClient, tag, []string{N.NetworkTCP}, []string{}), + ctx: ctx, + router: router, + logger: logger, + uuid: clientUUID, + key: clientKey, + } + outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) + outbound, err := outboundRegistry.CreateOutbound(ctx, router, logger, options.Outbound.Tag, options.Outbound.Type, options.Outbound.Options) + if err != nil { + return nil, err + } + client.outbound = outbound + return client, nil +} + +func (c *ClientEndpoint) Start(stage adapter.StartStage) error { + if stage != adapter.StartStatePostStart { + return nil + } + for range 5 { + go func() { + for { + select { + case <-c.ctx.Done(): + return + default: + err := c.startInboundConn() + if err != nil { + c.logger.ErrorContext(c.ctx, err) + time.Sleep(time.Second * 5) + } + } + } + }() + } + return nil +} + +func (c *ClientEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if network != N.NetworkTCP { + return nil, os.ErrInvalid + } + var destinationUUID *uuid.UUID + if metadata := adapter.ContextFrom(ctx); metadata != nil { + if metadata.TunnelDestination != "" { + uuid, err := uuid.FromString(metadata.TunnelDestination) + if err != nil { + return nil, err + } + destinationUUID = &uuid + } + } + if destinationUUID == nil { + return nil, E.New("tunnel destination not set") + } + if *destinationUUID == c.uuid { + return nil, E.New("routing loop") + } + conn, err := c.outbound.DialContext(ctx, N.NetworkTCP, Destination) + if err != nil { + return nil, err + } + err = WriteRequest(conn, &Request{UUID: c.key, Command: CommandTCP, DestinationUUID: *destinationUUID, Destination: destination}) + return conn, err +} + +func (c *ClientEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +func (c *ClientEndpoint) Close() error { + return nil +} + +func (c *ClientEndpoint) startInboundConn() error { + conn, err := c.outbound.DialContext(c.ctx, N.NetworkTCP, Destination) + if err != nil { + return err + } + err = WriteRequest(conn, &Request{UUID: c.key, Command: CommandInbound, Destination: Destination}) + if err != nil { + return err + } + request, err := ReadRequest(conn) + if err != nil { + return err + } + go c.connHandler(conn, request) + return nil +} + +func (c *ClientEndpoint) connHandler(conn net.Conn, request *Request) { + metadata := adapter.InboundContext{ + Source: M.ParseSocksaddr(conn.RemoteAddr().String()), + Destination: request.Destination, + } + if request.UUID == c.uuid { + c.logger.ErrorContext(c.ctx, "routing loop") + conn.Close() + return + } + metadata.TunnelSource = request.UUID.String() + c.router.RouteConnectionEx(c.ctx, conn, metadata, func(it error) {}) +} diff --git a/protocol/tunnel/protocol.go b/protocol/tunnel/protocol.go new file mode 100644 index 0000000000..19e6f1cd62 --- /dev/null +++ b/protocol/tunnel/protocol.go @@ -0,0 +1,91 @@ +package tunnel + +import ( + "encoding/binary" + "io" + + "github.com/gofrs/uuid/v5" + "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" +) + +const ( + Version = 0 +) + +const ( + CommandInbound = 1 + CommandTCP = 2 +) + +var Destination = M.Socksaddr{ + Fqdn: "sp.tunnel.sing-box.arpa", + Port: 444, +} + +var AddressSerializer = M.NewSerializer( + M.AddressFamilyByte(0x01, M.AddressFamilyIPv4), + M.AddressFamilyByte(0x03, M.AddressFamilyIPv6), + M.AddressFamilyByte(0x02, M.AddressFamilyFqdn), + M.PortThenAddress(), +) + +type Request struct { + UUID uuid.UUID + Command byte + DestinationUUID uuid.UUID + Destination M.Socksaddr +} + +func ReadRequest(reader io.Reader) (*Request, error) { + var request Request + var version uint8 + err := binary.Read(reader, binary.BigEndian, &version) + if err != nil { + return nil, err + } + if version != Version { + return nil, E.New("unknown version: ", version) + } + _, err = io.ReadFull(reader, request.UUID[:]) + if err != nil { + return nil, err + } + err = binary.Read(reader, binary.BigEndian, &request.Command) + if err != nil { + return nil, err + } + _, err = io.ReadFull(reader, request.DestinationUUID[:]) + if err != nil { + return nil, err + } + request.Destination, err = AddressSerializer.ReadAddrPort(reader) + if err != nil { + return nil, err + } + return &request, nil +} + +func WriteRequest(writer io.Writer, request *Request) error { + var requestLen int + requestLen += 1 // version + requestLen += 16 // UUID + requestLen += 16 // destinationUUID + requestLen += 1 // command + requestLen += AddressSerializer.AddrPortLen(request.Destination) + buffer := buf.NewSize(requestLen) + defer buffer.Release() + common.Must( + buffer.WriteByte(Version), + common.Error(buffer.Write(request.UUID[:])), + buffer.WriteByte(request.Command), + common.Error(buffer.Write(request.DestinationUUID[:])), + ) + err := AddressSerializer.WriteAddrPort(buffer, request.Destination) + if err != nil { + return err + } + return common.Error(writer.Write(buffer.Bytes())) +} diff --git a/protocol/tunnel/router.go b/protocol/tunnel/router.go new file mode 100644 index 0000000000..e2e708b542 --- /dev/null +++ b/protocol/tunnel/router.go @@ -0,0 +1,41 @@ +package tunnel + +import ( + "context" + "net" + "os" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" +) + +type Router struct { + adapter.Router + logger logger.ContextLogger + handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error +} + +func NewRouter(router adapter.Router, logger logger.ContextLogger, handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error) *Router { + return &Router{Router: router, logger: logger, handler: handler} +} + +func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return r.handler(ctx, conn, metadata, func(error) {}) +} + +func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return os.ErrInvalid +} + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + if err := r.handler(ctx, conn, metadata, onClose); err != nil { + r.logger.ErrorContext(ctx, err) + N.CloseOnHandshakeFailure(conn, onClose, err) + } +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + r.logger.ErrorContext(ctx, os.ErrInvalid) + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) +} diff --git a/protocol/tunnel/server.go b/protocol/tunnel/server.go new file mode 100644 index 0000000000..d447be8c65 --- /dev/null +++ b/protocol/tunnel/server.go @@ -0,0 +1,203 @@ +package tunnel + +import ( + "context" + "net" + "os" + "sync" + "time" + + "github.com/gofrs/uuid/v5" + "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" + "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" +) + +func RegisterServerEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.TunnelServerEndpointOptions](registry, C.TypeTunnelServer, NewServerEndpoint) +} + +type ServerEndpoint struct { + outbound.Adapter + logger logger.ContextLogger + inbound adapter.Inbound + router adapter.Router + uuid uuid.UUID + users map[uuid.UUID]uuid.UUID + keys map[uuid.UUID]uuid.UUID + conns map[uuid.UUID]chan net.Conn + timeout time.Duration + + mtx sync.Mutex +} + +func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelServerEndpointOptions) (adapter.Endpoint, error) { + serverUUID, err := uuid.FromString(options.UUID) + if err != nil { + return nil, err + } + server := &ServerEndpoint{ + Adapter: outbound.NewAdapter(C.TypeTunnelServer, tag, []string{N.NetworkTCP}, []string{}), + logger: logger, + router: router, + uuid: serverUUID, + } + inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) + inbound, err := inboundRegistry.Create(ctx, NewRouter(router, logger, server.connHandler), logger, options.Inbound.Tag, options.Inbound.Type, options.Inbound.Options) + if err != nil { + return nil, err + } + server.inbound = inbound + server.users = make(map[uuid.UUID]uuid.UUID, len(options.Users)) + server.keys = make(map[uuid.UUID]uuid.UUID, len(options.Users)) + server.conns = make(map[uuid.UUID]chan net.Conn) + for _, user := range options.Users { + key, err := uuid.FromString(user.Key) + if err != nil { + return nil, err + } + uuid, err := uuid.FromString(user.UUID) + if err != nil { + return nil, err + } + server.users[key] = uuid + server.keys[uuid] = key + server.conns[uuid] = make(chan net.Conn, 10) + } + if options.ConnectTimeout != 0 { + server.timeout = time.Duration(options.ConnectTimeout) + } else { + server.timeout = C.TCPConnectTimeout + } + return server, nil +} + +func (s *ServerEndpoint) Start(stage adapter.StartStage) error { + return s.inbound.Start(stage) +} + +func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if network != N.NetworkTCP { + return nil, os.ErrInvalid + } + var sourceUUID *uuid.UUID + var ch chan net.Conn + if metadata := adapter.ContextFrom(ctx); metadata != nil { + if metadata.TunnelDestination != "" { + tunnelDestination, err := uuid.FromString(metadata.TunnelDestination) + if err != nil { + return nil, err + } + s.mtx.Lock() + var ok bool + ch, ok = s.conns[tunnelDestination] + if !ok { + return nil, E.New("user ", metadata.TunnelDestination, " not found") + } + s.mtx.Unlock() + } + if metadata.TunnelSource != "" { + tunnelSource, err := uuid.FromString(metadata.TunnelSource) + if err != nil { + return nil, err + } + sourceUUID = &tunnelSource + } + } + if ch == nil { + return nil, E.New("tunnel destination not set") + } + if sourceUUID == nil { + sourceUUID = &s.uuid + } + ctx, cancel := context.WithTimeout(ctx, s.timeout) + defer cancel() + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + select { + case conn := <-ch: + err := WriteRequest(conn, &Request{UUID: *sourceUUID, Command: CommandTCP, Destination: destination}) + if err != nil { + s.logger.ErrorContext(ctx, err) + continue + } + return conn, nil + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} + +func (s *ServerEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +func (s *ServerEndpoint) Close() error { + return common.Close(s.inbound) +} + +func (s *ServerEndpoint) connHandler(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + if metadata.Destination != Destination { + s.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return nil + } + request, err := ReadRequest(conn) + if err != nil { + return err + } + if request.Command == CommandInbound { + s.mtx.Lock() + defer s.mtx.Unlock() + uuid, ok := s.users[request.UUID] + if !ok { + return E.New("key ", request.UUID.String(), " not found") + } + ch := s.conns[uuid] + select { + case ch <- conn: + default: + oldConn := <-ch + oldConn.Close() + ch <- conn + } + return nil + } + if request.Command == CommandTCP { + sourceUUID, ok := s.users[request.UUID] + if !ok { + return E.New("key ", request.UUID, " not found") + } + if sourceUUID == request.DestinationUUID { + return E.New("routing loop on ", sourceUUID) + } + s.mtx.Lock() + if request.DestinationUUID != s.uuid { + _, ok = s.keys[request.DestinationUUID] + if !ok { + return E.New("user ", sourceUUID, " not found") + } + } + s.mtx.Unlock() + metadata.Inbound = s.Tag() + metadata.InboundType = C.TypeTunnelServer + metadata.Destination = request.Destination + metadata.TunnelSource = sourceUUID.String() + metadata.TunnelDestination = request.DestinationUUID.String() + s.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return nil + } + return E.New("command ", request.Command, " not found") +} diff --git a/route/route.go b/route/route.go index d9bf263821..9d60f59820 100644 --- a/route/route.go +++ b/route/route.go @@ -425,6 +425,9 @@ match: Fqdn: metadata.Destination.Fqdn, } } + if routeOptions.OverrideTunnelDestination != "" { + metadata.TunnelDestination = routeOptions.OverrideTunnelDestination + } if routeOptions.NetworkStrategy != nil { metadata.NetworkStrategy = routeOptions.NetworkStrategy } diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index d4c6625d29..6246c7bee1 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -33,6 +33,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti RuleActionRouteOptions: RuleActionRouteOptions{ OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0), OverridePort: action.RouteOptions.OverridePort, + OverrideTunnelDestination: action.RouteOptions.OverrideTunnelDestination, NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptions.NetworkStrategy), FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, @@ -147,6 +148,7 @@ func (r *RuleActionRoute) String() string { type RuleActionRouteOptions struct { OverrideAddress M.Socksaddr OverridePort uint16 + OverrideTunnelDestination string NetworkStrategy *C.NetworkStrategy NetworkType []C.InterfaceType FallbackNetworkType []C.InterfaceType @@ -168,6 +170,9 @@ func (r *RuleActionRouteOptions) String() string { if r.OverridePort > 0 { descriptions = append(descriptions, F.ToString("override-port=", r.OverridePort)) } + if r.OverrideTunnelDestination != "" { + descriptions = append(descriptions, F.ToString("override-tunnel-destination=", r.OverrideTunnelDestination)) + } if r.NetworkStrategy != nil { descriptions = append(descriptions, F.ToString("network-strategy=", r.NetworkStrategy)) } diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 56cf32dc07..e06502eb9b 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -189,6 +189,16 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.destinationPortItems = append(rule.destinationPortItems, item) rule.allItems = append(rule.allItems, item) } + if len(options.TunnelSource) > 0 { + item := NewTunnelSourceItem(options.TunnelSource) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.TunnelDestination) > 0 { + item := NewTunnelDestinationItem(options.TunnelDestination) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.ProcessName) > 0 { item := NewProcessItem(options.ProcessName) rule.items = append(rule.items, item) diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index d6eb7104f5..a198b009d9 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -180,6 +180,16 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.destinationPortItems = append(rule.destinationPortItems, item) rule.allItems = append(rule.allItems, item) } + if len(options.TunnelSource) > 0 { + item := NewTunnelSourceItem(options.TunnelSource) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.TunnelDestination) > 0 { + item := NewTunnelDestinationItem(options.TunnelDestination) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.ProcessName) > 0 { item := NewProcessItem(options.ProcessName) rule.items = append(rule.items, item) diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index ba17ca374c..78c5b7ca34 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -121,6 +121,16 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR rule.destinationPortItems = append(rule.destinationPortItems, item) rule.allItems = append(rule.allItems, item) } + if len(options.TunnelSource) > 0 { + item := NewTunnelSourceItem(options.TunnelSource) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.TunnelDestination) > 0 { + item := NewTunnelDestinationItem(options.TunnelDestination) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.ProcessName) > 0 { item := NewProcessItem(options.ProcessName) rule.items = append(rule.items, item) diff --git a/route/rule/rule_item_tunnel_destination.go b/route/rule/rule_item_tunnel_destination.go new file mode 100644 index 0000000000..34f711d6eb --- /dev/null +++ b/route/rule/rule_item_tunnel_destination.go @@ -0,0 +1,35 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*TunnelDestinationItem)(nil) + +type TunnelDestinationItem struct { + destinations []string + destinationMap map[string]bool +} + +func NewTunnelDestinationItem(destinations []string) *TunnelDestinationItem { + rule := &TunnelDestinationItem{destinations, make(map[string]bool)} + for _, destination := range destinations { + rule.destinationMap[destination] = true + } + return rule +} + +func (r *TunnelDestinationItem) Match(metadata *adapter.InboundContext) bool { + return r.destinationMap[metadata.TunnelDestination] +} + +func (r *TunnelDestinationItem) String() string { + if len(r.destinations) == 1 { + return F.ToString("tunnel_destination=", r.destinations[0]) + } else { + return F.ToString("tunnel_destination=[", strings.Join(r.destinations, " "), "]") + } +} diff --git a/route/rule/rule_item_tunnel_source.go b/route/rule/rule_item_tunnel_source.go new file mode 100644 index 0000000000..6a2f01cb2f --- /dev/null +++ b/route/rule/rule_item_tunnel_source.go @@ -0,0 +1,35 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*TunnelSourceItem)(nil) + +type TunnelSourceItem struct { + sources []string + sourceMap map[string]bool +} + +func NewTunnelSourceItem(sources []string) *TunnelSourceItem { + rule := &TunnelSourceItem{sources, make(map[string]bool)} + for _, source := range sources { + rule.sourceMap[source] = true + } + return rule +} + +func (r *TunnelSourceItem) Match(metadata *adapter.InboundContext) bool { + return r.sourceMap[metadata.TunnelSource] +} + +func (r *TunnelSourceItem) String() string { + if len(r.sources) == 1 { + return F.ToString("tunnel_source=", r.sources[0]) + } else { + return F.ToString("tunnel_source=[", strings.Join(r.sources, " "), "]") + } +} From 691ecd45a9734536164e566ecad4774053197362 Mon Sep 17 00:00:00 2001 From: Shtorm <108103062+shtorm-7@users.noreply.github.com> Date: Sun, 6 Jul 2025 21:16:54 +0300 Subject: [PATCH 0353/1272] Update README.md Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com> --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7b1c833a42..aef790f012 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,20 @@ -# sing-box +# sing-box-extended -The universal proxy platform. +Sing-box with extended features. -[![Packaging status](https://repology.org/badge/vertical-allrepos/sing-box.svg)](https://repology.org/project/sing-box/versions) +## Features -## Documentation +* Amnezia +* WARP +* Tunneling +* Mieru +* XHTTP +* SDNS (DNSCrypt) +* Unified delay -https://sing-box.sagernet.org +## Examples + +https://github.com/shtorm-7/sing-box-extended/tree/extended/examples ## License @@ -28,4 +36,4 @@ along with this program. If not, see . In addition, no derivative work may use the name or imply association with this application without prior consent. -``` \ No newline at end of file +``` From b8502759b5025a0bd83571401a9efe0af3b9a3b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 7 Jul 2025 13:57:37 +0800 Subject: [PATCH 0354/1272] Fix DNS reject check --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5ebdd34cb2..5bbd5bf7fb 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.10 - github.com/sagernet/sing-dns v0.4.5 + github.com/sagernet/sing-dns v0.4.6 github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.3 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index 55b2f9802c..fb9fc8fcc8 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4= github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.4.5 h1:D9REN14qx2FTrZRBrtFLL99f2CuFzQ9S7mIf8uV5hZI= -github.com/sagernet/sing-dns v0.4.5/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= +github.com/sagernet/sing-dns v0.4.6 h1:mjZC0o6d5sQ1sraoOBbK3G3apCbuL8wWYwu2RNu5rbM= +github.com/sagernet/sing-dns v0.4.6/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.4.3 h1:OZ/kGvSzjtYg+t0DY3F606hlT5LeiQQXDxfBopcRryQ= From 281d52a1eaf6365c6b9750c7a1219d3b2849ca87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 8 Jul 2025 13:11:03 +0800 Subject: [PATCH 0355/1272] Fix hy2 server crash --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5bbd5bf7fb..56d3a09cb9 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sagernet/sing v0.6.10 github.com/sagernet/sing-dns v0.4.6 github.com/sagernet/sing-mux v0.3.2 - github.com/sagernet/sing-quic v0.4.3 + github.com/sagernet/sing-quic v0.4.4 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.0 diff --git a/go.sum b/go.sum index fb9fc8fcc8..63299a07ad 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,8 @@ github.com/sagernet/sing-dns v0.4.6 h1:mjZC0o6d5sQ1sraoOBbK3G3apCbuL8wWYwu2RNu5r github.com/sagernet/sing-dns v0.4.6/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.4.3 h1:OZ/kGvSzjtYg+t0DY3F606hlT5LeiQQXDxfBopcRryQ= -github.com/sagernet/sing-quic v0.4.3/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= +github.com/sagernet/sing-quic v0.4.4 h1:qqOCLnzHbqKkj/wBcXEI3rhSyqoGlqDdv2S6mz2d/JA= +github.com/sagernet/sing-quic v0.4.4/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= 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 bc35aca01704497c179da1a03e45ad8e32f1a51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 7 Jul 2025 14:00:20 +0800 Subject: [PATCH 0356/1272] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/clients/android b/clients/android index eb2e13a6f9..7f1fa971e3 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit eb2e13a6f9a8c03a35ae672395ccab0a6bdcd954 +Subproject commit 7f1fa971e3c7bbc504c2bd455f4e813a562990cb diff --git a/clients/apple b/clients/apple index ae5818ee5a..f7883b0f3e 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit ae5818ee5a24af965dc91f80bffa16e1e6c109c1 +Subproject commit f7883b0f3ec26c449cba26b3b1a692f070f5424d diff --git a/docs/changelog.md b/docs/changelog.md index 8f995cfd1f..d5f4a586e7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,13 @@ icon: material/alert-decagram --- +### 1.11.15 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + ### 1.11.14 * Fixes and improvements From 3016338e34601dae219478796b63c2fabc8e3ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 16 Mar 2025 14:50:44 +0800 Subject: [PATCH 0357/1272] refactor: DNS --- .goreleaser.fury.yaml | 4 +- adapter/dns.go | 73 +++ adapter/experimental.go | 3 +- adapter/fakeip.go | 3 +- adapter/inbound.go | 2 - adapter/outbound/manager.go | 17 +- adapter/router.go | 17 - adapter/rule.go | 1 - box.go | 107 +++- cmd/sing-box/cmd.go | 2 +- common/dialer/dialer.go | 9 +- common/dialer/resolve.go | 60 +- common/dialer/router.go | 15 +- common/tls/ech_client.go | 4 +- common/tls/std_client.go | 5 +- constant/dns.go | 29 + dns/client.go | 563 ++++++++++++++++++ dns/client_log.go | 69 +++ dns/client_truncate.go | 31 + dns/extension_edns0_subnet.go | 56 ++ dns/rcode.go | 33 + dns/router.go | 444 ++++++++++++++ .../server.go => dns/transport/dhcp/dhcp.go | 125 ++-- dns/transport/fakeip/fakeip.go | 67 +++ {transport => dns/transport}/fakeip/memory.go | 0 {transport => dns/transport}/fakeip/store.go | 0 dns/transport/hosts/hosts.go | 63 ++ dns/transport/hosts/hosts_file.go | 102 ++++ dns/transport/hosts/hosts_test.go | 16 + dns/transport/hosts/hosts_unix.go | 5 + dns/transport/hosts/hosts_windows.go | 17 + dns/transport/hosts/testdata/hosts | 2 + dns/transport/https.go | 204 +++++++ dns/transport/local/local.go | 197 ++++++ dns/transport/local/resolv.go | 146 +++++ dns/transport/local/resolv_unix.go | 175 ++++++ dns/transport/local/resolv_windows.go | 100 ++++ dns/transport/predefined.go | 83 +++ dns/transport/quic/http3.go | 167 ++++++ dns/transport/quic/quic.go | 174 ++++++ dns/transport/tcp.go | 99 +++ dns/transport/tls.go | 115 ++++ dns/transport/udp.go | 223 +++++++ dns/transport_adapter.go | 70 +++ dns/transport_dialer.go | 93 +++ dns/transport_manager.go | 288 +++++++++ dns/transport_registry.go | 72 +++ experimental/clashapi/dns.go | 6 +- experimental/clashapi/server.go | 16 +- experimental/deprecated/constants.go | 15 + experimental/libbox/config.go | 19 +- experimental/libbox/dns.go | 155 ++--- experimental/libbox/platform.go | 1 + experimental/libbox/service.go | 6 +- go.mod | 1 - go.sum | 2 - include/dhcp.go | 9 +- include/dhcp_stub.go | 12 +- include/quic.go | 8 +- include/quic_stub.go | 14 +- include/registry.go | 27 +- option/dns.go | 315 +++++++++- option/dns_record.go | 161 +++++ option/rule_action.go | 5 +- option/rule_dns.go | 1 + option/types.go | 37 +- protocol/direct/outbound.go | 29 +- protocol/dns/handle.go | 14 +- protocol/dns/outbound.go | 5 +- protocol/socks/outbound.go | 17 +- protocol/wireguard/endpoint.go | 12 +- protocol/wireguard/outbound.go | 14 +- release/config/config.json | 9 +- route/dns.go | 27 +- route/geo_resources.go | 246 -------- route/route.go | 46 +- route/route_dns.go | 355 ----------- route/router.go | 412 ++----------- route/rule/rule_abstract.go | 25 - route/rule/rule_action.go | 14 +- route/rule/rule_default.go | 12 +- route/rule/rule_dns.go | 17 +- route/rule/rule_item_geoip.go | 98 --- route/rule/rule_item_geosite.go | 61 -- route/rule/rule_item_ip_accept_any.go | 21 + route/rule_conds.go | 21 - test/domain_inbound_test.go | 3 +- transport/fakeip/server.go | 95 --- transport/v2rayhttp/server.go | 2 +- 89 files changed, 4784 insertions(+), 1731 deletions(-) create mode 100644 adapter/dns.go create mode 100644 dns/client.go create mode 100644 dns/client_log.go create mode 100644 dns/client_truncate.go create mode 100644 dns/extension_edns0_subnet.go create mode 100644 dns/rcode.go create mode 100644 dns/router.go rename transport/dhcp/server.go => dns/transport/dhcp/dhcp.go (66%) create mode 100644 dns/transport/fakeip/fakeip.go rename {transport => dns/transport}/fakeip/memory.go (100%) rename {transport => dns/transport}/fakeip/store.go (100%) create mode 100644 dns/transport/hosts/hosts.go create mode 100644 dns/transport/hosts/hosts_file.go create mode 100644 dns/transport/hosts/hosts_test.go create mode 100644 dns/transport/hosts/hosts_unix.go create mode 100644 dns/transport/hosts/hosts_windows.go create mode 100644 dns/transport/hosts/testdata/hosts create mode 100644 dns/transport/https.go create mode 100644 dns/transport/local/local.go create mode 100644 dns/transport/local/resolv.go create mode 100644 dns/transport/local/resolv_unix.go create mode 100644 dns/transport/local/resolv_windows.go create mode 100644 dns/transport/predefined.go create mode 100644 dns/transport/quic/http3.go create mode 100644 dns/transport/quic/quic.go create mode 100644 dns/transport/tcp.go create mode 100644 dns/transport/tls.go create mode 100644 dns/transport/udp.go create mode 100644 dns/transport_adapter.go create mode 100644 dns/transport_dialer.go create mode 100644 dns/transport_manager.go create mode 100644 dns/transport_registry.go create mode 100644 option/dns_record.go delete mode 100644 route/geo_resources.go delete mode 100644 route/route_dns.go delete mode 100644 route/rule/rule_item_geoip.go delete mode 100644 route/rule/rule_item_geosite.go create mode 100644 route/rule/rule_item_ip_accept_any.go delete mode 100644 transport/fakeip/server.go diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml index 3212027ab2..4761a76af5 100644 --- a/.goreleaser.fury.yaml +++ b/.goreleaser.fury.yaml @@ -6,7 +6,9 @@ builds: - -v - -trimpath ldflags: - - -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid= + - -X github.com/sagernet/sing-box/constant.Version={{ .Version }} + - -s + - -buildid= tags: - with_gvisor - with_quic diff --git a/adapter/dns.go b/adapter/dns.go new file mode 100644 index 0000000000..e0f381b82b --- /dev/null +++ b/adapter/dns.go @@ -0,0 +1,73 @@ +package adapter + +import ( + "context" + "net/netip" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" + + "github.com/miekg/dns" +) + +type DNSRouter interface { + Lifecycle + Exchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error) + Lookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error) + ClearCache() + LookupReverseMapping(ip netip.Addr) (string, bool) + ResetNetwork() +} + +type DNSClient interface { + Start() + Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) + Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) + LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool) + ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool) + ClearCache() +} + +type DNSQueryOptions struct { + Transport DNSTransport + Strategy C.DomainStrategy + DisableCache bool + RewriteTTL *uint32 + ClientSubnet netip.Prefix +} + +type RDRCStore interface { + LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) + SaveRDRC(transportName string, qName string, qType uint16) error + SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) +} + +type DNSTransport interface { + Type() string + Tag() string + Dependencies() []string + Reset() + Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error) +} + +type LegacyDNSTransport interface { + LegacyStrategy() C.DomainStrategy + LegacyClientSubnet() netip.Prefix +} + +type DNSTransportRegistry interface { + option.DNSTransportOptionsRegistry + CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error) +} + +type DNSTransportManager interface { + Lifecycle + Transports() []DNSTransport + Transport(tag string) (DNSTransport, bool) + Default() DNSTransport + FakeIP() FakeIPTransport + Remove(tag string) error + Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) error +} diff --git a/adapter/experimental.go b/adapter/experimental.go index 648eb41880..99d7c9a52d 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -7,7 +7,6 @@ import ( "time" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/varbin" ) @@ -31,7 +30,7 @@ type CacheFile interface { FakeIPStorage StoreRDRC() bool - dns.RDRCStore + RDRCStore LoadMode() string StoreMode(mode string) error diff --git a/adapter/fakeip.go b/adapter/fakeip.go index 51247c32fe..97d1c3c070 100644 --- a/adapter/fakeip.go +++ b/adapter/fakeip.go @@ -3,7 +3,6 @@ package adapter import ( "net/netip" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/logger" ) @@ -27,6 +26,6 @@ type FakeIPStorage interface { } type FakeIPTransport interface { - dns.Transport + DNSTransport Store() FakeIPStore } diff --git a/adapter/inbound.go b/adapter/inbound.go index 3e93120b8d..e1f2d8c9a3 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -78,8 +78,6 @@ type InboundContext struct { FallbackNetworkType []C.InterfaceType FallbackDelay time.Duration - DNSServer string - DestinationAddresses []netip.Addr SourceGeoIPCode string GeoIPCode string diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index c3941d02b5..977fe4caba 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -23,7 +23,7 @@ type Manager struct { registry adapter.OutboundRegistry endpoint adapter.EndpointManager defaultTag string - access sync.Mutex + access sync.RWMutex started bool stage adapter.StartStage outbounds []adapter.Outbound @@ -169,15 +169,15 @@ func (m *Manager) Close() error { } func (m *Manager) Outbounds() []adapter.Outbound { - m.access.Lock() - defer m.access.Unlock() + m.access.RLock() + defer m.access.RUnlock() return m.outbounds } func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) { - m.access.Lock() + m.access.RLock() outbound, found := m.outboundByTag[tag] - m.access.Unlock() + m.access.RUnlock() if found { return outbound, true } @@ -185,8 +185,8 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) { } func (m *Manager) Default() adapter.Outbound { - m.access.Lock() - defer m.access.Unlock() + m.access.RLock() + defer m.access.RUnlock() if m.defaultOutbound != nil { return m.defaultOutbound } else { @@ -196,9 +196,9 @@ func (m *Manager) Default() adapter.Outbound { func (m *Manager) Remove(tag string) error { m.access.Lock() + defer m.access.Unlock() outbound, found := m.outboundByTag[tag] if !found { - m.access.Unlock() return os.ErrInvalid } delete(m.outboundByTag, tag) @@ -232,7 +232,6 @@ func (m *Manager) Remove(tag string) error { }) } } - m.access.Unlock() if started { return common.Close(outbound) } diff --git a/adapter/router.go b/adapter/router.go index 687943cbfe..45e4f723f8 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -4,42 +4,25 @@ import ( "context" "net" "net/http" - "net/netip" "sync" - "github.com/sagernet/sing-box/common/geoip" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-dns" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" - mdns "github.com/miekg/dns" "go4.org/netipx" ) type Router interface { Lifecycle - - FakeIPStore() FakeIPStore - ConnectionRouter PreMatch(metadata InboundContext) error ConnectionRouterEx - - GeoIPReader() *geoip.Reader - LoadGeosite(code string) (Rule, error) RuleSet(tag string) (RuleSet, bool) NeedWIFIState() bool - - Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error) - Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) - LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) - ClearDNSCache() Rules() []Rule - AppendTracker(tracker ConnectionTracker) - ResetNetwork() } diff --git a/adapter/rule.go b/adapter/rule.go index f3737a25de..2512a77bf4 100644 --- a/adapter/rule.go +++ b/adapter/rule.go @@ -13,7 +13,6 @@ type Rule interface { HeadlessRule Service Type() string - UpdateGeosite() error Action() RuleAction } diff --git a/box.go b/box.go index b9f04c8782..e810b77148 100644 --- a/box.go +++ b/box.go @@ -16,6 +16,8 @@ import ( "github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "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" @@ -34,17 +36,19 @@ import ( var _ adapter.Service = (*Box)(nil) type Box struct { - createdAt time.Time - logFactory log.Factory - logger log.ContextLogger - network *route.NetworkManager - endpoint *endpoint.Manager - inbound *inbound.Manager - outbound *outbound.Manager - connection *route.ConnectionManager - router *route.Router - services []adapter.LifecycleService - done chan struct{} + createdAt time.Time + logFactory log.Factory + logger log.ContextLogger + network *route.NetworkManager + endpoint *endpoint.Manager + inbound *inbound.Manager + outbound *outbound.Manager + dnsTransport *dns.TransportManager + dnsRouter *dns.Router + connection *route.ConnectionManager + router *route.Router + services []adapter.LifecycleService + done chan struct{} } type Options struct { @@ -58,6 +62,7 @@ func Context( inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry, endpointRegistry adapter.EndpointRegistry, + dnsTransportRegistry adapter.DNSTransportRegistry, ) context.Context { if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || service.FromContext[adapter.InboundRegistry](ctx) == nil { @@ -74,6 +79,10 @@ func Context( ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry) ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry) } + if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil { + ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry) + ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry) + } return ctx } @@ -88,6 +97,7 @@ func New(options Options) (*Box, error) { endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) + dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx) if endpointRegistry == nil { return nil, E.New("missing endpoint registry in context") @@ -132,13 +142,17 @@ func New(options Options) (*Box, error) { } routeOptions := common.PtrValueOrDefault(options.Route) + dnsOptions := common.PtrValueOrDefault(options.DNS) endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry) inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager) outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final) + dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final) service.MustRegister[adapter.EndpointManager](ctx, endpointManager) service.MustRegister[adapter.InboundManager](ctx, inboundManager) service.MustRegister[adapter.OutboundManager](ctx, outboundManager) - + service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager) + dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions) + service.MustRegister[adapter.DNSRouter](ctx, dnsRouter) networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions) if err != nil { return nil, E.Cause(err, "initialize network manager") @@ -146,18 +160,40 @@ func New(options Options) (*Box, error) { service.MustRegister[adapter.NetworkManager](ctx, networkManager) connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection")) service.MustRegister[adapter.ConnectionManager](ctx, connectionManager) - router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS)) + router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions) + service.MustRegister[adapter.Router](ctx, router) + err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet) if err != nil { return nil, E.Cause(err, "initialize router") } - ntpOptions := common.PtrValueOrDefault(options.NTP) var timeService *tls.TimeServiceWrapper if ntpOptions.Enabled { timeService = new(tls.TimeServiceWrapper) service.MustRegister[ntp.TimeService](ctx, timeService) } - + for i, transportOptions := range dnsOptions.Servers { + var tag string + if transportOptions.Tag != "" { + tag = transportOptions.Tag + } else { + tag = F.ToString(i) + } + err = dnsTransportManager.Create( + ctx, + logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")), + tag, + transportOptions.Type, + transportOptions.Options, + ) + if err != nil { + return nil, E.Cause(err, "initialize inbound[", i, "]") + } + } + err = dnsRouter.Initialize(dnsOptions.Rules) + if err != nil { + return nil, E.Cause(err, "initialize dns router") + } for i, endpointOptions := range options.Endpoints { var tag string if endpointOptions.Tag != "" { @@ -238,6 +274,13 @@ func New(options Options) (*Box, error) { option.DirectOutboundOptions{}, ), )) + dnsTransportManager.Initialize(common.Must1( + local.NewTransport( + ctx, + logFactory.NewLogger("dns/local"), + "local", + option.LocalDNSServerOptions{}, + ))) if platformInterface != nil { err = platformInterface.Initialize(networkManager) if err != nil { @@ -289,17 +332,19 @@ func New(options Options) (*Box, error) { services = append(services, adapter.NewLifecycleService(ntpService, "ntp service")) } return &Box{ - network: networkManager, - endpoint: endpointManager, - inbound: inboundManager, - outbound: outboundManager, - connection: connectionManager, - router: router, - createdAt: createdAt, - logFactory: logFactory, - logger: logFactory.Logger(), - services: services, - done: make(chan struct{}), + network: networkManager, + endpoint: endpointManager, + inbound: inboundManager, + outbound: outboundManager, + dnsTransport: dnsTransportManager, + dnsRouter: dnsRouter, + connection: connectionManager, + router: router, + createdAt: createdAt, + logFactory: logFactory, + logger: logFactory.Logger(), + services: services, + done: make(chan struct{}), }, nil } @@ -353,11 +398,11 @@ func (s *Box) preStart() error { if err != nil { return err } - err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint) + err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint) if err != nil { return err } - err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router) + err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router) if err != nil { return err } @@ -381,7 +426,7 @@ func (s *Box) start() error { if err != nil { return err } - err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint) + err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint) if err != nil { return err } @@ -389,7 +434,7 @@ func (s *Box) start() error { if err != nil { return err } - err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint) + err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint) if err != nil { return err } @@ -408,7 +453,7 @@ func (s *Box) Close() error { close(s.done) } err := common.Close( - s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.network, + s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network, ) for _, lifecycleService := range s.services { err = E.Append(err, lifecycleService.Close(), func(err error) error { diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go index d55235b855..55fe1179e2 100644 --- a/cmd/sing-box/cmd.go +++ b/cmd/sing-box/cmd.go @@ -69,5 +69,5 @@ func preRun(cmd *cobra.Command, args []string) { configPaths = append(configPaths, "config.json") } globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())) - globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()) + globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry()) } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 89d1eeaba9..f63e3864cb 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -37,13 +36,13 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { dialer = NewDetour(outboundManager, options.Detour) } if options.Detour == "" { - router := service.FromContext[adapter.Router](ctx) + router := service.FromContext[adapter.DNSRouter](ctx) if router != nil { dialer = NewResolveDialer( router, dialer, options.Detour == "" && !options.TCPFastOpen, - dns.DomainStrategy(options.DomainStrategy), + C.DomainStrategy(options.DomainStrategy), time.Duration(options.FallbackDelay)) } } @@ -62,10 +61,10 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter return nil, err } return NewResolveParallelInterfaceDialer( - service.FromContext[adapter.Router](ctx), + service.FromContext[adapter.DNSRouter](ctx), dialer, true, - dns.DomainStrategy(options.DomainStrategy), + C.DomainStrategy(options.DomainStrategy), time.Duration(options.FallbackDelay), ), nil } diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index ede1afd614..3d667a6c83 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -3,13 +3,11 @@ package dialer import ( "context" "net" - "net/netip" "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -23,12 +21,12 @@ var ( type resolveDialer struct { dialer N.Dialer parallel bool - router adapter.Router - strategy dns.DomainStrategy + router adapter.DNSRouter + strategy C.DomainStrategy fallbackDelay time.Duration } -func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) N.Dialer { +func NewResolveDialer(router adapter.DNSRouter, dialer N.Dialer, parallel bool, strategy C.DomainStrategy, fallbackDelay time.Duration) N.Dialer { return &resolveDialer{ dialer, parallel, @@ -43,7 +41,7 @@ type resolveParallelNetworkDialer struct { dialer ParallelInterfaceDialer } -func NewResolveParallelInterfaceDialer(router adapter.Router, dialer ParallelInterfaceDialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer { +func NewResolveParallelInterfaceDialer(router adapter.DNSRouter, dialer ParallelInterfaceDialer, parallel bool, strategy C.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer { return &resolveParallelNetworkDialer{ resolveDialer{ dialer, @@ -60,22 +58,13 @@ func (d *resolveDialer) DialContext(ctx context.Context, network string, destina if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } - ctx, metadata := adapter.ExtendContext(ctx) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - metadata.Destination = destination - metadata.Domain = "" - var addresses []netip.Addr - var err error - if d.strategy == dns.DomainStrategyAsIS { - addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) - } else { - addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) - } + addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Strategy: d.strategy}) if err != nil { return nil, err } if d.parallel { - return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay) + return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay) } else { return N.DialSerial(ctx, d.dialer, network, destination, addresses) } @@ -85,17 +74,8 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } - ctx, metadata := adapter.ExtendContext(ctx) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - metadata.Destination = destination - metadata.Domain = "" - var addresses []netip.Addr - var err error - if d.strategy == dns.DomainStrategyAsIS { - addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) - } else { - addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) - } + addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Strategy: d.strategy}) if err != nil { return nil, err } @@ -110,17 +90,10 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } - ctx, metadata := adapter.ExtendContext(ctx) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - metadata.Destination = destination - metadata.Domain = "" - var addresses []netip.Addr - var err error - if d.strategy == dns.DomainStrategyAsIS { - addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) - } else { - addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) - } + addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{ + Strategy: d.strategy, + }) if err != nil { return nil, err } @@ -128,7 +101,7 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context fallbackDelay = d.fallbackDelay } if d.parallel { - return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } else { return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } @@ -138,17 +111,8 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } - ctx, metadata := adapter.ExtendContext(ctx) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - metadata.Destination = destination - metadata.Domain = "" - var addresses []netip.Addr - var err error - if d.strategy == dns.DomainStrategyAsIS { - addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) - } else { - addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) - } + addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Strategy: d.strategy}) if err != nil { return nil, err } diff --git a/common/dialer/router.go b/common/dialer/router.go index 3edce65b09..801a36b1dc 100644 --- a/common/dialer/router.go +++ b/common/dialer/router.go @@ -7,24 +7,27 @@ import ( "github.com/sagernet/sing-box/adapter" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) type DefaultOutboundDialer struct { - outboundManager adapter.OutboundManager + outbound adapter.OutboundManager } -func NewDefaultOutbound(outboundManager adapter.OutboundManager) N.Dialer { - return &DefaultOutboundDialer{outboundManager: outboundManager} +func NewDefaultOutbound(ctx context.Context) N.Dialer { + return &DefaultOutboundDialer{ + outbound: service.FromContext[adapter.OutboundManager](ctx), + } } func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - return d.outboundManager.Default().DialContext(ctx, network, destination) + return d.outbound.Default().DialContext(ctx, network, destination) } func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return d.outboundManager.Default().ListenPacket(ctx, destination) + return d.outbound.Default().ListenPacket(ctx, destination) } func (d *DefaultOutboundDialer) Upstream() any { - return d.outboundManager.Default() + return d.outbound.Default() } diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go index 0ae3997aba..fff1873d29 100644 --- a/common/tls/ech_client.go +++ b/common/tls/ech_client.go @@ -15,8 +15,8 @@ import ( cftls "github.com/sagernet/cloudflare-tls" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/service" @@ -215,7 +215,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam }, }, } - response, err := service.FromContext[adapter.Router](ctx).Exchange(ctx, message) + response, err := service.FromContext[adapter.DNSRouter](ctx).Exchange(ctx, message, adapter.DNSQueryOptions{}) if err != nil { return nil, err } diff --git a/common/tls/std_client.go b/common/tls/std_client.go index 90f51821e8..7cd130a6e6 100644 --- a/common/tls/std_client.go +++ b/common/tls/std_client.go @@ -5,7 +5,6 @@ import ( "crypto/tls" "crypto/x509" "net" - "net/netip" "os" "strings" @@ -51,9 +50,7 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb if options.ServerName != "" { serverName = options.ServerName } else if serverAddress != "" { - if _, err := netip.ParseAddr(serverName); err != nil { - serverName = serverAddress - } + serverName = serverAddress } if serverName == "" && !options.Insecure { return nil, E.New("missing server_name or insecure=true") diff --git a/constant/dns.go b/constant/dns.go index 3907b8c15e..99461a270b 100644 --- a/constant/dns.go +++ b/constant/dns.go @@ -1,5 +1,34 @@ package constant +const ( + DefaultDNSTTL = 600 +) + +type DomainStrategy = uint8 + +const ( + DomainStrategyAsIS DomainStrategy = iota + DomainStrategyPreferIPv4 + DomainStrategyPreferIPv6 + DomainStrategyIPv4Only + DomainStrategyIPv6Only +) + +const ( + DNSTypeLegacy = "legacy" + DNSTypeUDP = "udp" + DNSTypeTCP = "tcp" + DNSTypeTLS = "tls" + DNSTypeHTTPS = "https" + DNSTypeQUIC = "quic" + DNSTypeHTTP3 = "h3" + DNSTypeHosts = "hosts" + DNSTypeLocal = "local" + DNSTypePreDefined = "predefined" + DNSTypeFakeIP = "fakeip" + DNSTypeDHCP = "dhcp" +) + const ( DNSProviderAliDNS = "alidns" DNSProviderCloudflare = "cloudflare" diff --git a/dns/client.go b/dns/client.go new file mode 100644 index 0000000000..79b6fce574 --- /dev/null +++ b/dns/client.go @@ -0,0 +1,563 @@ +package dns + +import ( + "context" + "net" + "net/netip" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/task" + "github.com/sagernet/sing/contrab/freelru" + "github.com/sagernet/sing/contrab/maphash" + + "github.com/miekg/dns" +) + +var ( + ErrNoRawSupport = E.New("no raw query support by current transport") + ErrNotCached = E.New("not cached") + ErrResponseRejected = E.New("response rejected") + ErrResponseRejectedCached = E.Extend(ErrResponseRejected, "cached") +) + +var _ adapter.DNSClient = (*Client)(nil) + +type Client struct { + timeout time.Duration + disableCache bool + disableExpire bool + independentCache bool + rdrc adapter.RDRCStore + initRDRCFunc func() adapter.RDRCStore + logger logger.ContextLogger + cache freelru.Cache[dns.Question, *dns.Msg] + transportCache freelru.Cache[transportCacheKey, *dns.Msg] +} + +type ClientOptions struct { + Timeout time.Duration + DisableCache bool + DisableExpire bool + IndependentCache bool + CacheCapacity uint32 + RDRC func() adapter.RDRCStore + Logger logger.ContextLogger +} + +func NewClient(options ClientOptions) *Client { + client := &Client{ + timeout: options.Timeout, + disableCache: options.DisableCache, + disableExpire: options.DisableExpire, + independentCache: options.IndependentCache, + initRDRCFunc: options.RDRC, + logger: options.Logger, + } + if client.timeout == 0 { + client.timeout = C.DNSTimeout + } + cacheCapacity := options.CacheCapacity + if cacheCapacity < 1024 { + cacheCapacity = 1024 + } + if !client.disableCache { + if !client.independentCache { + client.cache = common.Must1(freelru.NewSharded[dns.Question, *dns.Msg](cacheCapacity, maphash.NewHasher[dns.Question]().Hash32)) + } else { + client.transportCache = common.Must1(freelru.NewSharded[transportCacheKey, *dns.Msg](cacheCapacity, maphash.NewHasher[transportCacheKey]().Hash32)) + } + } + return client +} + +type transportCacheKey struct { + dns.Question + transportTag string +} + +func (c *Client) Start() { + if c.initRDRCFunc != nil { + c.rdrc = c.initRDRCFunc() + } +} + +func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) { + if len(message.Question) == 0 { + if c.logger != nil { + c.logger.WarnContext(ctx, "bad question size: ", len(message.Question)) + } + responseMessage := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: message.Id, + Response: true, + Rcode: dns.RcodeFormatError, + }, + Question: message.Question, + } + return &responseMessage, nil + } + question := message.Question[0] + if options.ClientSubnet.IsValid() { + message = SetClientSubnet(message, options.ClientSubnet, true) + } + isSimpleRequest := len(message.Question) == 1 && + len(message.Ns) == 0 && + len(message.Extra) == 0 && + !options.ClientSubnet.IsValid() + disableCache := !isSimpleRequest || c.disableCache || options.DisableCache + if !disableCache { + response, ttl := c.loadResponse(question, transport) + if response != nil { + logCachedResponse(c.logger, ctx, response, ttl) + response.Id = message.Id + return response, nil + } + } + if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only { + responseMessage := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: message.Id, + Response: true, + Rcode: dns.RcodeSuccess, + }, + Question: []dns.Question{question}, + } + if c.logger != nil { + c.logger.DebugContext(ctx, "strategy rejected") + } + return &responseMessage, nil + } + messageId := message.Id + contextTransport, clientSubnetLoaded := transportTagFromContext(ctx) + if clientSubnetLoaded && transport.Tag() == contextTransport { + return nil, E.New("DNS query loopback in transport[", contextTransport, "]") + } + ctx = contextWithTransportTag(ctx, transport.Tag()) + if responseChecker != nil && c.rdrc != nil { + rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype) + if rejected { + return nil, ErrResponseRejectedCached + } + } + ctx, cancel := context.WithTimeout(ctx, c.timeout) + response, err := transport.Exchange(ctx, message) + cancel() + if err != nil { + return nil, err + } + /*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA { + validResponse := response + loop: + for { + var ( + addresses int + queryCNAME string + ) + for _, rawRR := range validResponse.Answer { + switch rr := rawRR.(type) { + case *dns.A: + break loop + case *dns.AAAA: + break loop + case *dns.CNAME: + queryCNAME = rr.Target + } + } + if queryCNAME == "" { + break + } + exMessage := *message + exMessage.Question = []dns.Question{{ + Name: queryCNAME, + Qtype: question.Qtype, + }} + validResponse, err = c.Exchange(ctx, transport, &exMessage, options, responseChecker) + if err != nil { + return nil, err + } + } + if validResponse != response { + response.Answer = append(response.Answer, validResponse.Answer...) + } + }*/ + if responseChecker != nil { + addr, addrErr := MessageToAddresses(response) + if addrErr != nil || !responseChecker(addr) { + if c.rdrc != nil { + c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger) + } + logRejectedResponse(c.logger, ctx, response) + return response, ErrResponseRejected + } + } + if question.Qtype == dns.TypeHTTPS { + if options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only { + for _, rr := range response.Answer { + https, isHTTPS := rr.(*dns.HTTPS) + if !isHTTPS { + continue + } + content := https.SVCB + content.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool { + if options.Strategy == C.DomainStrategyIPv4Only { + return it.Key() != dns.SVCB_IPV6HINT + } else { + return it.Key() != dns.SVCB_IPV4HINT + } + }) + https.SVCB = content + } + } + } + var timeToLive uint32 + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive { + timeToLive = record.Header().Ttl + } + } + } + if options.RewriteTTL != nil { + timeToLive = *options.RewriteTTL + } + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + record.Header().Ttl = timeToLive + } + } + response.Id = messageId + if !disableCache { + c.storeCache(transport, question, response, timeToLive) + } + logExchangedResponse(c.logger, ctx, response, timeToLive) + return response, err +} + +func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) { + domain = FqdnToDomain(domain) + dnsName := dns.Fqdn(domain) + if options.Strategy == C.DomainStrategyIPv4Only { + return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker) + } else if options.Strategy == C.DomainStrategyIPv6Only { + return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker) + } + var response4 []netip.Addr + var response6 []netip.Addr + var group task.Group + group.Append("exchange4", func(ctx context.Context) error { + response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker) + if err != nil { + return err + } + response4 = response + return nil + }) + group.Append("exchange6", func(ctx context.Context) error { + response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker) + if err != nil { + return err + } + response6 = response + return nil + }) + err := group.Run(ctx) + if len(response4) == 0 && len(response6) == 0 { + return nil, err + } + return sortAddresses(response4, response6, options.Strategy), nil +} + +func (c *Client) ClearCache() { + if c.cache != nil { + c.cache.Purge() + } + if c.transportCache != nil { + c.transportCache.Purge() + } +} + +func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool) { + if c.disableCache || c.independentCache { + return nil, false + } + if dns.IsFqdn(domain) { + domain = domain[:len(domain)-1] + } + dnsName := dns.Fqdn(domain) + if strategy == C.DomainStrategyIPv4Only { + response, err := c.questionCache(dns.Question{ + Name: dnsName, + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }, nil) + if err != ErrNotCached { + return response, true + } + } else if strategy == C.DomainStrategyIPv6Only { + response, err := c.questionCache(dns.Question{ + Name: dnsName, + Qtype: dns.TypeAAAA, + Qclass: dns.ClassINET, + }, nil) + if err != ErrNotCached { + return response, true + } + } else { + response4, _ := c.questionCache(dns.Question{ + Name: dnsName, + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }, nil) + response6, _ := c.questionCache(dns.Question{ + Name: dnsName, + Qtype: dns.TypeAAAA, + Qclass: dns.ClassINET, + }, nil) + if len(response4) > 0 || len(response6) > 0 { + return sortAddresses(response4, response6, strategy), true + } + } + return nil, false +} + +func (c *Client) ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool) { + if c.disableCache || c.independentCache || len(message.Question) != 1 { + return nil, false + } + question := message.Question[0] + response, ttl := c.loadResponse(question, nil) + if response == nil { + return nil, false + } + logCachedResponse(c.logger, ctx, response, ttl) + response.Id = message.Id + return response, true +} + +func sortAddresses(response4 []netip.Addr, response6 []netip.Addr, strategy C.DomainStrategy) []netip.Addr { + if strategy == C.DomainStrategyPreferIPv6 { + return append(response6, response4...) + } else { + return append(response4, response6...) + } +} + +func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Question, message *dns.Msg, timeToLive uint32) { + if timeToLive == 0 { + return + } + if c.disableExpire { + if !c.independentCache { + c.cache.Add(question, message) + } else { + c.transportCache.Add(transportCacheKey{ + Question: question, + transportTag: transport.Tag(), + }, message) + } + return + } + if !c.independentCache { + c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive)) + } else { + c.transportCache.AddWithLifetime(transportCacheKey{ + Question: question, + transportTag: transport.Tag(), + }, message, time.Second*time.Duration(timeToLive)) + } +} + +func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) { + question := dns.Question{ + Name: name, + Qtype: qType, + Qclass: dns.ClassINET, + } + disableCache := c.disableCache || options.DisableCache + if !disableCache { + cachedAddresses, err := c.questionCache(question, transport) + if err != ErrNotCached { + return cachedAddresses, err + } + } + message := dns.Msg{ + MsgHdr: dns.MsgHdr{ + RecursionDesired: true, + }, + Question: []dns.Question{question}, + } + response, err := c.Exchange(ctx, transport, &message, options, responseChecker) + if err != nil { + return nil, err + } + return MessageToAddresses(response) +} + +func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) { + response, _ := c.loadResponse(question, transport) + if response == nil { + return nil, ErrNotCached + } + return MessageToAddresses(response) +} + +func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) { + var ( + response *dns.Msg + loaded bool + ) + if c.disableExpire { + if !c.independentCache { + response, loaded = c.cache.Get(question) + } else { + response, loaded = c.transportCache.Get(transportCacheKey{ + Question: question, + transportTag: transport.Tag(), + }) + } + if !loaded { + return nil, 0 + } + return response.Copy(), 0 + } else { + var expireAt time.Time + if !c.independentCache { + response, expireAt, loaded = c.cache.GetWithLifetime(question) + } else { + response, expireAt, loaded = c.transportCache.GetWithLifetime(transportCacheKey{ + Question: question, + transportTag: transport.Tag(), + }) + } + if !loaded { + return nil, 0 + } + timeNow := time.Now() + if timeNow.After(expireAt) { + if !c.independentCache { + c.cache.Remove(question) + } else { + c.transportCache.Remove(transportCacheKey{ + Question: question, + transportTag: transport.Tag(), + }) + } + return nil, 0 + } + var originTTL int + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL { + originTTL = int(record.Header().Ttl) + } + } + } + nowTTL := int(expireAt.Sub(timeNow).Seconds()) + if nowTTL < 0 { + nowTTL = 0 + } + response = response.Copy() + if originTTL > 0 { + duration := uint32(originTTL - nowTTL) + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + record.Header().Ttl = record.Header().Ttl - duration + } + } + } else { + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + record.Header().Ttl = uint32(nowTTL) + } + } + } + return response, nowTTL + } +} + +func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) { + if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError { + return nil, RCodeError(response.Rcode) + } + addresses := make([]netip.Addr, 0, len(response.Answer)) + for _, rawAnswer := range response.Answer { + switch answer := rawAnswer.(type) { + case *dns.A: + addresses = append(addresses, M.AddrFromIP(answer.A)) + case *dns.AAAA: + addresses = append(addresses, M.AddrFromIP(answer.AAAA)) + case *dns.HTTPS: + for _, value := range answer.SVCB.Value { + if value.Key() == dns.SVCB_IPV4HINT || value.Key() == dns.SVCB_IPV6HINT { + addresses = append(addresses, common.Map(strings.Split(value.String(), ","), M.ParseAddr)...) + } + } + } + } + return addresses, nil +} + +func wrapError(err error) error { + switch dnsErr := err.(type) { + case *net.DNSError: + if dnsErr.IsNotFound { + return RCodeNameError + } + case *net.AddrError: + return RCodeNameError + } + return err +} + +type transportKey struct{} + +func contextWithTransportTag(ctx context.Context, transportTag string) context.Context { + return context.WithValue(ctx, transportKey{}, transportTag) +} + +func transportTagFromContext(ctx context.Context) (string, bool) { + value, loaded := ctx.Value(transportKey{}).(string) + return value, loaded +} + +func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg { + response := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: id, + Rcode: dns.RcodeSuccess, + Response: true, + }, + Question: []dns.Question{question}, + } + for _, address := range addresses { + if address.Is4() { + response.Answer = append(response.Answer, &dns.A{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + A: address.AsSlice(), + }) + } else { + response.Answer = append(response.Answer, &dns.AAAA{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + AAAA: address.AsSlice(), + }) + } + } + return &response +} diff --git a/dns/client_log.go b/dns/client_log.go new file mode 100644 index 0000000000..67d0070841 --- /dev/null +++ b/dns/client_log.go @@ -0,0 +1,69 @@ +package dns + +import ( + "context" + "strings" + + "github.com/sagernet/sing/common/logger" + + "github.com/miekg/dns" +) + +func logCachedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl int) { + if logger == nil || len(response.Question) == 0 { + return + } + domain := FqdnToDomain(response.Question[0].Name) + logger.DebugContext(ctx, "cached ", domain, " ", dns.RcodeToString[response.Rcode], " ", ttl) + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + logger.InfoContext(ctx, "cached ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String())) + } + } +} + +func logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) { + if logger == nil || len(response.Question) == 0 { + return + } + domain := FqdnToDomain(response.Question[0].Name) + logger.DebugContext(ctx, "exchanged ", domain, " ", dns.RcodeToString[response.Rcode], " ", ttl) + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + logger.InfoContext(ctx, "exchanged ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String())) + } + } +} + +func logRejectedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg) { + if logger == nil || len(response.Question) == 0 { + return + } + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + logger.InfoContext(ctx, "rejected ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String())) + } + } +} + +func FqdnToDomain(fqdn string) string { + if dns.IsFqdn(fqdn) { + return fqdn[:len(fqdn)-1] + } + return fqdn +} + +func FormatQuestion(string string) string { + for strings.HasPrefix(string, ";") { + string = string[1:] + } + string = strings.ReplaceAll(string, "\t", " ") + string = strings.ReplaceAll(string, "\n", " ") + string = strings.ReplaceAll(string, ";; ", " ") + string = strings.ReplaceAll(string, "; ", " ") + + for strings.Contains(string, " ") { + string = strings.ReplaceAll(string, " ", " ") + } + return strings.TrimSpace(string) +} diff --git a/dns/client_truncate.go b/dns/client_truncate.go new file mode 100644 index 0000000000..f00023f27b --- /dev/null +++ b/dns/client_truncate.go @@ -0,0 +1,31 @@ +package dns + +import ( + "github.com/sagernet/sing/common/buf" + + "github.com/miekg/dns" +) + +func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf.Buffer, error) { + maxLen := 512 + if edns0Option := request.IsEdns0(); edns0Option != nil { + if udpSize := int(edns0Option.UDPSize()); udpSize > 512 { + maxLen = udpSize + } + } + responseLen := response.Len() + if responseLen > maxLen { + copyResponse := *response + response = ©Response + response.Truncate(maxLen) + } + buffer := buf.NewSize(headroom*2 + 1 + responseLen) + buffer.Resize(headroom, 0) + rawMessage, err := response.PackBuffer(buffer.FreeBytes()) + if err != nil { + buffer.Release() + return nil, err + } + buffer.Truncate(len(rawMessage)) + return buffer, nil +} diff --git a/dns/extension_edns0_subnet.go b/dns/extension_edns0_subnet.go new file mode 100644 index 0000000000..1c4033d36e --- /dev/null +++ b/dns/extension_edns0_subnet.go @@ -0,0 +1,56 @@ +package dns + +import ( + "net/netip" + + "github.com/miekg/dns" +) + +func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg { + var ( + optRecord *dns.OPT + subnetOption *dns.EDNS0_SUBNET + ) +findExists: + for _, record := range message.Extra { + var isOPTRecord bool + if optRecord, isOPTRecord = record.(*dns.OPT); isOPTRecord { + for _, option := range optRecord.Option { + var isEDNS0Subnet bool + subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET) + if isEDNS0Subnet { + if !override { + return message + } + break findExists + } + } + } + } + if optRecord == nil { + exMessage := *message + message = &exMessage + optRecord = &dns.OPT{ + Hdr: dns.RR_Header{ + Name: ".", + Rrtype: dns.TypeOPT, + }, + } + message.Extra = append(message.Extra, optRecord) + } else { + message = message.Copy() + } + if subnetOption == nil { + subnetOption = new(dns.EDNS0_SUBNET) + optRecord.Option = append(optRecord.Option, subnetOption) + } + subnetOption.Code = dns.EDNS0SUBNET + if clientSubnet.Addr().Is4() { + subnetOption.Family = 1 + } else { + subnetOption.Family = 2 + } + subnetOption.SourceNetmask = uint8(clientSubnet.Bits()) + subnetOption.Address = clientSubnet.Addr().AsSlice() + return message +} diff --git a/dns/rcode.go b/dns/rcode.go new file mode 100644 index 0000000000..5b7e52ccb8 --- /dev/null +++ b/dns/rcode.go @@ -0,0 +1,33 @@ +package dns + +import F "github.com/sagernet/sing/common/format" + +const ( + RCodeSuccess RCodeError = 0 // NoError + RCodeFormatError RCodeError = 1 // FormErr + RCodeServerFailure RCodeError = 2 // ServFail + RCodeNameError RCodeError = 3 // NXDomain + RCodeNotImplemented RCodeError = 4 // NotImp + RCodeRefused RCodeError = 5 // Refused +) + +type RCodeError uint16 + +func (e RCodeError) Error() string { + switch e { + case RCodeSuccess: + return "success" + case RCodeFormatError: + return "format error" + case RCodeServerFailure: + return "server failure" + case RCodeNameError: + return "name error" + case RCodeNotImplemented: + return "not implemented" + case RCodeRefused: + return "refused" + default: + return F.ToString("unknown error: ", uint16(e)) + } +} diff --git a/dns/router.go b/dns/router.go new file mode 100644 index 0000000000..f6f42a3152 --- /dev/null +++ b/dns/router.go @@ -0,0 +1,444 @@ +package dns + +import ( + "context" + "errors" + "net/netip" + "strings" + "time" + + "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" + "github.com/sagernet/sing-tun" + "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" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/contrab/freelru" + "github.com/sagernet/sing/contrab/maphash" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSRouter = (*Router)(nil) + +type Router struct { + ctx context.Context + logger logger.ContextLogger + transport adapter.DNSTransportManager + outbound adapter.OutboundManager + client adapter.DNSClient + rules []adapter.DNSRule + defaultDomainStrategy C.DomainStrategy + dnsReverseMapping freelru.Cache[netip.Addr, string] + platformInterface platform.Interface +} + +func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router { + router := &Router{ + ctx: ctx, + logger: logFactory.NewLogger("dns"), + transport: service.FromContext[adapter.DNSTransportManager](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + rules: make([]adapter.DNSRule, 0, len(options.Rules)), + defaultDomainStrategy: C.DomainStrategy(options.Strategy), + } + router.client = NewClient(ClientOptions{ + DisableCache: options.DNSClientOptions.DisableCache, + DisableExpire: options.DNSClientOptions.DisableExpire, + IndependentCache: options.DNSClientOptions.IndependentCache, + CacheCapacity: options.DNSClientOptions.CacheCapacity, + RDRC: func() adapter.RDRCStore { + cacheFile := service.FromContext[adapter.CacheFile](ctx) + if cacheFile == nil { + return nil + } + if !cacheFile.StoreRDRC() { + return nil + } + return cacheFile + }, + Logger: router.logger, + }) + if options.ReverseMapping { + router.dnsReverseMapping = common.Must1(freelru.NewSharded[netip.Addr, string](1024, maphash.NewHasher[netip.Addr]().Hash32)) + } + return router +} + +func (r *Router) Initialize(rules []option.DNSRule) error { + for i, ruleOptions := range rules { + dnsRule, err := R.NewDNSRule(r.ctx, r.logger, ruleOptions, true) + if err != nil { + return E.Cause(err, "parse dns rule[", i, "]") + } + r.rules = append(r.rules, dnsRule) + } + return nil +} + +func (r *Router) Start(stage adapter.StartStage) error { + monitor := taskmonitor.New(r.logger, C.StartTimeout) + switch stage { + case adapter.StartStateStart: + monitor.Start("initialize DNS client") + r.client.Start() + monitor.Finish() + + for i, rule := range r.rules { + monitor.Start("initialize DNS rule[", i, "]") + err := rule.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "initialize DNS rule[", i, "]") + } + } + } + return nil +} + +func (r *Router) Close() error { + monitor := taskmonitor.New(r.logger, C.StopTimeout) + var err error + for i, rule := range r.rules { + monitor.Start("close dns rule[", i, "]") + err = E.Append(err, rule.Close(), func(err error) error { + return E.Cause(err, "close dns rule[", i, "]") + }) + monitor.Finish() + } + return err +} + +func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, isAddressQuery bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, adapter.DNSRule, int) { + metadata := adapter.ContextFrom(ctx) + if metadata == nil { + panic("no context") + } + var currentRuleIndex int + if ruleIndex != -1 { + currentRuleIndex = ruleIndex + 1 + } + for ; currentRuleIndex < len(r.rules); currentRuleIndex++ { + currentRule := r.rules[currentRuleIndex] + if currentRule.WithAddressLimit() && !isAddressQuery { + continue + } + metadata.ResetRuleCache() + if currentRule.Match(metadata) { + displayRuleIndex := currentRuleIndex + if displayRuleIndex != -1 { + displayRuleIndex += displayRuleIndex + 1 + } + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + } + switch action := currentRule.Action().(type) { + case *R.RuleActionDNSRoute: + transport, loaded := r.transport.Transport(action.Server) + if !loaded { + r.logger.ErrorContext(ctx, "transport not found: ", action.Server) + continue + } + isFakeIP := transport.Type() == C.DNSTypeFakeIP + if isFakeIP && !allowFakeIP { + continue + } + if action.Strategy != C.DomainStrategyAsIS { + options.Strategy = action.Strategy + } + if isFakeIP || action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL + } + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet + } + if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy { + if options.Strategy == C.DomainStrategyAsIS { + options.Strategy = legacyTransport.LegacyStrategy() + } + if !options.ClientSubnet.IsValid() { + options.ClientSubnet = legacyTransport.LegacyClientSubnet() + } + } + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + return transport, currentRule, currentRuleIndex + case *R.RuleActionDNSRouteOptions: + if action.Strategy != C.DomainStrategyAsIS { + options.Strategy = action.Strategy + } + if action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL + } + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet + } + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + case *R.RuleActionReject: + r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) + return nil, currentRule, currentRuleIndex + } + } + } + return r.transport.Default(), nil, -1 +} + +func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) { + if len(message.Question) != 1 { + r.logger.WarnContext(ctx, "bad question size: ", len(message.Question)) + responseMessage := mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Response: true, + Rcode: mDNS.RcodeFormatError, + }, + Question: message.Question, + } + return &responseMessage, nil + } + r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String())) + var ( + transport adapter.DNSTransport + err error + ) + response, cached := r.client.ExchangeCache(ctx, message) + if !cached { + var metadata *adapter.InboundContext + ctx, metadata = adapter.ExtendContext(ctx) + metadata.Destination = M.Socksaddr{} + metadata.QueryType = message.Question[0].Qtype + switch metadata.QueryType { + case mDNS.TypeA: + metadata.IPVersion = 4 + case mDNS.TypeAAAA: + metadata.IPVersion = 6 + } + metadata.Domain = FqdnToDomain(message.Question[0].Name) + if options.Transport != nil { + transport = options.Transport + if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy { + if options.Strategy == C.DomainStrategyAsIS { + options.Strategy = legacyTransport.LegacyStrategy() + } + if !options.ClientSubnet.IsValid() { + options.ClientSubnet = legacyTransport.LegacyClientSubnet() + } + } + if options.Strategy == C.DomainStrategyAsIS { + options.Strategy = r.defaultDomainStrategy + } + response, err = r.client.Exchange(ctx, transport, message, options, nil) + } else { + var ( + rule adapter.DNSRule + ruleIndex int + ) + ruleIndex = -1 + for { + dnsCtx := adapter.OverrideContext(ctx) + dnsOptions := options + transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions) + if rule != nil { + switch action := rule.Action().(type) { + case *R.RuleActionReject: + switch action.Method { + case C.RuleActionRejectMethodDefault: + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Rcode: mDNS.RcodeRefused, + Response: true, + }, + Question: []mDNS.Question{message.Question[0]}, + }, nil + case C.RuleActionRejectMethodDrop: + return nil, tun.ErrDrop + } + } + } + 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) + } + } + if dnsOptions.Strategy == C.DomainStrategyAsIS { + dnsOptions.Strategy = r.defaultDomainStrategy + } + response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck) + var rejected bool + if err != nil { + if errors.Is(err, ErrResponseRejectedCached) { + rejected = true + r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())), " (cached)") + } else if errors.Is(err, ErrResponseRejected) { + rejected = true + r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String()))) + } else if len(message.Question) > 0 { + r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String()))) + } else { + r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ")) + } + } + if responseCheck != nil && rejected { + continue + } + break + } + } + } + if err != nil { + return nil, err + } + if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { + if transport == nil || transport.Type() != C.DNSTypeFakeIP { + for _, answer := range response.Answer { + switch record := answer.(type) { + case *mDNS.A: + r.dnsReverseMapping.AddWithLifetime(M.AddrFromIP(record.A), FqdnToDomain(record.Hdr.Name), time.Duration(record.Hdr.Ttl)*time.Second) + case *mDNS.AAAA: + r.dnsReverseMapping.AddWithLifetime(M.AddrFromIP(record.AAAA), FqdnToDomain(record.Hdr.Name), time.Duration(record.Hdr.Ttl)*time.Second) + } + } + } + } + return response, nil +} + +func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) { + var ( + responseAddrs []netip.Addr + cached bool + err error + ) + printResult := func() { + if err != nil { + if errors.Is(err, ErrResponseRejectedCached) { + r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)") + } else if errors.Is(err, ErrResponseRejected) { + r.logger.DebugContext(ctx, "response rejected for ", domain) + } else { + r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) + } + } else if len(responseAddrs) == 0 { + r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") + err = RCodeNameError + } + } + responseAddrs, cached = r.client.LookupCache(domain, options.Strategy) + if cached { + if len(responseAddrs) == 0 { + return nil, RCodeNameError + } + return responseAddrs, nil + } + r.logger.DebugContext(ctx, "lookup domain ", domain) + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Destination = M.Socksaddr{} + metadata.Domain = FqdnToDomain(domain) + if options.Transport != nil { + transport := options.Transport + if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy { + if options.Strategy == C.DomainStrategyAsIS { + options.Strategy = r.defaultDomainStrategy + } + if !options.ClientSubnet.IsValid() { + options.ClientSubnet = legacyTransport.LegacyClientSubnet() + } + } + if options.Strategy == C.DomainStrategyAsIS { + options.Strategy = r.defaultDomainStrategy + } + responseAddrs, err = r.client.Lookup(ctx, transport, domain, options, nil) + } else { + var ( + transport adapter.DNSTransport + rule adapter.DNSRule + ruleIndex int + ) + ruleIndex = -1 + for { + dnsCtx := adapter.OverrideContext(ctx) + transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &options) + if rule != nil { + switch action := rule.Action().(type) { + case *R.RuleActionReject: + switch action.Method { + case C.RuleActionRejectMethodDefault: + return nil, nil + case C.RuleActionRejectMethodDrop: + return nil, tun.ErrDrop + } + } + } + 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) + } + } + if options.Strategy == C.DomainStrategyAsIS { + options.Strategy = r.defaultDomainStrategy + } + responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, options, responseCheck) + if responseCheck == nil || err == nil { + break + } + printResult() + } + } + printResult() + if len(responseAddrs) > 0 { + r.logger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " ")) + } + return responseAddrs, err +} + +func isAddressQuery(message *mDNS.Msg) bool { + for _, question := range message.Question { + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA || question.Qtype == mDNS.TypeHTTPS { + return true + } + } + return false +} + +func (r *Router) ClearCache() { + r.client.ClearCache() + if r.platformInterface != nil { + r.platformInterface.ClearDNSCache() + } +} + +func (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) { + if r.dnsReverseMapping == nil { + return "", false + } + domain, loaded := r.dnsReverseMapping.Get(ip) + return domain, loaded +} + +func (r *Router) ResetNetwork() { + r.ClearCache() + for _, transport := range r.transport.Transports() { + transport.Reset() + } +} diff --git a/transport/dhcp/server.go b/dns/transport/dhcp/dhcp.go similarity index 66% rename from transport/dhcp/server.go rename to dns/transport/dhcp/dhcp.go index 8b9187f0fb..c75d736981 100644 --- a/transport/dhcp/server.go +++ b/dns/transport/dhcp/dhcp.go @@ -3,9 +3,6 @@ package dhcp import ( "context" "net" - "net/netip" - "net/url" - "os" "runtime" "strings" "sync" @@ -14,13 +11,18 @@ import ( "github.com/sagernet/sing-box/adapter" "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/dns/transport" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" 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/task" "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" @@ -29,65 +31,57 @@ import ( mDNS "github.com/miekg/dns" ) -func init() { - dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) { - return NewTransport(options) - }) +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.DHCPDNSServerOptions](registry, C.DNSTypeDHCP, NewTransport) } +var _ adapter.DNSTransport = (*Transport)(nil) + type Transport struct { - options dns.TransportOptions - router adapter.Router + dns.TransportAdapter + ctx context.Context + dialer N.Dialer + logger logger.ContextLogger networkManager adapter.NetworkManager interfaceName string - autoInterface bool interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] - transports []dns.Transport + transports []adapter.DNSTransport updateAccess sync.Mutex updatedAt time.Time } -func NewTransport(options dns.TransportOptions) (*Transport, error) { - linkURL, err := url.Parse(options.Address) +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewLocalDialer(ctx, options.LocalDNSServerOptions) if err != nil { return nil, err } - if linkURL.Host == "" { - return nil, E.New("missing interface name for DHCP") - } - transport := &Transport{ - options: options, - networkManager: service.FromContext[adapter.NetworkManager](options.Context), - interfaceName: linkURL.Host, - autoInterface: linkURL.Host == "auto", - } - return transport, nil + return &Transport{ + TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions), + ctx: ctx, + dialer: transportDialer, + logger: logger, + networkManager: service.FromContext[adapter.NetworkManager](ctx), + interfaceName: options.Interface, + }, nil } -func (t *Transport) Name() string { - return t.options.Name -} - -func (t *Transport) Start() error { +func (t *Transport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } err := t.fetchServers() if err != nil { return err } - if t.autoInterface { + if t.interfaceName == "" { t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) } return nil } -func (t *Transport) Reset() { - for _, transport := range t.transports { - transport.Reset() - } -} - func (t *Transport) Close() error { for _, transport := range t.transports { - transport.Close() + transport.Reset() } if t.interfaceCallback != nil { t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) @@ -95,8 +89,10 @@ func (t *Transport) Close() error { return nil } -func (t *Transport) Raw() bool { - return true +func (t *Transport) Reset() { + for _, transport := range t.transports { + transport.Reset() + } } func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { @@ -120,7 +116,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, } func (t *Transport) fetchInterface() (*control.Interface, error) { - if t.autoInterface { + if t.interfaceName == "" { if t.networkManager.InterfaceMonitor() == nil { return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface") } @@ -152,8 +148,8 @@ func (t *Transport) updateServers() error { return E.Cause(err, "dhcp: prepare interface") } - t.options.Logger.Info("dhcp: query DNS servers on ", iface.Name) - fetchCtx, cancel := context.WithTimeout(t.options.Context, C.DHCPTimeout) + t.logger.Info("dhcp: query DNS servers on ", iface.Name) + fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout) err = t.fetchServers0(fetchCtx, iface) cancel() if err != nil { @@ -169,7 +165,7 @@ func (t *Transport) updateServers() error { func (t *Transport) interfaceUpdated(defaultInterface *control.Interface, flags int) { err := t.updateServers() if err != nil { - t.options.Logger.Error("update servers: ", err) + t.logger.Error("update servers: ", err) } } @@ -181,7 +177,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface) if runtime.GOOS == "linux" || runtime.GOOS == "android" { listenAddr = "255.255.255.255:68" } - packetConn, err := listener.ListenPacket(t.options.Context, "udp4", listenAddr) + packetConn, err := listener.ListenPacket(t.ctx, "udp4", listenAddr) if err != nil { return err } @@ -219,17 +215,17 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes()) if err != nil { - t.options.Logger.Trace("dhcp: parse DHCP response: ", err) + t.logger.Trace("dhcp: parse DHCP response: ", err) return err } if dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer { - t.options.Logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType()) + t.logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType()) continue } if dhcpPacket.TransactionID != transactionID { - t.options.Logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID) + t.logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID) continue } @@ -237,44 +233,27 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne if len(dns) == 0 { return nil } - - var addrs []netip.Addr - for _, ip := range dns { - addr, _ := netip.AddrFromSlice(ip) - addrs = append(addrs, addr.Unmap()) - } - return t.recreateServers(iface, addrs) + return t.recreateServers(iface, common.Map(dns, func(it net.IP) M.Socksaddr { + return M.SocksaddrFrom(M.AddrFromIP(it), 53) + })) } } -func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []netip.Addr) error { +func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.Socksaddr) error { if len(serverAddrs) > 0 { - t.options.Logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string { - return it.String() - }), ","), "]") + t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "]") } - serverDialer := common.Must1(dialer.NewDefault(t.options.Context, option.DialerOptions{ + serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{ BindInterface: iface.Name, UDPFragmentDefault: true, })) - var transports []dns.Transport + var transports []adapter.DNSTransport for _, serverAddr := range serverAddrs { - newOptions := t.options - newOptions.Address = serverAddr.String() - newOptions.Dialer = serverDialer - serverTransport, err := dns.NewUDPTransport(newOptions) - if err != nil { - return E.Cause(err, "create UDP transport from DHCP result: ", serverAddr) - } - transports = append(transports, serverTransport) + transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr)) } for _, transport := range t.transports { - transport.Close() + transport.Reset() } t.transports = transports return nil } - -func (t *Transport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { - return nil, os.ErrInvalid -} diff --git a/dns/transport/fakeip/fakeip.go b/dns/transport/fakeip/fakeip.go new file mode 100644 index 0000000000..07f0fd0973 --- /dev/null +++ b/dns/transport/fakeip/fakeip.go @@ -0,0 +1,67 @@ +package fakeip + +import ( + "context" + "net/netip" + + "github.com/sagernet/sing-box/adapter" + 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" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + + mDNS "github.com/miekg/dns" +) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.FakeIPDNSServerOptions](registry, C.DNSTypeFakeIP, NewTransport) +} + +var _ adapter.FakeIPTransport = (*Transport)(nil) + +type Transport struct { + dns.TransportAdapter + logger logger.ContextLogger + store adapter.FakeIPStore +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.FakeIPDNSServerOptions) (adapter.DNSTransport, error) { + store := NewStore(ctx, logger, options.Inet4Range.Build(netip.Prefix{}), options.Inet6Range.Build(netip.Prefix{})) + return &Transport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypeFakeIP, tag, nil), + logger: logger, + store: store, + }, nil +} + +func (t *Transport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return t.store.Start() +} + +func (t *Transport) Close() error { + return t.store.Close() +} + +func (t *Transport) 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 { + return nil, E.New("only IP queries are supported by fakeip") + } + address, err := t.store.Create(dns.FqdnToDomain(question.Name), question.Qtype == mDNS.TypeAAAA) + if err != nil { + return nil, err + } + return dns.FixedResponse(message.Id, question, []netip.Addr{address}, C.DefaultDNSTTL), nil +} + +func (t *Transport) Store() adapter.FakeIPStore { + return t.store +} diff --git a/transport/fakeip/memory.go b/dns/transport/fakeip/memory.go similarity index 100% rename from transport/fakeip/memory.go rename to dns/transport/fakeip/memory.go diff --git a/transport/fakeip/store.go b/dns/transport/fakeip/store.go similarity index 100% rename from transport/fakeip/store.go rename to dns/transport/fakeip/store.go diff --git a/dns/transport/hosts/hosts.go b/dns/transport/hosts/hosts.go new file mode 100644 index 0000000000..29f6778a7a --- /dev/null +++ b/dns/transport/hosts/hosts.go @@ -0,0 +1,63 @@ +package hosts + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + 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" + + mDNS "github.com/miekg/dns" +) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.HostsDNSServerOptions](registry, C.DNSTypeHosts, NewTransport) +} + +var _ adapter.DNSTransport = (*Transport)(nil) + +type Transport struct { + dns.TransportAdapter + files []*File +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) { + var files []*File + if len(options.Path) == 0 { + files = append(files, NewFile(DefaultPath)) + } else { + for _, path := range options.Path { + files = append(files, NewFile(path)) + } + } + return &Transport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil), + files: files, + }, nil +} + +func (t *Transport) Reset() { +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + question := message.Question[0] + domain := dns.FqdnToDomain(question.Name) + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { + for _, file := range t.files { + addresses := file.Lookup(domain) + if len(addresses) > 0 { + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil + } + } + } + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Rcode: mDNS.RcodeNameError, + Response: true, + }, + Question: []mDNS.Question{question}, + }, nil +} diff --git a/dns/transport/hosts/hosts_file.go b/dns/transport/hosts/hosts_file.go new file mode 100644 index 0000000000..7ff34f692c --- /dev/null +++ b/dns/transport/hosts/hosts_file.go @@ -0,0 +1,102 @@ +package hosts + +import ( + "bufio" + "errors" + "io" + "net/netip" + "os" + "strings" + "sync" + "time" + + "github.com/miekg/dns" +) + +const cacheMaxAge = 5 * time.Second + +type File struct { + path string + access sync.Mutex + byName map[string][]netip.Addr + expire time.Time + modTime time.Time + size int64 +} + +func NewFile(path string) *File { + return &File{ + path: path, + } +} + +func (f *File) Lookup(name string) []netip.Addr { + f.access.Lock() + defer f.access.Unlock() + f.update() + return f.byName[name] +} + +func (f *File) update() { + now := time.Now() + if now.Before(f.expire) && len(f.byName) > 0 { + return + } + stat, err := os.Stat(f.path) + if err != nil { + return + } + if f.modTime.Equal(stat.ModTime()) && f.size == stat.Size() { + f.expire = now.Add(cacheMaxAge) + return + } + byName := make(map[string][]netip.Addr) + file, err := os.Open(f.path) + if err != nil { + return + } + defer file.Close() + reader := bufio.NewReader(file) + var ( + prefix []byte + line []byte + isPrefix bool + ) + for { + line, isPrefix, err = reader.ReadLine() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return + } + if isPrefix { + prefix = append(prefix, line...) + continue + } else if len(prefix) > 0 { + line = append(prefix, line...) + prefix = nil + } + commentIndex := strings.IndexRune(string(line), '#') + if commentIndex != -1 { + line = line[:commentIndex] + } + fields := strings.Fields(string(line)) + if len(fields) < 2 { + continue + } + var addr netip.Addr + addr, err = netip.ParseAddr(fields[0]) + if err != nil { + continue + } + for index := 1; index < len(fields); index++ { + canonicalName := dns.CanonicalName(fields[index]) + byName[canonicalName] = append(byName[canonicalName], addr) + } + } + f.expire = now.Add(cacheMaxAge) + f.modTime = stat.ModTime() + f.size = stat.Size() + f.byName = byName +} diff --git a/dns/transport/hosts/hosts_test.go b/dns/transport/hosts/hosts_test.go new file mode 100644 index 0000000000..944aa437a4 --- /dev/null +++ b/dns/transport/hosts/hosts_test.go @@ -0,0 +1,16 @@ +package hosts_test + +import ( + "net/netip" + "testing" + + "github.com/sagernet/sing-box/dns/transport/hosts" + + "github.com/stretchr/testify/require" +) + +func TestHosts(t *testing.T) { + t.Parallel() + require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost.")) + require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost.")) +} diff --git a/dns/transport/hosts/hosts_unix.go b/dns/transport/hosts/hosts_unix.go new file mode 100644 index 0000000000..4caed8b406 --- /dev/null +++ b/dns/transport/hosts/hosts_unix.go @@ -0,0 +1,5 @@ +//go:build !windows + +package hosts + +var DefaultPath = "/etc/hosts" diff --git a/dns/transport/hosts/hosts_windows.go b/dns/transport/hosts/hosts_windows.go new file mode 100644 index 0000000000..3144e50d56 --- /dev/null +++ b/dns/transport/hosts/hosts_windows.go @@ -0,0 +1,17 @@ +package hosts + +import ( + "path/filepath" + + "golang.org/x/sys/windows" +) + +var DefaultPath string + +func init() { + systemDirectory, err := windows.GetSystemDirectory() + if err != nil { + systemDirectory = "C:\\Windows\\System32" + } + DefaultPath = filepath.Join(systemDirectory, "Drivers/etc/hosts") +} diff --git a/dns/transport/hosts/testdata/hosts b/dns/transport/hosts/testdata/hosts new file mode 100644 index 0000000000..9ddcc8c137 --- /dev/null +++ b/dns/transport/hosts/testdata/hosts @@ -0,0 +1,2 @@ +127.0.0.1 localhost +::1 localhost diff --git a/dns/transport/https.go b/dns/transport/https.go new file mode 100644 index 0000000000..1cfb25740b --- /dev/null +++ b/dns/transport/https.go @@ -0,0 +1,204 @@ +package transport + +import ( + "bytes" + "context" + "io" + "net" + "net/http" + "net/url" + "strconv" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "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" + "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" + sHTTP "github.com/sagernet/sing/protocol/http" + + mDNS "github.com/miekg/dns" + "golang.org/x/net/http2" +) + +const MimeType = "application/dns-message" + +var _ adapter.DNSTransport = (*HTTPSTransport)(nil) + +func RegisterHTTPS(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTPS, NewHTTPS) +} + +type HTTPSTransport struct { + dns.TransportAdapter + logger logger.ContextLogger + dialer N.Dialer + destination *url.URL + headers http.Header + transport *http.Transport +} + +func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions) + if err != nil { + return nil, err + } + tlsOptions := common.PtrValueOrDefault(options.TLS) + tlsOptions.Enabled = true + tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions) + if err != nil { + return nil, err + } + if common.Error(tlsConfig.Config()) == nil && !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) { + tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), http2.NextProtoTLS)) + } + if !common.Contains(tlsConfig.NextProtos(), "http/1.1") { + tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), "http/1.1")) + } + headers := options.Headers.Build() + host := headers.Get("Host") + if host != "" { + headers.Del("Host") + } else { + if tlsConfig.ServerName() != "" { + host = tlsConfig.ServerName() + } else { + host = options.Server + } + } + destinationURL := url.URL{ + Scheme: "https", + Host: host, + } + if destinationURL.Host == "" { + destinationURL.Host = options.Server + } + if options.ServerPort != 0 && options.ServerPort != 443 { + destinationURL.Host = net.JoinHostPort(destinationURL.Host, strconv.Itoa(int(options.ServerPort))) + } + path := options.Path + if path == "" { + path = "/dns-query" + } + err = sHTTP.URLSetPath(&destinationURL, path) + if err != nil { + return nil, err + } + serverAddr := options.ServerOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 443 + } + return NewHTTPSRaw( + dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions), + logger, + transportDialer, + &destinationURL, + headers, + serverAddr, + tlsConfig, + ), nil +} + +func NewHTTPSRaw( + adapter dns.TransportAdapter, + logger log.ContextLogger, + dialer N.Dialer, + destination *url.URL, + headers http.Header, + serverAddr M.Socksaddr, + tlsConfig tls.Config, +) *HTTPSTransport { + var transport *http.Transport + if tlsConfig != nil { + transport = &http.Transport{ + ForceAttemptHTTP2: true, + DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + tcpConn, hErr := dialer.DialContext(ctx, network, serverAddr) + if hErr != nil { + return nil, hErr + } + tlsConn, hErr := aTLS.ClientHandshake(ctx, tcpConn, tlsConfig) + if hErr != nil { + tcpConn.Close() + return nil, hErr + } + return tlsConn, nil + }, + } + } else { + transport = &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, network, serverAddr) + }, + } + } + return &HTTPSTransport{ + TransportAdapter: adapter, + logger: logger, + dialer: dialer, + destination: destination, + headers: headers, + transport: transport, + } +} + +func (t *HTTPSTransport) Reset() { + t.transport.CloseIdleConnections() + t.transport = t.transport.Clone() +} + +func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + exMessage := *message + exMessage.Id = 0 + exMessage.Compress = true + requestBuffer := buf.NewSize(1 + message.Len()) + rawMessage, err := exMessage.PackBuffer(requestBuffer.FreeBytes()) + if err != nil { + requestBuffer.Release() + return nil, err + } + request, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage)) + if err != nil { + requestBuffer.Release() + return nil, err + } + request.Header = t.headers.Clone() + request.Header.Set("Content-Type", MimeType) + request.Header.Set("Accept", MimeType) + response, err := t.transport.RoundTrip(request) + requestBuffer.Release() + if err != nil { + return nil, err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return nil, E.New("unexpected status: ", response.Status) + } + var responseMessage mDNS.Msg + if response.ContentLength > 0 { + responseBuffer := buf.NewSize(int(response.ContentLength)) + _, 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 { + return nil, err + } + err = responseMessage.Unpack(rawMessage) + } + if err != nil { + return nil, err + } + return &responseMessage, nil +} diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go new file mode 100644 index 0000000000..e5e8aef97a --- /dev/null +++ b/dns/transport/local/local.go @@ -0,0 +1,197 @@ +package local + +import ( + "context" + "math/rand" + "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/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" + 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 { + dns.TransportAdapter + hosts *hosts.File + dialer N.Dialer +} + +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 + } + return &Transport{ + TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), + hosts: hosts.NewFile(hosts.DefaultPath), + dialer: transportDialer, + }, nil +} + +func (t *Transport) Reset() { +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + question := message.Question[0] + domain := dns.FqdnToDomain(question.Name) + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { + addresses := t.hosts.Lookup(domain) + if len(addresses) > 0 { + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil + } + } + systemConfig := getSystemDNSConfig() + 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 { + addresses, _ := dns.MessageToAddresses(response) + if len(addresses) == 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) { + var networks []string + if useTCP { + networks = []string{N.NetworkTCP} + } else { + networks = []string{N.NetworkUDP, N.NetworkTCP} + } + request := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: uint16(rand.Uint32()), + RecursionDesired: true, + AuthenticatedData: ad, + }, + Question: []mDNS.Question{question}, + Compress: true, + } + request.SetEdns0(maxDNSPacketSize, false) + buffer := buf.Get(buf.UDPBufferSize) + defer buf.Put(buffer) + for _, network := range networks { + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout)) + defer cancel() + conn, err := t.dialer.DialContext(ctx, network, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + conn.SetDeadline(deadline) + } + rawMessage, err := request.PackBuffer(buffer) + if err != nil { + return nil, E.Cause(err, "pack request") + } + _, err = conn.Write(rawMessage) + if err != nil { + return nil, E.Cause(err, "write request") + } + n, err := conn.Read(buffer) + if err != nil { + 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 && network == N.NetworkUDP { + continue + } + return &response, nil + } + panic("unexpected") +} diff --git a/dns/transport/local/resolv.go b/dns/transport/local/resolv.go new file mode 100644 index 0000000000..5484d0ecd2 --- /dev/null +++ b/dns/transport/local/resolv.go @@ -0,0 +1,146 @@ +package local + +import ( + "os" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" +) + +const ( + // net.maxDNSPacketSize + maxDNSPacketSize = 1232 +) + +type resolverConfig struct { + initOnce sync.Once + ch chan struct{} + lastChecked time.Time + dnsConfig atomic.Pointer[dnsConfig] +} + +var resolvConf resolverConfig + +func getSystemDNSConfig() *dnsConfig { + resolvConf.tryUpdate("/etc/resolv.conf") + return resolvConf.dnsConfig.Load() +} + +func (conf *resolverConfig) init() { + conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf")) + conf.lastChecked = time.Now() + conf.ch = make(chan struct{}, 1) +} + +func (conf *resolverConfig) tryUpdate(name string) { + conf.initOnce.Do(conf.init) + + if conf.dnsConfig.Load().noReload { + return + } + if !conf.tryAcquireSema() { + return + } + defer conf.releaseSema() + + now := time.Now() + if conf.lastChecked.After(now.Add(-5 * time.Second)) { + return + } + conf.lastChecked = now + if runtime.GOOS != "windows" { + var mtime time.Time + if fi, err := os.Stat(name); err == nil { + mtime = fi.ModTime() + } + if mtime.Equal(conf.dnsConfig.Load().mtime) { + return + } + } + dnsConf := dnsReadConfig(name) + conf.dnsConfig.Store(dnsConf) +} + +func (conf *resolverConfig) tryAcquireSema() bool { + select { + case conf.ch <- struct{}{}: + return true + default: + return false + } +} + +func (conf *resolverConfig) releaseSema() { + <-conf.ch +} + +type dnsConfig struct { + servers []string + search []string + ndots int + timeout time.Duration + attempts int + rotate bool + unknownOpt bool + lookup []string + err error + mtime time.Time + soffset uint32 + singleRequest bool + useTCP bool + trustAD bool + noReload bool +} + +func (c *dnsConfig) serverOffset() uint32 { + if c.rotate { + return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start + } + return 0 +} + +func (conf *dnsConfig) nameList(name string) []string { + l := len(name) + rooted := l > 0 && name[l-1] == '.' + if l > 254 || l == 254 && !rooted { + return nil + } + + if rooted { + if avoidDNS(name) { + return nil + } + return []string{name} + } + + hasNdots := strings.Count(name, ".") >= conf.ndots + name += "." + // l++ + + names := make([]string, 0, 1+len(conf.search)) + if hasNdots && !avoidDNS(name) { + names = append(names, name) + } + for _, suffix := range conf.search { + fqdn := name + suffix + if !avoidDNS(fqdn) && len(fqdn) <= 254 { + names = append(names, fqdn) + } + } + if !hasNdots && !avoidDNS(name) { + names = append(names, name) + } + return names +} + +func avoidDNS(name string) bool { + if name == "" { + return true + } + if name[len(name)-1] == '.' { + name = name[:len(name)-1] + } + return strings.HasSuffix(name, ".onion") +} diff --git a/dns/transport/local/resolv_unix.go b/dns/transport/local/resolv_unix.go new file mode 100644 index 0000000000..6594ae4100 --- /dev/null +++ b/dns/transport/local/resolv_unix.go @@ -0,0 +1,175 @@ +//go:build !windows + +package local + +import ( + "bufio" + "net" + "net/netip" + "os" + "strings" + "time" + _ "unsafe" +) + +func dnsReadConfig(name string) *dnsConfig { + conf := &dnsConfig{ + ndots: 1, + timeout: 5 * time.Second, + attempts: 2, + } + file, err := os.Open(name) + if err != nil { + conf.servers = defaultNS + conf.search = dnsDefaultSearch() + conf.err = err + return conf + } + defer file.Close() + fi, err := file.Stat() + if err == nil { + conf.mtime = fi.ModTime() + } else { + conf.servers = defaultNS + conf.search = dnsDefaultSearch() + conf.err = err + return conf + } + reader := bufio.NewReader(file) + var ( + prefix []byte + line []byte + isPrefix bool + ) + for { + line, isPrefix, err = reader.ReadLine() + if err != nil { + break + } + if isPrefix { + prefix = append(prefix, line...) + continue + } else if len(prefix) > 0 { + line = append(prefix, line...) + prefix = nil + } + if len(line) > 0 && (line[0] == ';' || line[0] == '#') { + continue + } + f := strings.Fields(string(line)) + if len(f) < 1 { + continue + } + switch f[0] { + case "nameserver": + if len(f) > 1 && len(conf.servers) < 3 { + if _, err := netip.ParseAddr(f[1]); err == nil { + conf.servers = append(conf.servers, net.JoinHostPort(f[1], "53")) + } + } + case "domain": + if len(f) > 1 { + conf.search = []string{ensureRooted(f[1])} + } + + case "search": + conf.search = make([]string, 0, len(f)-1) + for i := 1; i < len(f); i++ { + name := ensureRooted(f[i]) + if name == "." { + continue + } + conf.search = append(conf.search, name) + } + + case "options": + for _, s := range f[1:] { + switch { + case strings.HasPrefix(s, "ndots:"): + n, _, _ := dtoi(s[6:]) + if n < 0 { + n = 0 + } else if n > 15 { + n = 15 + } + conf.ndots = n + case strings.HasPrefix(s, "timeout:"): + n, _, _ := dtoi(s[8:]) + if n < 1 { + n = 1 + } + conf.timeout = time.Duration(n) * time.Second + case strings.HasPrefix(s, "attempts:"): + n, _, _ := dtoi(s[9:]) + if n < 1 { + n = 1 + } + conf.attempts = n + case s == "rotate": + conf.rotate = true + case s == "single-request" || s == "single-request-reopen": + conf.singleRequest = true + case s == "use-vc" || s == "usevc" || s == "tcp": + conf.useTCP = true + case s == "trust-ad": + conf.trustAD = true + case s == "edns0": + case s == "no-reload": + conf.noReload = true + default: + conf.unknownOpt = true + } + } + + case "lookup": + conf.lookup = f[1:] + + default: + conf.unknownOpt = true + } + } + if len(conf.servers) == 0 { + conf.servers = defaultNS + } + if len(conf.search) == 0 { + conf.search = dnsDefaultSearch() + } + return conf +} + +//go:linkname defaultNS net.defaultNS +var defaultNS []string + +func dnsDefaultSearch() []string { + hn, err := os.Hostname() + if err != nil { + return nil + } + if i := strings.IndexRune(hn, '.'); i >= 0 && i < len(hn)-1 { + return []string{ensureRooted(hn[i+1:])} + } + return nil +} + +func ensureRooted(s string) string { + if len(s) > 0 && s[len(s)-1] == '.' { + return s + } + return s + "." +} + +const big = 0xFFFFFF + +func dtoi(s string) (n int, i int, ok bool) { + n = 0 + for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + n = n*10 + int(s[i]-'0') + if n >= big { + return big, i, false + } + } + if i == 0 { + return 0, 0, false + } + return n, i, true +} diff --git a/dns/transport/local/resolv_windows.go b/dns/transport/local/resolv_windows.go new file mode 100644 index 0000000000..577e7a129e --- /dev/null +++ b/dns/transport/local/resolv_windows.go @@ -0,0 +1,100 @@ +package local + +import ( + "net" + "net/netip" + "os" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/windows" +) + +func dnsReadConfig(_ string) *dnsConfig { + conf := &dnsConfig{ + ndots: 1, + timeout: 5 * time.Second, + attempts: 2, + } + defer func() { + if len(conf.servers) == 0 { + conf.servers = defaultNS + } + }() + aas, err := adapterAddresses() + if err != nil { + return nil + } + + for _, aa := range aas { + // Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs. + if aa.OperStatus != windows.IfOperStatusUp { + continue + } + + // Only take interfaces which have at least one gateway + if aa.FirstGatewayAddress == nil { + continue + } + + for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next { + sa, err := dns.Address.Sockaddr.Sockaddr() + if err != nil { + continue + } + var ip netip.Addr + switch sa := sa.(type) { + case *syscall.SockaddrInet4: + ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]}) + case *syscall.SockaddrInet6: + var addr16 [16]byte + copy(addr16[:], sa.Addr[:]) + if addr16[0] == 0xfe && addr16[1] == 0xc0 { + // fec0/10 IPv6 addresses are site local anycast DNS + // addresses Microsoft sets by default if no other + // IPv6 DNS address is set. Site local anycast is + // deprecated since 2004, see + // https://datatracker.ietf.org/doc/html/rfc3879 + continue + } + ip = netip.AddrFrom16(addr16) + default: + // Unexpected type. + continue + } + conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53")) + } + } + return conf +} + +//go:linkname defaultNS net.defaultNS +var defaultNS []string + +func adapterAddresses() ([]*windows.IpAdapterAddresses, error) { + var b []byte + l := uint32(15000) // recommended initial size + for { + b = make([]byte, l) + const flags = windows.GAA_FLAG_INCLUDE_PREFIX | windows.GAA_FLAG_INCLUDE_GATEWAYS + err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, flags, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l) + if err == nil { + if l == 0 { + return nil, nil + } + break + } + if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW { + return nil, os.NewSyscallError("getadaptersaddresses", err) + } + if l <= uint32(len(b)) { + return nil, os.NewSyscallError("getadaptersaddresses", err) + } + } + var aas []*windows.IpAdapterAddresses + for aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next { + aas = append(aas, aa) + } + return aas, nil +} diff --git a/dns/transport/predefined.go b/dns/transport/predefined.go new file mode 100644 index 0000000000..3f11288681 --- /dev/null +++ b/dns/transport/predefined.go @@ -0,0 +1,83 @@ +package transport + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + 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" + E "github.com/sagernet/sing/common/exceptions" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*PredefinedTransport)(nil) + +func RegisterPredefined(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.PredefinedDNSServerOptions](registry, C.DNSTypePreDefined, NewPredefined) +} + +type PredefinedTransport struct { + dns.TransportAdapter + responses []*predefinedResponse +} + +type predefinedResponse struct { + questions []mDNS.Question + answer *mDNS.Msg +} + +func NewPredefined(ctx context.Context, logger log.ContextLogger, tag string, options option.PredefinedDNSServerOptions) (adapter.DNSTransport, error) { + var responses []*predefinedResponse + for _, response := range options.Responses { + questions, msg, err := response.Build() + if err != nil { + return nil, err + } + responses = append(responses, &predefinedResponse{ + questions: questions, + answer: msg, + }) + } + if len(responses) == 0 { + return nil, E.New("empty predefined responses") + } + return &PredefinedTransport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypePreDefined, tag, nil), + responses: responses, + }, nil +} + +func (t *PredefinedTransport) Reset() { +} + +func (t *PredefinedTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + for _, response := range t.responses { + for _, question := range response.questions { + if func() bool { + if question.Name == "" && question.Qtype == mDNS.TypeNone { + return true + } else if question.Name == "" { + return common.Any(message.Question, func(it mDNS.Question) bool { + return it.Qtype == question.Qtype + }) + } else if question.Qtype == mDNS.TypeNone { + return common.Any(message.Question, func(it mDNS.Question) bool { + return it.Name == question.Name + }) + } else { + return common.Contains(message.Question, question) + } + }() { + copyAnswer := *response.answer + copyAnswer.Id = message.Id + copyAnswer.Question = message.Question + return ©Answer, nil + } + } + } + return nil, dns.RCodeNameError +} diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go new file mode 100644 index 0000000000..a5181ae08b --- /dev/null +++ b/dns/transport/quic/http3.go @@ -0,0 +1,167 @@ +package quic + +import ( + "bytes" + "context" + "io" + "net" + "net/http" + "net/url" + "strconv" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHTTP "github.com/sagernet/sing/protocol/http" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*HTTP3Transport)(nil) + +func RegisterHTTP3Transport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTP3, NewHTTP3) +} + +type HTTP3Transport struct { + dns.TransportAdapter + logger logger.ContextLogger + dialer N.Dialer + destination *url.URL + headers http.Header + transport *http3.Transport +} + +func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions) + if err != nil { + return nil, err + } + tlsOptions := common.PtrValueOrDefault(options.TLS) + tlsOptions.Enabled = true + tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions) + if err != nil { + return nil, err + } + stdConfig, err := tlsConfig.Config() + if err != nil { + return nil, err + } + headers := options.Headers.Build() + host := headers.Get("Host") + if host != "" { + headers.Del("Host") + } else { + if tlsConfig.ServerName() != "" { + host = tlsConfig.ServerName() + } else { + host = options.Server + } + } + destinationURL := url.URL{ + Scheme: "https", + Host: host, + } + if destinationURL.Host == "" { + destinationURL.Host = options.Server + } + if options.ServerPort != 0 && options.ServerPort != 443 { + destinationURL.Host = net.JoinHostPort(destinationURL.Host, strconv.Itoa(int(options.ServerPort))) + } + path := options.Path + if path == "" { + path = "/dns-query" + } + err = sHTTP.URLSetPath(&destinationURL, path) + if err != nil { + return nil, err + } + serverAddr := options.ServerOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 443 + } + return &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.EarlyConnection, error) { + destinationAddr := M.ParseSocksaddr(addr) + conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, destinationAddr) + if dialErr != nil { + return nil, dialErr + } + return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(conn), conn.RemoteAddr(), tlsCfg, cfg) + }, + TLSClientConfig: stdConfig, + }, + }, nil +} + +func (t *HTTP3Transport) Reset() { + t.transport.Close() +} + +func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + exMessage := *message + exMessage.Id = 0 + exMessage.Compress = true + requestBuffer := buf.NewSize(1 + message.Len()) + rawMessage, err := exMessage.PackBuffer(requestBuffer.FreeBytes()) + if err != nil { + requestBuffer.Release() + return nil, err + } + request, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage)) + if err != nil { + requestBuffer.Release() + return nil, err + } + request.Header = t.headers.Clone() + request.Header.Set("Content-Type", transport.MimeType) + request.Header.Set("Accept", transport.MimeType) + response, err := t.transport.RoundTrip(request) + requestBuffer.Release() + if err != nil { + return nil, err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return nil, E.New("unexpected status: ", response.Status) + } + var responseMessage mDNS.Msg + if response.ContentLength > 0 { + responseBuffer := buf.NewSize(int(response.ContentLength)) + _, 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 { + return nil, err + } + err = responseMessage.Unpack(rawMessage) + } + if err != nil { + return nil, err + } + return &responseMessage, nil +} diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go new file mode 100644 index 0000000000..d3844c2b55 --- /dev/null +++ b/dns/transport/quic/quic.go @@ -0,0 +1,174 @@ +package quic + +import ( + "context" + "errors" + "sync" + + "github.com/sagernet/quic-go" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + sQUIC "github.com/sagernet/sing-quic" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + "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" +) + +var _ adapter.DNSTransport = (*Transport)(nil) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeQUIC, NewQUIC) +} + +type Transport struct { + dns.TransportAdapter + ctx context.Context + logger logger.ContextLogger + dialer N.Dialer + serverAddr M.Socksaddr + tlsConfig tls.Config + access sync.Mutex + connection quic.EarlyConnection +} + +func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions) + if err != nil { + return nil, err + } + tlsOptions := common.PtrValueOrDefault(options.TLS) + tlsOptions.Enabled = true + tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions) + if err != nil { + return nil, err + } + if len(tlsConfig.NextProtos()) == 0 { + tlsConfig.SetNextProtos([]string{"doq"}) + } + serverAddr := options.ServerOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 853 + } + return &Transport{ + TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions), + ctx: ctx, + logger: logger, + dialer: transportDialer, + serverAddr: serverAddr, + tlsConfig: tlsConfig, + }, nil +} + +func (t *Transport) Reset() { + t.access.Lock() + defer t.access.Unlock() + connection := t.connection + if connection != nil { + connection.CloseWithError(0, "") + } +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + var ( + conn quic.Connection + err error + response *mDNS.Msg + ) + for i := 0; i < 2; i++ { + conn, err = t.openConnection() + if err != nil { + return nil, err + } + response, err = t.exchange(ctx, message, conn) + if err == nil { + return response, nil + } else if !isQUICRetryError(err) { + return nil, err + } else { + conn.CloseWithError(quic.ApplicationErrorCode(0), "") + continue + } + } + return nil, err +} + +func (t *Transport) openConnection() (quic.EarlyConnection, 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.Connection) (*mDNS.Msg, error) { + stream, err := conn.OpenStreamSync(ctx) + if err != nil { + return nil, err + } + defer stream.Close() + defer stream.CancelRead(0) + err = transport.WriteMessage(stream, 0, message) + if err != nil { + return nil, err + } + return transport.ReadMessage(stream) +} + +// https://github.com/AdguardTeam/dnsproxy/blob/fd1868577652c639cce3da00e12ca548f421baf1/upstream/upstream_quic.go#L394 +func isQUICRetryError(err error) (ok bool) { + var qAppErr *quic.ApplicationError + if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 0 { + return true + } + + var qIdleErr *quic.IdleTimeoutError + if errors.As(err, &qIdleErr) { + return true + } + + var resetErr *quic.StatelessResetError + if errors.As(err, &resetErr) { + return true + } + + var qTransportError *quic.TransportError + if errors.As(err, &qTransportError) && qTransportError.ErrorCode == quic.NoError { + return true + } + + if errors.Is(err, quic.Err0RTTRejected) { + return true + } + + return false +} diff --git a/dns/transport/tcp.go b/dns/transport/tcp.go new file mode 100644 index 0000000000..6061585e08 --- /dev/null +++ b/dns/transport/tcp.go @@ -0,0 +1,99 @@ +package transport + +import ( + "context" + "encoding/binary" + "io" + + "github.com/sagernet/sing-box/adapter" + 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" + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*TCPTransport)(nil) + +func RegisterTCP(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteDNSServerOptions](registry, C.DNSTypeTCP, NewTCP) +} + +type TCPTransport struct { + dns.TransportAdapter + dialer N.Dialer + serverAddr M.Socksaddr +} + +func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options) + if err != nil { + return nil, err + } + serverAddr := options.ServerOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 53 + } + return &TCPTransport{ + TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options), + dialer: transportDialer, + serverAddr: serverAddr, + }, 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 + } + defer conn.Close() + err = WriteMessage(conn, 0, message) + if err != nil { + return nil, err + } + return ReadMessage(conn) +} + +func ReadMessage(reader io.Reader) (*mDNS.Msg, error) { + var responseLen uint16 + err := binary.Read(reader, binary.BigEndian, &responseLen) + if err != nil { + return nil, err + } + if responseLen < 10 { + return nil, mDNS.ErrShortRead + } + buffer := buf.NewSize(int(responseLen)) + defer buffer.Release() + _, err = buffer.ReadFullFrom(reader, int(responseLen)) + if err != nil { + return nil, err + } + var message mDNS.Msg + err = message.Unpack(buffer.Bytes()) + return &message, err +} + +func WriteMessage(writer io.Writer, messageId uint16, message *mDNS.Msg) error { + requestLen := message.Len() + buffer := buf.NewSize(3 + requestLen) + defer buffer.Release() + common.Must(binary.Write(buffer, binary.BigEndian, uint16(requestLen))) + exMessage := *message + exMessage.Id = messageId + exMessage.Compress = true + rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes()) + if err != nil { + return err + } + buffer.Truncate(2 + len(rawMessage)) + return common.Error(writer.Write(buffer.Bytes())) +} diff --git a/dns/transport/tls.go b/dns/transport/tls.go new file mode 100644 index 0000000000..28fa885a55 --- /dev/null +++ b/dns/transport/tls.go @@ -0,0 +1,115 @@ +package transport + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "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/x/list" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*TLSTransport)(nil) + +func RegisterTLS(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeTLS, NewTLS) +} + +type TLSTransport struct { + dns.TransportAdapter + logger logger.ContextLogger + dialer N.Dialer + serverAddr M.Socksaddr + tlsConfig tls.Config + access sync.Mutex + connections list.List[*tlsDNSConn] +} + +type tlsDNSConn struct { + tls.Conn + queryId uint16 +} + +func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions) + if err != nil { + return nil, err + } + tlsOptions := common.PtrValueOrDefault(options.TLS) + tlsOptions.Enabled = true + tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions) + if err != nil { + return nil, err + } + serverAddr := options.ServerOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 853 + } + return &TLSTransport{ + TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), + logger: logger, + dialer: transportDialer, + serverAddr: serverAddr, + tlsConfig: tlsConfig, + }, nil +} + +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() +} + +func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + t.access.Lock() + conn := t.connections.PopFront() + t.access.Unlock() + if conn != nil { + response, err := t.exchange(message, conn) + if err == nil { + return response, nil + } + } + tcpConn, err := t.dialer.DialContext(ctx, N.NetworkTCP, 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}) +} + +func (t *TLSTransport) exchange(message *mDNS.Msg, conn *tlsDNSConn) (*mDNS.Msg, error) { + conn.queryId++ + err := WriteMessage(conn, conn.queryId, message) + if err != nil { + conn.Close() + return nil, E.Cause(err, "write request") + } + response, err := ReadMessage(conn) + if err != nil { + conn.Close() + return nil, E.Cause(err, "read response") + } + t.access.Lock() + t.connections.PushBack(conn) + t.access.Unlock() + return response, nil +} diff --git a/dns/transport/udp.go b/dns/transport/udp.go new file mode 100644 index 0000000000..5099c6f6d8 --- /dev/null +++ b/dns/transport/udp.go @@ -0,0 +1,223 @@ +package transport + +import ( + "context" + "net" + "os" + "sync" + + "github.com/sagernet/sing-box/adapter" + 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/buf" + "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" +) + +var _ adapter.DNSTransport = (*UDPTransport)(nil) + +func RegisterUDP(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteDNSServerOptions](registry, C.DNSTypeUDP, NewUDP) +} + +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{} +} + +func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options) + if err != nil { + return nil, err + } + serverAddr := options.ServerOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 53 + } + 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: 512, + tcpTransport: &TCPTransport{ + dialer: dialer, + serverAddr: serverAddr, + }, + done: make(chan struct{}), + } +} + +func (t *UDPTransport) Reset() { + t.access.Lock() + defer t.access.Unlock() + close(t.done) + t.done = make(chan struct{}) +} + +func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + 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) + } + return response, nil +} + +func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + conn, err := t.open(ctx) + if err != nil { + return nil, err + } + if edns0Opt := message.IsEdns0(); edns0Opt != nil { + if udpSize := int(edns0Opt.UDPSize()); udpSize > t.udpSize { + t.udpSize = udpSize + } + } + buffer := buf.NewSize(1 + message.Len()) + defer buffer.Release() + exMessage := *message + exMessage.Compress = true + messageId := message.Id + callback := &dnsCallback{ + done: make(chan struct{}), + } + conn.access.Lock() + conn.queryId++ + exMessage.Id = conn.queryId + conn.callbacks[exMessage.Id] = callback + conn.access.Unlock() + defer func() { + conn.access.Lock() + delete(conn.callbacks, messageId) + conn.access.Unlock() + callback.access.Lock() + select { + case <-callback.done: + default: + close(callback.done) + } + callback.access.Unlock() + }() + 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 + } + 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 + 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) { + for { + buffer := buf.NewSize(t.udpSize) + _, err := buffer.ReadOnceFrom(conn) + if err != nil { + buffer.Release() + conn.Close(err) + return + } + var message mDNS.Msg + err = message.Unpack(buffer.Bytes()) + buffer.Release() + if err != nil { + conn.Close(err) + return + } + conn.access.RLock() + callback, loaded := conn.callbacks[message.Id] + conn.access.RUnlock() + if !loaded { + continue + } + callback.access.Lock() + select { + case <-callback.done: + default: + callback.message = &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() { + close(c.done) + c.err = err + }) + c.Conn.Close() +} + +type dnsCallback struct { + access sync.Mutex + message *mDNS.Msg + done chan struct{} +} diff --git a/dns/transport_adapter.go b/dns/transport_adapter.go new file mode 100644 index 0000000000..02c84621f9 --- /dev/null +++ b/dns/transport_adapter.go @@ -0,0 +1,70 @@ +package dns + +import ( + "net/netip" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" +) + +var _ adapter.LegacyDNSTransport = (*TransportAdapter)(nil) + +type TransportAdapter struct { + transportType string + transportTag string + dependencies []string + strategy C.DomainStrategy + clientSubnet netip.Prefix +} + +func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter { + return TransportAdapter{ + transportType: transportType, + transportTag: transportTag, + dependencies: dependencies, + } +} + +func NewTransportAdapterWithLocalOptions(transportType string, transportTag string, localOptions option.LocalDNSServerOptions) TransportAdapter { + return TransportAdapter{ + transportType: transportType, + transportTag: transportTag, + strategy: C.DomainStrategy(localOptions.LegacyStrategy), + clientSubnet: localOptions.LegacyClientSubnet, + } +} + +func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter { + var dependencies []string + if remoteOptions.AddressResolver != "" { + dependencies = []string{remoteOptions.AddressResolver} + } + return TransportAdapter{ + transportType: transportType, + transportTag: transportTag, + dependencies: dependencies, + strategy: C.DomainStrategy(remoteOptions.LegacyStrategy), + clientSubnet: remoteOptions.LegacyClientSubnet, + } +} + +func (a *TransportAdapter) Type() string { + return a.transportType +} + +func (a *TransportAdapter) Tag() string { + return a.transportTag +} + +func (a *TransportAdapter) Dependencies() []string { + return a.dependencies +} + +func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy { + return a.strategy +} + +func (a *TransportAdapter) LegacyClientSubnet() netip.Prefix { + return a.clientSubnet +} diff --git a/dns/transport_dialer.go b/dns/transport_dialer.go new file mode 100644 index 0000000000..14e1188dab --- /dev/null +++ b/dns/transport_dialer.go @@ -0,0 +1,93 @@ +package dns + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) { + if options.LegacyDefaultDialer { + return dialer.NewDefaultOutbound(ctx), nil + } else { + return dialer.New(ctx, options.DialerOptions) + } +} + +func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) { + transportDialer, err := NewLocalDialer(ctx, options.LocalDNSServerOptions) + if err != nil { + return nil, err + } + if options.AddressResolver != "" { + transport := service.FromContext[adapter.DNSTransportManager](ctx) + resolverTransport, loaded := transport.Transport(options.AddressResolver) + if !loaded { + return nil, E.New("address resolver not found: ", options.AddressResolver) + } + transportDialer = NewTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.AddressStrategy), time.Duration(options.AddressFallbackDelay)) + } else if M.IsDomainName(options.Server) { + return nil, E.New("missing address resolver for server: ", options.Server) + } + return transportDialer, nil +} + +type TransportDialer struct { + dialer N.Dialer + dnsRouter adapter.DNSRouter + transport adapter.DNSTransport + strategy C.DomainStrategy + fallbackDelay time.Duration +} + +func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *TransportDialer { + return &TransportDialer{ + dialer, + dnsRouter, + transport, + strategy, + fallbackDelay, + } +} + +func (d *TransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if destination.IsIP() { + return d.dialer.DialContext(ctx, network, destination) + } + addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{ + Transport: d.transport, + Strategy: d.strategy, + }) + if err != nil { + return nil, err + } + return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay) +} + +func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if destination.IsIP() { + return d.dialer.ListenPacket(ctx, destination) + } + addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{ + Transport: d.transport, + Strategy: d.strategy, + }) + if err != nil { + return nil, err + } + conn, _, err := N.ListenSerial(ctx, d.dialer, destination, addresses) + return conn, err +} + +func (d *TransportDialer) Upstream() any { + return d.dialer +} diff --git a/dns/transport_manager.go b/dns/transport_manager.go new file mode 100644 index 0000000000..4497923b46 --- /dev/null +++ b/dns/transport_manager.go @@ -0,0 +1,288 @@ +package dns + +import ( + "context" + "io" + "os" + "strings" + "sync" + + "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/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +var _ adapter.DNSTransportManager = (*TransportManager)(nil) + +type TransportManager struct { + logger log.ContextLogger + registry adapter.DNSTransportRegistry + outbound adapter.OutboundManager + defaultTag string + access sync.RWMutex + started bool + stage adapter.StartStage + transports []adapter.DNSTransport + transportByTag map[string]adapter.DNSTransport + dependByTag map[string][]string + defaultTransport adapter.DNSTransport + defaultTransportFallback adapter.DNSTransport + fakeIPTransport adapter.FakeIPTransport +} + +func NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransportRegistry, outbound adapter.OutboundManager, defaultTag string) *TransportManager { + return &TransportManager{ + logger: logger, + registry: registry, + outbound: outbound, + defaultTag: defaultTag, + transportByTag: make(map[string]adapter.DNSTransport), + dependByTag: make(map[string][]string), + } +} + +func (m *TransportManager) Initialize(defaultTransportFallback adapter.DNSTransport) { + m.defaultTransportFallback = defaultTransportFallback +} + +func (m *TransportManager) Start(stage adapter.StartStage) error { + m.access.Lock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + outbounds := m.transports + m.access.Unlock() + if stage == adapter.StartStateStart { + return m.startTransports(m.transports) + } else { + for _, outbound := range outbounds { + err := adapter.LegacyStart(outbound, stage) + if err != nil { + return E.Cause(err, stage, " dns/", outbound.Type(), "[", outbound.Tag(), "]") + } + } + } + return nil +} + +func (m *TransportManager) startTransports(transports []adapter.DNSTransport) error { + monitor := taskmonitor.New(m.logger, C.StartTimeout) + started := make(map[string]bool) + for { + canContinue := false + startOne: + for _, transportToStart := range transports { + transportTag := transportToStart.Tag() + if started[transportTag] { + continue + } + dependencies := transportToStart.Dependencies() + for _, dependency := range dependencies { + if !started[dependency] { + continue startOne + } + } + started[transportTag] = true + canContinue = true + if starter, isStarter := transportToStart.(adapter.Lifecycle); isStarter { + monitor.Start("start dns/", transportToStart.Type(), "[", transportTag, "]") + err := starter.Start(adapter.StartStateStart) + monitor.Finish() + if err != nil { + return E.Cause(err, "start dns/", transportToStart.Type(), "[", transportTag, "]") + } + } + } + if len(started) == len(transports) { + break + } + if canContinue { + continue + } + currentTransport := common.Find(transports, func(it adapter.DNSTransport) bool { + return !started[it.Tag()] + }) + var lintTransport func(oTree []string, oCurrent adapter.DNSTransport) error + lintTransport = func(oTree []string, oCurrent adapter.DNSTransport) error { + problemTransportTag := common.Find(oCurrent.Dependencies(), func(it string) bool { + return !started[it] + }) + if common.Contains(oTree, problemTransportTag) { + return E.New("circular server dependency: ", strings.Join(oTree, " -> "), " -> ", problemTransportTag) + } + m.access.Lock() + problemTransport := m.transportByTag[problemTransportTag] + m.access.Unlock() + if problemTransport == nil { + return E.New("dependency[", problemTransportTag, "] not found for server[", oCurrent.Tag(), "]") + } + return lintTransport(append(oTree, problemTransportTag), problemTransport) + } + return lintTransport([]string{currentTransport.Tag()}, currentTransport) + } + return nil +} + +func (m *TransportManager) Close() error { + monitor := taskmonitor.New(m.logger, C.StopTimeout) + m.access.Lock() + if !m.started { + m.access.Unlock() + return nil + } + m.started = false + transports := m.transports + m.transports = nil + m.access.Unlock() + var err error + for _, transport := range transports { + if closer, isCloser := transport.(io.Closer); isCloser { + monitor.Start("close server/", transport.Type(), "[", transport.Tag(), "]") + err = E.Append(err, closer.Close(), func(err error) error { + return E.Cause(err, "close server/", transport.Type(), "[", transport.Tag(), "]") + }) + monitor.Finish() + } + } + return nil +} + +func (m *TransportManager) Transports() []adapter.DNSTransport { + m.access.RLock() + defer m.access.RUnlock() + return m.transports +} + +func (m *TransportManager) Transport(tag string) (adapter.DNSTransport, bool) { + m.access.RLock() + outbound, found := m.transportByTag[tag] + m.access.RUnlock() + return outbound, found +} + +func (m *TransportManager) Default() adapter.DNSTransport { + m.access.RLock() + defer m.access.RUnlock() + if m.defaultTransport != nil { + return m.defaultTransport + } else { + return m.defaultTransportFallback + } +} + +func (m *TransportManager) FakeIP() adapter.FakeIPTransport { + m.access.RLock() + defer m.access.RUnlock() + return m.fakeIPTransport +} + +func (m *TransportManager) Remove(tag string) error { + m.access.Lock() + defer m.access.Unlock() + transport, found := m.transportByTag[tag] + if !found { + return os.ErrInvalid + } + delete(m.transportByTag, tag) + index := common.Index(m.transports, func(it adapter.DNSTransport) bool { + return it == transport + }) + if index == -1 { + panic("invalid inbound index") + } + m.transports = append(m.transports[:index], m.transports[index+1:]...) + started := m.started + if m.defaultTransport == transport { + if len(m.transports) > 0 { + nextTransport := m.transports[0] + if nextTransport.Type() != C.DNSTypeFakeIP { + return E.New("default server cannot be fakeip") + } + m.defaultTransport = nextTransport + m.logger.Info("updated default server to ", m.defaultTransport.Tag()) + } else { + m.defaultTransport = nil + } + } + dependBy := m.dependByTag[tag] + if len(dependBy) > 0 { + return E.New("server[", tag, "] is depended by ", strings.Join(dependBy, ", ")) + } + dependencies := transport.Dependencies() + for _, dependency := range dependencies { + if len(m.dependByTag[dependency]) == 1 { + delete(m.dependByTag, dependency) + } else { + m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool { + return it != tag + }) + } + } + if started { + transport.Reset() + } + return nil +} + +func (m *TransportManager) Create(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) error { + if tag == "" { + return os.ErrInvalid + } + transport, err := m.registry.CreateDNSTransport(ctx, logger, tag, transportType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + for _, stage := range adapter.ListStartStages { + err = adapter.LegacyStart(transport, stage) + if err != nil { + return E.Cause(err, stage, " dns/", transport.Type(), "[", transport.Tag(), "]") + } + } + } + if existsTransport, loaded := m.transportByTag[tag]; loaded { + if m.started { + err = common.Close(existsTransport) + if err != nil { + return E.Cause(err, "close dns/", existsTransport.Type(), "[", existsTransport.Tag(), "]") + } + } + existsIndex := common.Index(m.transports, func(it adapter.DNSTransport) bool { + return it == existsTransport + }) + if existsIndex == -1 { + panic("invalid inbound index") + } + m.transports = append(m.transports[:existsIndex], m.transports[existsIndex+1:]...) + } + m.transports = append(m.transports, transport) + m.transportByTag[tag] = transport + dependencies := transport.Dependencies() + for _, dependency := range dependencies { + m.dependByTag[dependency] = append(m.dependByTag[dependency], tag) + } + if tag == m.defaultTag || (m.defaultTag == "" && m.defaultTransport == nil) { + if transport.Type() == C.DNSTypeFakeIP { + return E.New("default server cannot be fakeip") + } + m.defaultTransport = transport + if m.started { + m.logger.Info("updated default server to ", transport.Tag()) + } + } + if transport.Type() == C.DNSTypeFakeIP { + if m.fakeIPTransport != nil { + return E.New("multiple fakeip server are not supported") + } + m.fakeIPTransport = transport.(adapter.FakeIPTransport) + } + return nil +} diff --git a/dns/transport_registry.go b/dns/transport_registry.go new file mode 100644 index 0000000000..d838158b22 --- /dev/null +++ b/dns/transport_registry.go @@ -0,0 +1,72 @@ +package dns + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type TransportConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.DNSTransport, error) + +func RegisterTransport[Options any](registry *TransportRegistry, transportType string, constructor TransportConstructorFunc[Options]) { + registry.register(transportType, func() any { + return new(Options) + }, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.DNSTransport, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, logger, tag, common.PtrValueOrDefault(options)) + }) +} + +var _ adapter.DNSTransportRegistry = (*TransportRegistry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.DNSTransport, error) +) + +type TransportRegistry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructors map[string]constructorFunc +} + +func NewTransportRegistry() *TransportRegistry { + return &TransportRegistry{ + optionsType: make(map[string]optionsConstructorFunc), + constructors: make(map[string]constructorFunc), + } +} + +func (r *TransportRegistry) CreateOptions(transportType string) (any, bool) { + r.access.Lock() + defer r.access.Unlock() + optionsConstructor, loaded := r.optionsType[transportType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (r *TransportRegistry) CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (adapter.DNSTransport, error) { + r.access.Lock() + defer r.access.Unlock() + constructor, loaded := r.constructors[transportType] + if !loaded { + return nil, E.New("transport type not found: " + transportType) + } + return constructor(ctx, logger, tag, options) +} + +func (r *TransportRegistry) register(transportType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + r.access.Lock() + defer r.access.Unlock() + r.optionsType[transportType] = optionsConstructor + r.constructors[transportType] = constructor +} diff --git a/experimental/clashapi/dns.go b/experimental/clashapi/dns.go index 2a21a7c101..4f850f8287 100644 --- a/experimental/clashapi/dns.go +++ b/experimental/clashapi/dns.go @@ -13,13 +13,13 @@ import ( "github.com/miekg/dns" ) -func dnsRouter(router adapter.Router) http.Handler { +func dnsRouter(router adapter.DNSRouter) http.Handler { r := chi.NewRouter() r.Get("/query", queryDNS(router)) return r } -func queryDNS(router adapter.Router) func(w http.ResponseWriter, r *http.Request) { +func queryDNS(router adapter.DNSRouter) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") qTypeStr := r.URL.Query().Get("type") @@ -39,7 +39,7 @@ func queryDNS(router adapter.Router) func(w http.ResponseWriter, r *http.Request msg := dns.Msg{} msg.SetQuestion(dns.Fqdn(name), qType) - resp, err := router.Exchange(ctx, &msg) + resp, err := router.Exchange(ctx, &msg, adapter.DNSQueryOptions{}) if err != nil { render.Status(r, http.StatusInternalServerError) render.JSON(w, r, newError(err.Error())) diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index efe2ce3690..d01e180a9f 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -42,6 +42,7 @@ var _ adapter.ClashServer = (*Server)(nil) type Server struct { ctx context.Context router adapter.Router + dnsRouter adapter.DNSRouter outbound adapter.OutboundManager endpoint adapter.EndpointManager logger log.Logger @@ -62,11 +63,12 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op trafficManager := trafficontrol.NewManager() chiRouter := chi.NewRouter() s := &Server{ - ctx: ctx, - router: service.FromContext[adapter.Router](ctx), - outbound: service.FromContext[adapter.OutboundManager](ctx), - endpoint: service.FromContext[adapter.EndpointManager](ctx), - logger: logFactory.NewLogger("clash-api"), + ctx: ctx, + router: service.FromContext[adapter.Router](ctx), + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + endpoint: service.FromContext[adapter.EndpointManager](ctx), + logger: logFactory.NewLogger("clash-api"), httpServer: &http.Server{ Addr: options.ExternalController, Handler: chiRouter, @@ -121,7 +123,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op r.Mount("/script", scriptRouter()) r.Mount("/profile", profileRouter()) r.Mount("/cache", cacheRouter(ctx)) - r.Mount("/dns", dnsRouter(s.router)) + r.Mount("/dns", dnsRouter(s.dnsRouter)) s.setupMetaAPI(r) }) @@ -221,7 +223,7 @@ func (s *Server) SetMode(newMode string) { default: } } - s.router.ClearDNSCache() + s.dnsRouter.ClearCache() cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { err := cacheFile.StoreMode(newMode) diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 68aa9aca05..bf64836541 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -146,6 +146,21 @@ var OptionTUNGSO = Note{ EnvName: "TUN_GSO", } +var OptionLegacyDNSTransport = Note{ + Name: "legacy-dns-transport", + Description: "legacy DNS transport", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", + EnvName: "LEGACY_DNS_TRANSPORT", +} + +var OptionLegacyDNSFakeIPOptions = Note{ + Name: "legacy-dns-fakeip-options", + Description: "legacy DNS fakeip options", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", +} + var Options = []Note{ OptionBadMatchSource, OptionGEOIP, diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 159fd8f6a0..603d8b6d27 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -9,8 +9,11 @@ import ( "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" "github.com/sagernet/sing/common/control" @@ -21,6 +24,18 @@ import ( "github.com/sagernet/sing/service" ) +func BaseContext(platformInterface PlatformInterface) context.Context { + dnsRegistry := include.DNSTransportRegistry() + if platformInterface != nil { + if localTransport := platformInterface.LocalDNSTransport(); localTransport != nil { + dns.RegisterTransport[option.LocalDNSServerOptions](dnsRegistry, C.DNSTypeLocal, func(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) { + return newPlatformTransport(localTransport, tag, options), nil + }) + } + } + return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry) +} + func parseConfig(ctx context.Context, configContent string) (option.Options, error) { options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent)) if err != nil { @@ -30,7 +45,7 @@ func parseConfig(ctx context.Context, configContent string) (option.Options, err } func CheckConfig(configContent string) error { - ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()) + ctx := BaseContext(nil) options, err := parseConfig(ctx, configContent) if err != nil { return err @@ -131,7 +146,7 @@ func (s *platformInterfaceStub) SendNotification(notification *platform.Notifica } func FormatConfig(configContent string) (*StringBox, error) { - options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()), configContent) + options, err := parseConfig(BaseContext(nil), configContent) if err != nil { return nil, err } diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index a46d9b4241..a7ccd2a2e6 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -6,7 +6,10 @@ import ( "strings" "syscall" - "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -21,118 +24,80 @@ type LocalDNSTransport interface { Exchange(ctx *ExchangeContext, message []byte) error } -func RegisterLocalDNSTransport(transport LocalDNSTransport) { - if transport == nil { - dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) { - return dns.NewLocalTransport(options), nil - }) - } else { - dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) { - return &platformLocalDNSTransport{ - iif: transport, - }, nil - }) - } -} +var _ adapter.DNSTransport = (*platformTransport)(nil) -var _ dns.Transport = (*platformLocalDNSTransport)(nil) - -type platformLocalDNSTransport struct { +type platformTransport struct { + dns.TransportAdapter iif LocalDNSTransport } -func (p *platformLocalDNSTransport) Name() string { - return "local" -} - -func (p *platformLocalDNSTransport) Start() error { - return nil -} - -func (p *platformLocalDNSTransport) Reset() { -} - -func (p *platformLocalDNSTransport) Close() error { - return nil +func newPlatformTransport(iif LocalDNSTransport, tag string, options option.LocalDNSServerOptions) *platformTransport { + return &platformTransport{ + TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), + iif: iif, + } } -func (p *platformLocalDNSTransport) Raw() bool { - return p.iif.Raw() +func (p *platformTransport) Reset() { } -func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - messageBytes, err := message.Pack() - if err != nil { - return nil, err - } +func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { response := &ExchangeContext{ context: ctx, } - var responseMessage *mDNS.Msg - var group task.Group - group.Append0(func(ctx context.Context) error { - err = p.iif.Exchange(response, messageBytes) + if p.iif.Raw() { + messageBytes, err := message.Pack() if err != nil { - return err + return nil, err } - if response.error != nil { - return response.error - } - responseMessage = &response.message - return nil - }) - err = group.Run(ctx) - if err != nil { - return nil, err - } - return responseMessage, nil -} - -func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { - var network string - switch strategy { - case dns.DomainStrategyUseIPv4: - network = "ip4" - case dns.DomainStrategyPreferIPv6: - network = "ip6" - default: - network = "ip" - } - response := &ExchangeContext{ - context: ctx, - } - var responseAddr []netip.Addr - var group task.Group - group.Append0(func(ctx context.Context) error { - err := p.iif.Lookup(response, network, domain) + var responseMessage *mDNS.Msg + var group task.Group + group.Append0(func(ctx context.Context) error { + err = p.iif.Exchange(response, messageBytes) + if err != nil { + return err + } + if response.error != nil { + return response.error + } + responseMessage = &response.message + return nil + }) + err = group.Run(ctx) if err != nil { - return err - } - if response.error != nil { - return response.error + return nil, err } - switch strategy { - case dns.DomainStrategyUseIPv4: - responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool { - return it.Is4() - }) - case dns.DomainStrategyPreferIPv6: - responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool { - return it.Is6() - }) + return responseMessage, nil + } else { + question := message.Question[0] + var network string + switch question.Qtype { + case mDNS.TypeA: + network = "ip4" + case mDNS.TypeAAAA: + network = "ip6" default: - responseAddr = response.addresses + return nil, E.New("only IP queries are supported by current version of Android") } - /*if len(responseAddr) == 0 { - response.error = dns.RCodeSuccess - }*/ - return nil - }) - err := group.Run(ctx) - if err != nil { - return nil, err + var responseAddrs []netip.Addr + var group task.Group + group.Append0(func(ctx context.Context) error { + err := p.iif.Lookup(response, network, question.Name) + if err != nil { + return err + } + if response.error != nil { + return response.error + } + responseAddrs = response.addresses + return nil + }) + err := group.Run(ctx) + if err != nil { + return nil, err + } + return dns.FixedResponse(message.Id, question, responseAddrs, C.DefaultDNSTTL), nil } - return responseAddr, nil } type Func interface { diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index d5951cd38f..f05903671f 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -6,6 +6,7 @@ import ( ) type PlatformInterface interface { + LocalDNSTransport() LocalDNSTransport UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int32) error OpenTun(options TunOptions) (int32, error) diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 16c04a1f5e..bd891f6076 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -18,7 +18,6 @@ import ( "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/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" @@ -44,7 +43,7 @@ type BoxService struct { } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { - ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()) + ctx := BaseContext(platformInterface) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) options, err := parseConfig(ctx, configContent) @@ -192,6 +191,9 @@ func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, err continue } w.defaultInterfaceAccess.Lock() + // (GOOS=windows) SA4006: this value of `isDefault` is never used + // Why not used? + //nolint:staticcheck isDefault := w.defaultInterface != nil && int(netInterface.Index) == w.defaultInterface.Index w.defaultInterfaceAccess.Unlock() interfaces = append(interfaces, adapter.NetworkInterface{ diff --git a/go.mod b/go.mod index 56d3a09cb9..8176d73e7b 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,6 @@ require ( github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.10 - github.com/sagernet/sing-dns v0.4.6 github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.4 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index 63299a07ad..a6c9bda6fd 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,6 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4= github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.4.6 h1:mjZC0o6d5sQ1sraoOBbK3G3apCbuL8wWYwu2RNu5rbM= -github.com/sagernet/sing-dns v0.4.6/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.4.4 h1:qqOCLnzHbqKkj/wBcXEI3rhSyqoGlqDdv2S6mz2d/JA= diff --git a/include/dhcp.go b/include/dhcp.go index 0e4b4ccf49..8cf074becd 100644 --- a/include/dhcp.go +++ b/include/dhcp.go @@ -2,4 +2,11 @@ package include -import _ "github.com/sagernet/sing-box/transport/dhcp" +import ( + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/dhcp" +) + +func registerDHCPTransport(registry *dns.TransportRegistry) { + dhcp.RegisterTransport(registry) +} diff --git a/include/dhcp_stub.go b/include/dhcp_stub.go index 47a19d2e57..272f313aaf 100644 --- a/include/dhcp_stub.go +++ b/include/dhcp_stub.go @@ -3,12 +3,18 @@ package include import ( - "github.com/sagernet/sing-dns" + "context" + + "github.com/sagernet/sing-box/adapter" + 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" E "github.com/sagernet/sing/common/exceptions" ) -func init() { - dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) { +func registerDHCPTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.DHCPDNSServerOptions](registry, C.DNSTypeDHCP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) { return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`) }) } diff --git a/include/quic.go b/include/quic.go index 980b458113..6a3f301755 100644 --- a/include/quic.go +++ b/include/quic.go @@ -5,12 +5,13 @@ package include import ( "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/quic" "github.com/sagernet/sing-box/protocol/hysteria" "github.com/sagernet/sing-box/protocol/hysteria2" _ "github.com/sagernet/sing-box/protocol/naive/quic" "github.com/sagernet/sing-box/protocol/tuic" _ "github.com/sagernet/sing-box/transport/v2rayquic" - _ "github.com/sagernet/sing-dns/quic" ) func registerQUICInbounds(registry *inbound.Registry) { @@ -24,3 +25,8 @@ func registerQUICOutbounds(registry *outbound.Registry) { tuic.RegisterOutbound(registry) hysteria2.RegisterOutbound(registry) } + +func registerQUICTransports(registry *dns.TransportRegistry) { + quic.RegisterTransport(registry) + quic.RegisterHTTP3Transport(registry) +} diff --git a/include/quic_stub.go b/include/quic_stub.go index 66c0859038..c20a511474 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -13,20 +13,17 @@ import ( "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/dns" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/protocol/naive" "github.com/sagernet/sing-box/transport/v2ray" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) func init() { - dns.RegisterTransport([]string{"quic", "h3"}, func(options dns.TransportOptions) (dns.Transport, error) { - return nil, C.ErrQUICNotIncluded - }) v2ray.RegisterQUICConstructor( func(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { return nil, C.ErrQUICNotIncluded @@ -63,3 +60,12 @@ func registerQUICOutbounds(registry *outbound.Registry) { return nil, C.ErrQUICNotIncluded }) } + +func registerQUICTransports(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeQUIC, func(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) { + return nil, C.ErrQUICNotIncluded + }) + dns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTP3, func(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) { + return nil, C.ErrQUICNotIncluded + }) +} diff --git a/include/registry.go b/include/registry.go index e71ffb0c82..cbf793f460 100644 --- a/include/registry.go +++ b/include/registry.go @@ -8,11 +8,16 @@ import ( "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" 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/fakeip" + "github.com/sagernet/sing-box/dns/transport/hosts" + "github.com/sagernet/sing-box/dns/transport/local" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/protocol/block" "github.com/sagernet/sing-box/protocol/direct" - "github.com/sagernet/sing-box/protocol/dns" + 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" @@ -61,7 +66,7 @@ func OutboundRegistry() *outbound.Registry { direct.RegisterOutbound(registry) block.RegisterOutbound(registry) - dns.RegisterOutbound(registry) + protocolDNS.RegisterOutbound(registry) group.RegisterSelector(registry) group.RegisterURLTest(registry) @@ -91,6 +96,24 @@ func EndpointRegistry() *endpoint.Registry { return registry } +func DNSTransportRegistry() *dns.TransportRegistry { + registry := dns.NewTransportRegistry() + + transport.RegisterTCP(registry) + transport.RegisterUDP(registry) + transport.RegisterTLS(registry) + transport.RegisterHTTPS(registry) + transport.RegisterPredefined(registry) + hosts.RegisterTransport(registry) + local.RegisterTransport(registry) + fakeip.RegisterTransport(registry) + + registerQUICTransports(registry) + registerDHCPTransport(registry) + + return registry +} + func registerStubForRemovedInbounds(registry *inbound.Registry) { inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") diff --git a/option/dns.go b/option/dns.go index 272c518054..8c9b8bda6e 100644 --- a/option/dns.go +++ b/option/dns.go @@ -1,29 +1,53 @@ package option import ( + "context" "net/netip" + "net/url" + "os" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badoption" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/service" + + "github.com/miekg/dns" ) -type DNSOptions struct { - Servers []DNSServerOptions `json:"servers,omitempty"` - Rules []DNSRule `json:"rules,omitempty"` - Final string `json:"final,omitempty"` - ReverseMapping bool `json:"reverse_mapping,omitempty"` - FakeIP *DNSFakeIPOptions `json:"fakeip,omitempty"` +type RawDNSOptions struct { + Servers []NewDNSServerOptions `json:"servers,omitempty"` + Rules []DNSRule `json:"rules,omitempty"` + Final string `json:"final,omitempty"` + ReverseMapping bool `json:"reverse_mapping,omitempty"` DNSClientOptions } -type DNSServerOptions struct { - Tag string `json:"tag,omitempty"` - Address string `json:"address"` - AddressResolver string `json:"address_resolver,omitempty"` - AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` - AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"` - Strategy DomainStrategy `json:"strategy,omitempty"` - Detour string `json:"detour,omitempty"` - ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` +type LegacyDNSOptions struct { + FakeIP *LegacyDNSFakeIPOptions `json:"fakeip,omitempty"` +} + +type DNSOptions struct { + RawDNSOptions + LegacyDNSOptions +} + +func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions) + if err != nil { + return err + } + if o.FakeIP != nil && o.FakeIP.Enabled { + deprecated.Report(ctx, deprecated.OptionLegacyDNSFakeIPOptions) + ctx = context.WithValue(ctx, (*LegacyDNSFakeIPOptions)(nil), o.FakeIP) + } + legacyOptions := o.LegacyDNSOptions + o.LegacyDNSOptions = LegacyDNSOptions{} + return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions) } type DNSClientOptions struct { @@ -35,8 +59,261 @@ type DNSClientOptions struct { ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } -type DNSFakeIPOptions struct { - Enabled bool `json:"enabled,omitempty"` - Inet4Range *netip.Prefix `json:"inet4_range,omitempty"` - Inet6Range *netip.Prefix `json:"inet6_range,omitempty"` +type LegacyDNSFakeIPOptions struct { + Enabled bool `json:"enabled,omitempty"` + Inet4Range *badoption.Prefix `json:"inet4_range,omitempty"` + Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"` +} + +type DNSTransportOptionsRegistry interface { + CreateOptions(transportType string) (any, bool) +} + +type _NewDNSServerOptions struct { + Type string `json:"type,omitempty"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` +} + +type NewDNSServerOptions _NewDNSServerOptions + +func (o *NewDNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_NewDNSServerOptions)(o), o.Options) +} + +func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_NewDNSServerOptions)(o)) + if err != nil { + return err + } + registry := service.FromContext[DNSTransportOptionsRegistry](ctx) + if registry == nil { + return E.New("missing outbound options registry in context") + } + var options any + switch o.Type { + case "", C.DNSTypeLegacy: + o.Type = C.DNSTypeLegacy + options = new(LegacyDNSServerOptions) + deprecated.Report(ctx, deprecated.OptionLegacyDNSTransport) + default: + var loaded bool + options, loaded = registry.CreateOptions(o.Type) + if !loaded { + return E.New("unknown transport type: ", o.Type) + } + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Outbound)(o), options) + if err != nil { + return err + } + o.Options = options + if o.Type == C.DNSTypeLegacy { + err = o.Upgrade(ctx) + if err != nil { + return err + } + } + return nil +} + +func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { + if o.Type != C.DNSTypeLegacy { + return nil + } + defer func() { + encoder := json.NewEncoder(os.Stderr) + encoder.SetIndent("", " ") + encoder.Encode(o) + }() + options := o.Options.(*LegacyDNSServerOptions) + serverURL, _ := url.Parse(options.Address) + var serverType string + if serverURL.Scheme != "" { + serverType = serverURL.Scheme + } else { + switch options.Address { + case "local", "fakeip": + serverType = options.Address + default: + serverType = C.DNSTypeUDP + } + } + remoteOptions := RemoteDNSServerOptions{ + LocalDNSServerOptions: LocalDNSServerOptions{ + DialerOptions: DialerOptions{ + Detour: options.Detour, + }, + LegacyStrategy: options.Strategy, + LegacyDefaultDialer: options.Detour == "", + LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), + }, + AddressResolver: options.AddressResolver, + AddressStrategy: options.AddressStrategy, + AddressFallbackDelay: options.AddressFallbackDelay, + } + switch serverType { + case C.DNSTypeLocal: + o.Type = C.DNSTypeLocal + o.Options = &remoteOptions.LocalDNSServerOptions + case C.DNSTypeUDP: + o.Type = C.DNSTypeUDP + o.Options = &remoteOptions + var serverAddr M.Socksaddr + if serverURL.Scheme == "" { + serverAddr = M.ParseSocksaddr(options.Address) + } else { + serverAddr = M.ParseSocksaddr(serverURL.Host) + } + if !serverAddr.IsValid() { + return E.New("invalid server address") + } + remoteOptions.Server = serverAddr.Addr.String() + if serverAddr.Port != 0 && serverAddr.Port != 53 { + remoteOptions.ServerPort = serverAddr.Port + } + remoteOptions.Server = serverAddr.AddrString() + remoteOptions.ServerPort = serverAddr.Port + case C.DNSTypeTCP: + o.Type = C.DNSTypeTCP + o.Options = &remoteOptions + serverAddr := M.ParseSocksaddr(serverURL.Host) + if !serverAddr.IsValid() { + return E.New("invalid server address") + } + remoteOptions.Server = serverAddr.Addr.String() + if serverAddr.Port != 0 && serverAddr.Port != 53 { + remoteOptions.ServerPort = serverAddr.Port + } + remoteOptions.Server = serverAddr.AddrString() + remoteOptions.ServerPort = serverAddr.Port + case C.DNSTypeTLS, C.DNSTypeQUIC: + o.Type = serverType + serverAddr := M.ParseSocksaddr(serverURL.Host) + if !serverAddr.IsValid() { + return E.New("invalid server address") + } + remoteOptions.Server = serverAddr.Addr.String() + if serverAddr.Port != 0 && serverAddr.Port != 853 { + remoteOptions.ServerPort = serverAddr.Port + } + o.Options = &RemoteTLSDNSServerOptions{ + RemoteDNSServerOptions: remoteOptions, + } + case C.DNSTypeHTTPS, C.DNSTypeHTTP3: + o.Type = serverType + httpsOptions := RemoteHTTPSDNSServerOptions{ + RemoteTLSDNSServerOptions: RemoteTLSDNSServerOptions{ + RemoteDNSServerOptions: remoteOptions, + }, + } + o.Options = &httpsOptions + serverAddr := M.ParseSocksaddr(serverURL.Host) + if !serverAddr.IsValid() { + return E.New("invalid server address") + } + httpsOptions.Server = serverAddr.Addr.String() + if serverAddr.Port != 0 && serverAddr.Port != 443 { + httpsOptions.ServerPort = serverAddr.Port + } + if serverURL.Path != "/dns-query" { + httpsOptions.Path = serverURL.Path + } + case "rcode": + var rcode int + switch serverURL.Host { + case "success": + rcode = dns.RcodeSuccess + case "format_error": + rcode = dns.RcodeFormatError + case "server_failure": + rcode = dns.RcodeServerFailure + case "name_error": + rcode = dns.RcodeNameError + case "not_implemented": + rcode = dns.RcodeNotImplemented + case "refused": + rcode = dns.RcodeRefused + default: + return E.New("unknown rcode: ", serverURL.Host) + } + o.Type = C.DNSTypePreDefined + o.Options = &PredefinedDNSServerOptions{ + Responses: []DNSResponseOptions{ + { + RCode: common.Ptr(DNSRCode(rcode)), + }, + }, + } + case C.DNSTypeDHCP: + o.Type = C.DNSTypeDHCP + dhcpOptions := DHCPDNSServerOptions{} + if serverURL.Host != "" && serverURL.Host != "auto" { + dhcpOptions.Interface = serverURL.Host + } + o.Options = &dhcpOptions + case C.DNSTypeFakeIP: + o.Type = C.DNSTypeFakeIP + fakeipOptions := FakeIPDNSServerOptions{} + if legacyOptions, loaded := ctx.Value((*LegacyDNSFakeIPOptions)(nil)).(*LegacyDNSFakeIPOptions); loaded { + fakeipOptions.Inet4Range = legacyOptions.Inet4Range + fakeipOptions.Inet6Range = legacyOptions.Inet6Range + } + o.Options = &fakeipOptions + default: + return E.New("unsupported DNS server scheme: ", serverType) + } + return nil +} + +type LegacyDNSServerOptions struct { + Address string `json:"address"` + AddressResolver string `json:"address_resolver,omitempty"` + AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` + AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + Detour string `json:"detour,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` +} + +type HostsDNSServerOptions struct { + Path badoption.Listable[string] `json:"path,omitempty"` + Predefined badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"` +} + +type LocalDNSServerOptions struct { + DialerOptions + LegacyStrategy DomainStrategy `json:"-"` + LegacyDefaultDialer bool `json:"-"` + LegacyClientSubnet netip.Prefix `json:"-"` +} + +type RemoteDNSServerOptions struct { + LocalDNSServerOptions + ServerOptions + AddressResolver string `json:"address_resolver,omitempty"` + AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` + AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"` +} + +type RemoteTLSDNSServerOptions struct { + RemoteDNSServerOptions + OutboundTLSOptionsContainer +} + +type RemoteHTTPSDNSServerOptions struct { + RemoteTLSDNSServerOptions + Path string `json:"path,omitempty"` + Method string `json:"method,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` +} + +type FakeIPDNSServerOptions struct { + Inet4Range *badoption.Prefix `json:"inet4_range,omitempty"` + Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"` +} + +type DHCPDNSServerOptions struct { + LocalDNSServerOptions + Interface string `json:"interface,omitempty"` } diff --git a/option/dns_record.go b/option/dns_record.go new file mode 100644 index 0000000000..c76a76c6f0 --- /dev/null +++ b/option/dns_record.go @@ -0,0 +1,161 @@ +package option + +import ( + "encoding/base64" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" + M "github.com/sagernet/sing/common/metadata" + + "github.com/miekg/dns" +) + +type PredefinedDNSServerOptions struct { + Responses []DNSResponseOptions `json:"responses,omitempty"` +} + +type DNSResponseOptions struct { + Query badoption.Listable[string] `json:"query,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + + RCode *DNSRCode `json:"rcode,omitempty"` + Answer badoption.Listable[DNSRecordOptions] `json:"answer,omitempty"` + Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"` + Extra badoption.Listable[DNSRecordOptions] `json:"extra,omitempty"` +} + +type DNSRCode int + +func (r DNSRCode) MarshalJSON() ([]byte, error) { + rCodeValue, loaded := dns.RcodeToString[int(r)] + if loaded { + return json.Marshal(rCodeValue) + } + return json.Marshal(int(r)) +} + +func (r *DNSRCode) UnmarshalJSON(bytes []byte) error { + var intValue int + err := json.Unmarshal(bytes, &intValue) + if err == nil { + *r = DNSRCode(intValue) + return nil + } + var stringValue string + err = json.Unmarshal(bytes, &stringValue) + if err != nil { + return err + } + rCodeValue, loaded := dns.StringToRcode[stringValue] + if !loaded { + return E.New("unknown rcode: " + stringValue) + } + *r = DNSRCode(rCodeValue) + return nil +} + +func (r *DNSRCode) Build() int { + if r == nil { + return dns.RcodeSuccess + } + return int(*r) +} + +func (o DNSResponseOptions) Build() ([]dns.Question, *dns.Msg, error) { + var questions []dns.Question + if len(o.Query) == 0 && len(o.QueryType) == 0 { + questions = []dns.Question{{Qclass: dns.ClassINET}} + } else if len(o.Query) == 0 { + for _, queryType := range o.QueryType { + questions = append(questions, dns.Question{ + Qtype: uint16(queryType), + Qclass: dns.ClassINET, + }) + } + } else if len(o.QueryType) == 0 { + for _, domain := range o.Query { + questions = append(questions, dns.Question{ + Name: dns.Fqdn(domain), + Qclass: dns.ClassINET, + }) + } + } else { + for _, queryType := range o.QueryType { + for _, domain := range o.Query { + questions = append(questions, dns.Question{ + Name: dns.Fqdn(domain), + Qtype: uint16(queryType), + Qclass: dns.ClassINET, + }) + } + } + } + return questions, &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Response: true, + Rcode: o.RCode.Build(), + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + }, + Answer: common.Map(o.Answer, DNSRecordOptions.build), + Ns: common.Map(o.Ns, DNSRecordOptions.build), + Extra: common.Map(o.Extra, DNSRecordOptions.build), + }, nil +} + +type DNSRecordOptions struct { + dns.RR + fromBase64 bool +} + +func (o DNSRecordOptions) MarshalJSON() ([]byte, error) { + if o.fromBase64 { + buffer := buf.Get(dns.Len(o.RR)) + defer buf.Put(buffer) + offset, err := dns.PackRR(o.RR, buffer, 0, nil, false) + if err != nil { + return nil, err + } + return json.Marshal(base64.StdEncoding.EncodeToString(buffer[:offset])) + } + return json.Marshal(o.RR.String()) +} + +func (o *DNSRecordOptions) UnmarshalJSON(data []byte) error { + var stringValue string + err := json.Unmarshal(data, &stringValue) + if err != nil { + return err + } + binary, err := base64.StdEncoding.DecodeString(stringValue) + if err == nil { + return o.unmarshalBase64(binary) + } + record, err := dns.NewRR(stringValue) + if err != nil { + return err + } + if a, isA := record.(*dns.A); isA { + a.A = M.AddrFromIP(a.A).Unmap().AsSlice() + } + o.RR = record + return nil +} + +func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error { + record, _, err := dns.UnpackRR(binary, 0) + if err != nil { + return E.New("parse binary DNS record") + } + o.RR = record + o.fromBase64 = true + return nil +} + +func (o DNSRecordOptions) build() dns.RR { + return o.RR +} diff --git a/option/rule_action.go b/option/rule_action.go index 35f334d63d..a715d2602b 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -7,7 +7,6 @@ import ( "time" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -168,12 +167,14 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { type DNSRouteActionOptions struct { Server string `json:"server,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } type _DNSRouteOptionsActionOptions struct { + Strategy DomainStrategy `json:"strategy,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` @@ -225,7 +226,7 @@ func (d DirectActionOptions) Descriptions() []string { if d.UDPFragment != nil { descriptions = append(descriptions, "udp_fragment="+fmt.Sprint(*d.UDPFragment)) } - if d.DomainStrategy != DomainStrategy(dns.DomainStrategyAsIS) { + if d.DomainStrategy != DomainStrategy(C.DomainStrategyAsIS) { descriptions = append(descriptions, "domain_strategy="+d.DomainStrategy.String()) } if d.FallbackDelay != 0 { diff --git a/option/rule_dns.go b/option/rule_dns.go index b437eb54d3..9d6fb1381a 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -83,6 +83,7 @@ type RawDefaultDNSRule struct { 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"` diff --git a/option/types.go b/option/types.go index 66f58ef84c..fe7d4b3d07 100644 --- a/option/types.go +++ b/option/types.go @@ -4,7 +4,6 @@ import ( "strings" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" @@ -45,19 +44,19 @@ func (v NetworkList) Build() []string { return strings.Split(string(v), "\n") } -type DomainStrategy dns.DomainStrategy +type DomainStrategy C.DomainStrategy func (s DomainStrategy) String() string { - switch dns.DomainStrategy(s) { - case dns.DomainStrategyAsIS: + switch C.DomainStrategy(s) { + case C.DomainStrategyAsIS: return "" - case dns.DomainStrategyPreferIPv4: + case C.DomainStrategyPreferIPv4: return "prefer_ipv4" - case dns.DomainStrategyPreferIPv6: + case C.DomainStrategyPreferIPv6: return "prefer_ipv6" - case dns.DomainStrategyUseIPv4: + case C.DomainStrategyIPv4Only: return "ipv4_only" - case dns.DomainStrategyUseIPv6: + case C.DomainStrategyIPv6Only: return "ipv6_only" default: panic(E.New("unknown domain strategy: ", s)) @@ -66,17 +65,17 @@ func (s DomainStrategy) String() string { func (s DomainStrategy) MarshalJSON() ([]byte, error) { var value string - switch dns.DomainStrategy(s) { - case dns.DomainStrategyAsIS: + switch C.DomainStrategy(s) { + case C.DomainStrategyAsIS: value = "" // value = "as_is" - case dns.DomainStrategyPreferIPv4: + case C.DomainStrategyPreferIPv4: value = "prefer_ipv4" - case dns.DomainStrategyPreferIPv6: + case C.DomainStrategyPreferIPv6: value = "prefer_ipv6" - case dns.DomainStrategyUseIPv4: + case C.DomainStrategyIPv4Only: value = "ipv4_only" - case dns.DomainStrategyUseIPv6: + case C.DomainStrategyIPv6Only: value = "ipv6_only" default: return nil, E.New("unknown domain strategy: ", s) @@ -92,15 +91,15 @@ func (s *DomainStrategy) UnmarshalJSON(bytes []byte) error { } switch value { case "", "as_is": - *s = DomainStrategy(dns.DomainStrategyAsIS) + *s = DomainStrategy(C.DomainStrategyAsIS) case "prefer_ipv4": - *s = DomainStrategy(dns.DomainStrategyPreferIPv4) + *s = DomainStrategy(C.DomainStrategyPreferIPv4) case "prefer_ipv6": - *s = DomainStrategy(dns.DomainStrategyPreferIPv6) + *s = DomainStrategy(C.DomainStrategyPreferIPv6) case "ipv4_only": - *s = DomainStrategy(dns.DomainStrategyUseIPv4) + *s = DomainStrategy(C.DomainStrategyIPv4Only) case "ipv6_only": - *s = DomainStrategy(dns.DomainStrategyUseIPv6) + *s = DomainStrategy(C.DomainStrategyIPv6Only) default: return E.New("unknown domain strategy: ", value) } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index aba5633621..d173ec532f 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -12,7 +12,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" @@ -34,7 +33,7 @@ type Outbound struct { outbound.Adapter logger logger.ContextLogger dialer dialer.ParallelInterfaceDialer - domainStrategy dns.DomainStrategy + domainStrategy C.DomainStrategy fallbackDelay time.Duration overrideOption int overrideDestination M.Socksaddr @@ -50,7 +49,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL outbound := &Outbound{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), logger: logger, - domainStrategy: dns.DomainStrategy(options.DomainStrategy), + domainStrategy: C.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), dialer: outboundDialer, // loopBack: newLoopBackDetector(router), @@ -151,26 +150,26 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - var domainStrategy dns.DomainStrategy - if h.domainStrategy != dns.DomainStrategyAsIS { + var domainStrategy C.DomainStrategy + if h.domainStrategy != C.DomainStrategyAsIS { domainStrategy = h.domainStrategy } else { //nolint:staticcheck - domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) + domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy) } switch domainStrategy { - case dns.DomainStrategyUseIPv4: + case C.DomainStrategyIPv4Only: destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4) if len(destinationAddresses) == 0 { return nil, E.New("no IPv4 address available for ", destination) } - case dns.DomainStrategyUseIPv6: + case C.DomainStrategyIPv6Only: destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6) if len(destinationAddresses) == 0 { return nil, E.New("no IPv6 address available for ", destination) } } - return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, nil, nil, nil, h.fallbackDelay) + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, nil, nil, nil, h.fallbackDelay) } func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { @@ -191,26 +190,26 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - var domainStrategy dns.DomainStrategy - if h.domainStrategy != dns.DomainStrategyAsIS { + var domainStrategy C.DomainStrategy + if h.domainStrategy != C.DomainStrategyAsIS { domainStrategy = h.domainStrategy } else { //nolint:staticcheck - domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) + domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy) } switch domainStrategy { - case dns.DomainStrategyUseIPv4: + case C.DomainStrategyIPv4Only: destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4) if len(destinationAddresses) == 0 { return nil, E.New("no IPv4 address available for ", destination) } - case dns.DomainStrategyUseIPv6: + case C.DomainStrategyIPv6Only: destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6) if len(destinationAddresses) == 0 { return nil, E.New("no IPv6 address available for ", destination) } } - return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay) + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay) } func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { diff --git a/protocol/dns/handle.go b/protocol/dns/handle.go index bc58d9e208..c4ad79d9ca 100644 --- a/protocol/dns/handle.go +++ b/protocol/dns/handle.go @@ -7,7 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -19,7 +19,7 @@ import ( mDNS "github.com/miekg/dns" ) -func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net.Conn, metadata adapter.InboundContext) error { +func HandleStreamDNSRequest(ctx context.Context, router adapter.DNSRouter, conn net.Conn, metadata adapter.InboundContext) error { var queryLength uint16 err := binary.Read(conn, binary.BigEndian, &queryLength) if err != nil { @@ -41,7 +41,7 @@ func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net } metadataInQuery := metadata go func() error { - response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message, adapter.DNSQueryOptions{}) if err != nil { conn.Close() return err @@ -61,7 +61,7 @@ func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net return nil } -func NewDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error { +func NewDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn var counters []N.CountFunc @@ -123,7 +123,7 @@ func NewDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.P } metadataInQuery := metadata go func() error { - response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message, adapter.DNSQueryOptions{}) if err != nil { cancel(err) return err @@ -148,7 +148,7 @@ func NewDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.P return group.Run(fastClose) } -func newDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { +func newDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { fastClose, cancel := common.ContextWithCancelCause(ctx) timeout := canceler.New(fastClose, cancel, C.DNSTimeout) var group task.Group @@ -193,7 +193,7 @@ func newDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.P } metadataInQuery := metadata go func() error { - response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message, adapter.DNSQueryOptions{}) if err != nil { cancel(err) return err diff --git a/protocol/dns/outbound.go b/protocol/dns/outbound.go index 5f06557ba4..277d7454ea 100644 --- a/protocol/dns/outbound.go +++ b/protocol/dns/outbound.go @@ -14,6 +14,7 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) func RegisterOutbound(registry *outbound.Registry) { @@ -22,14 +23,14 @@ func RegisterOutbound(registry *outbound.Registry) { type Outbound struct { outbound.Adapter - router adapter.Router + router adapter.DNSRouter logger logger.ContextLogger } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) { return &Outbound{ Adapter: outbound.NewAdapter(C.TypeDNS, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), - router: router, + router: service.FromContext[adapter.DNSRouter](ctx), logger: logger, }, nil } diff --git a/protocol/socks/outbound.go b/protocol/socks/outbound.go index 0632f0825f..323149e256 100644 --- a/protocol/socks/outbound.go +++ b/protocol/socks/outbound.go @@ -17,6 +17,7 @@ import ( N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/protocol/socks" + "github.com/sagernet/sing/service" ) func RegisterOutbound(registry *outbound.Registry) { @@ -27,7 +28,7 @@ var _ adapter.Outbound = (*Outbound)(nil) type Outbound struct { outbound.Adapter - router adapter.Router + dnsRouter adapter.DNSRouter logger logger.ContextLogger client *socks.Client resolve bool @@ -50,11 +51,11 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, tag, options.Network.Build(), options.DialerOptions), - router: router, - logger: logger, - client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), - resolve: version == socks.Version4, + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, tag, options.Network.Build(), options.DialerOptions), + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + logger: logger, + client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), + resolve: version == socks.Version4, } uotOptions := common.PtrValueOrDefault(options.UDPOverTCP) if uotOptions.Enabled { @@ -83,7 +84,7 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination return nil, E.Extend(N.ErrUnknownNetwork, network) } if h.resolve && destination.IsFqdn() { - destinationAddresses, err := h.router.LookupDefault(ctx, destination.Fqdn) + destinationAddresses, err := h.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) if err != nil { return nil, err } @@ -101,7 +102,7 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return h.uotClient.ListenPacket(ctx, destination) } if h.resolve && destination.IsFqdn() { - destinationAddresses, err := h.router.LookupDefault(ctx, destination.Fqdn) + destinationAddresses, err := h.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) if err != nil { return nil, err } diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 2f6825fd5c..1dead5c982 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -13,13 +13,13 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/wireguard" - "github.com/sagernet/sing-dns" "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" + "github.com/sagernet/sing/service" ) func RegisterEndpoint(registry *endpoint.Registry) { @@ -30,6 +30,7 @@ type Endpoint struct { endpoint.Adapter ctx context.Context router adapter.Router + dnsRouter adapter.DNSRouter logger logger.ContextLogger localAddresses []netip.Prefix endpoint *wireguard.Endpoint @@ -40,6 +41,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), ctx: ctx, router: router, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), logger: logger, localAddresses: options.Address, } @@ -74,7 +76,9 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL PrivateKey: options.PrivateKey, ListenPort: options.ListenPort, ResolvePeer: func(domain string) (netip.Addr, error) { - endpointAddresses, lookupErr := router.Lookup(ctx, domain, dns.DomainStrategy(options.DomainStrategy)) + endpointAddresses, lookupErr := ep.dnsRouter.Lookup(ctx, domain, adapter.DNSQueryOptions{ + Strategy: C.DomainStrategy(options.DomainStrategy), + }) if lookupErr != nil { return netip.Addr{}, lookupErr } @@ -175,7 +179,7 @@ func (w *Endpoint) DialContext(ctx context.Context, network string, destination w.logger.InfoContext(ctx, "outbound packet connection to ", destination) } if destination.IsFqdn() { - destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) + destinationAddresses, err := w.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) if err != nil { return nil, err } @@ -189,7 +193,7 @@ func (w *Endpoint) DialContext(ctx context.Context, network string, destination func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { w.logger.InfoContext(ctx, "outbound packet connection to ", destination) if destination.IsFqdn() { - destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) + destinationAddresses, err := w.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) if err != nil { return nil, err } diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 808dade45c..47e1dac595 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -13,12 +13,12 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/wireguard" - "github.com/sagernet/sing-dns" "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" ) func RegisterOutbound(registry *outbound.Registry) { @@ -28,7 +28,7 @@ func RegisterOutbound(registry *outbound.Registry) { type Outbound struct { outbound.Adapter ctx context.Context - router adapter.Router + dnsRouter adapter.DNSRouter logger logger.ContextLogger localAddresses []netip.Prefix endpoint *wireguard.Endpoint @@ -42,7 +42,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL outbound := &Outbound{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), ctx: ctx, - router: router, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), logger: logger, localAddresses: options.LocalAddress, } @@ -89,7 +89,9 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL Address: options.LocalAddress, PrivateKey: options.PrivateKey, ResolvePeer: func(domain string) (netip.Addr, error) { - endpointAddresses, lookupErr := router.Lookup(ctx, domain, dns.DomainStrategy(options.DomainStrategy)) + endpointAddresses, lookupErr := outbound.dnsRouter.Lookup(ctx, domain, adapter.DNSQueryOptions{ + Strategy: C.DomainStrategy(options.DomainStrategy), + }) if lookupErr != nil { return netip.Addr{}, lookupErr } @@ -127,7 +129,7 @@ func (o *Outbound) DialContext(ctx context.Context, network string, destination o.logger.InfoContext(ctx, "outbound packet connection to ", destination) } if destination.IsFqdn() { - destinationAddresses, err := o.router.LookupDefault(ctx, destination.Fqdn) + destinationAddresses, err := o.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) if err != nil { return nil, err } @@ -141,7 +143,7 @@ func (o *Outbound) DialContext(ctx context.Context, network string, 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.router.LookupDefault(ctx, destination.Fqdn) + destinationAddresses, err := o.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) if err != nil { return nil, err } diff --git a/release/config/config.json b/release/config/config.json index c518d18bd9..bdc78d40ad 100644 --- a/release/config/config.json +++ b/release/config/config.json @@ -14,10 +14,13 @@ "type": "shadowsocks", "listen": "::", "listen_port": 8080, - "sniff": true, "network": "tcp", "method": "2022-blake3-aes-128-gcm", - "password": "8JCsPssfgS8tiRwiMlhARg==" + "password": "Gn1JUS14bLUHgv1cWDDp4A==", + "multiplex": { + "enabled": true, + "padding": true + } } ], "outbounds": [ @@ -32,7 +35,7 @@ "route": { "rules": [ { - "protocol": "dns", + "port": 53, "outbound": "dns-out" } ] diff --git a/route/dns.go b/route/dns.go index 6545ce04e7..0a5d7776f0 100644 --- a/route/dns.go +++ b/route/dns.go @@ -7,11 +7,12 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" dnsOutbound "github.com/sagernet/sing-box/protocol/dns" R "github.com/sagernet/sing-box/route/rule" - "github.com/sagernet/sing-dns" "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" "github.com/sagernet/sing/common/udpnat2" @@ -23,7 +24,7 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad metadata.Destination = M.Socksaddr{} for { conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) - err := dnsOutbound.HandleStreamDNSRequest(ctx, r, conn, metadata) + err := dnsOutbound.HandleStreamDNSRequest(ctx, r.dns, conn, metadata) if err != nil { return err } @@ -37,10 +38,11 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB buffer := packet.Buffer destination := packet.Destination N.PutPacketBuffer(packet) - go ExchangeDNSPacket(ctx, r, natConn, buffer, metadata, destination) + go ExchangeDNSPacket(ctx, r.dns, r.logger, natConn, buffer, metadata, destination) } natConn.SetHandler(&dnsHijacker{ - router: r, + router: r.dns, + logger: r.logger, conn: conn, ctx: ctx, metadata: metadata, @@ -48,28 +50,28 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB }) return } - err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) + err := dnsOutbound.NewDNSPacketConnection(ctx, r.dns, conn, packetBuffers, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil && !E.IsClosedOrCanceled(err) { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) + r.logger.ErrorContext(ctx, E.Cause(err, "process DNS packet connection")) } } -func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) { +func ExchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, logger logger.ContextLogger, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) { err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination) if err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) { - router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) + logger.ErrorContext(ctx, E.Cause(err, "process DNS packet connection")) } } -func exchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) error { +func exchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) error { var message mDNS.Msg err := message.Unpack(buffer.Bytes()) buffer.Release() if err != nil { return E.Cause(err, "unpack request") } - response, err := router.Exchange(adapter.WithContext(ctx, &metadata), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{}) if err != nil { return err } @@ -82,7 +84,8 @@ func exchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, b } type dnsHijacker struct { - router *Router + router adapter.DNSRouter + logger logger.ContextLogger conn N.PacketConn ctx context.Context metadata adapter.InboundContext @@ -90,7 +93,7 @@ type dnsHijacker struct { } func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) { - go ExchangeDNSPacket(h.ctx, h.router, h.conn, buffer, h.metadata, destination) + go ExchangeDNSPacket(h.ctx, h.router, h.logger, h.conn, buffer, h.metadata, destination) } func (h *dnsHijacker) Close() error { diff --git a/route/geo_resources.go b/route/geo_resources.go deleted file mode 100644 index 8a8a3ef549..0000000000 --- a/route/geo_resources.go +++ /dev/null @@ -1,246 +0,0 @@ -package route - -import ( - "context" - "io" - "net" - "net/http" - "os" - "path/filepath" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/geoip" - "github.com/sagernet/sing-box/common/geosite" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" - R "github.com/sagernet/sing-box/route/rule" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/service/filemanager" -) - -func (r *Router) GeoIPReader() *geoip.Reader { - return r.geoIPReader -} - -func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { - rule, cached := r.geositeCache[code] - if cached { - return rule, nil - } - items, err := r.geositeReader.Read(code) - if err != nil { - return nil, err - } - rule, err = R.NewDefaultRule(r.ctx, nil, geosite.Compile(items)) - if err != nil { - return nil, err - } - r.geositeCache[code] = rule - return rule, nil -} - -func (r *Router) prepareGeoIPDatabase() error { - deprecated.Report(r.ctx, deprecated.OptionGEOIP) - var geoPath string - if r.geoIPOptions.Path != "" { - geoPath = r.geoIPOptions.Path - } else { - geoPath = "geoip.db" - if foundPath, loaded := C.FindPath(geoPath); loaded { - geoPath = foundPath - } - } - if !rw.IsFile(geoPath) { - geoPath = filemanager.BasePath(r.ctx, geoPath) - } - if stat, err := os.Stat(geoPath); err == nil { - if stat.IsDir() { - return E.New("geoip path is a directory: ", geoPath) - } - if stat.Size() == 0 { - os.Remove(geoPath) - } - } - if !rw.IsFile(geoPath) { - r.logger.Warn("geoip database not exists: ", geoPath) - var err error - for attempts := 0; attempts < 3; attempts++ { - err = r.downloadGeoIPDatabase(geoPath) - if err == nil { - break - } - r.logger.Error("download geoip database: ", err) - os.Remove(geoPath) - // time.Sleep(10 * time.Second) - } - if err != nil { - return err - } - } - geoReader, codes, err := geoip.Open(geoPath) - if err != nil { - return E.Cause(err, "open geoip database") - } - r.logger.Info("loaded geoip database: ", len(codes), " codes") - r.geoIPReader = geoReader - return nil -} - -func (r *Router) prepareGeositeDatabase() error { - deprecated.Report(r.ctx, deprecated.OptionGEOSITE) - var geoPath string - if r.geositeOptions.Path != "" { - geoPath = r.geositeOptions.Path - } else { - geoPath = "geosite.db" - if foundPath, loaded := C.FindPath(geoPath); loaded { - geoPath = foundPath - } - } - if !rw.IsFile(geoPath) { - geoPath = filemanager.BasePath(r.ctx, geoPath) - } - if stat, err := os.Stat(geoPath); err == nil { - if stat.IsDir() { - return E.New("geoip path is a directory: ", geoPath) - } - if stat.Size() == 0 { - os.Remove(geoPath) - } - } - if !rw.IsFile(geoPath) { - r.logger.Warn("geosite database not exists: ", geoPath) - var err error - for attempts := 0; attempts < 3; attempts++ { - err = r.downloadGeositeDatabase(geoPath) - if err == nil { - break - } - r.logger.Error("download geosite database: ", err) - os.Remove(geoPath) - } - if err != nil { - return err - } - } - geoReader, codes, err := geosite.Open(geoPath) - if err == nil { - r.logger.Info("loaded geosite database: ", len(codes), " codes") - r.geositeReader = geoReader - } else { - return E.Cause(err, "open geosite database") - } - return nil -} - -func (r *Router) downloadGeoIPDatabase(savePath string) error { - var downloadURL string - if r.geoIPOptions.DownloadURL != "" { - downloadURL = r.geoIPOptions.DownloadURL - } else { - downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db" - } - r.logger.Info("downloading geoip database") - var detour adapter.Outbound - if r.geoIPOptions.DownloadDetour != "" { - outbound, loaded := r.outbound.Outbound(r.geoIPOptions.DownloadDetour) - if !loaded { - return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour) - } - detour = outbound - } else { - detour = r.outbound.Default() - } - - if parentDir := filepath.Dir(savePath); parentDir != "" { - filemanager.MkdirAll(r.ctx, parentDir, 0o755) - } - - httpClient := &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: C.TCPTimeout, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - defer httpClient.CloseIdleConnections() - request, err := http.NewRequest("GET", downloadURL, nil) - if err != nil { - return err - } - response, err := httpClient.Do(request.WithContext(r.ctx)) - if err != nil { - return err - } - defer response.Body.Close() - - saveFile, err := filemanager.Create(r.ctx, savePath) - if err != nil { - return E.Cause(err, "open output file: ", downloadURL) - } - _, err = io.Copy(saveFile, response.Body) - saveFile.Close() - if err != nil { - filemanager.Remove(r.ctx, savePath) - } - return err -} - -func (r *Router) downloadGeositeDatabase(savePath string) error { - var downloadURL string - if r.geositeOptions.DownloadURL != "" { - downloadURL = r.geositeOptions.DownloadURL - } else { - downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db" - } - r.logger.Info("downloading geosite database") - var detour adapter.Outbound - if r.geositeOptions.DownloadDetour != "" { - outbound, loaded := r.outbound.Outbound(r.geositeOptions.DownloadDetour) - if !loaded { - return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour) - } - detour = outbound - } else { - detour = r.outbound.Default() - } - - if parentDir := filepath.Dir(savePath); parentDir != "" { - filemanager.MkdirAll(r.ctx, parentDir, 0o755) - } - - httpClient := &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: C.TCPTimeout, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - defer httpClient.CloseIdleConnections() - request, err := http.NewRequest("GET", downloadURL, nil) - if err != nil { - return err - } - response, err := httpClient.Do(request.WithContext(r.ctx)) - if err != nil { - return err - } - defer response.Body.Close() - - saveFile, err := filemanager.Create(r.ctx, savePath) - if err != nil { - return E.Cause(err, "open output file: ", downloadURL) - } - _, err = io.Copy(saveFile, response.Body) - saveFile.Close() - if err != nil { - filemanager.Remove(r.ctx, savePath) - } - return err -} diff --git a/route/route.go b/route/route.go index f50950b46e..91aa020abe 100644 --- a/route/route.go +++ b/route/route.go @@ -17,7 +17,6 @@ import ( 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-dns" "github.com/sagernet/sing-mux" "github.com/sagernet/sing-vmess" "github.com/sagernet/sing/common" @@ -316,22 +315,23 @@ func (r *Router) matchRule( metadata.ProcessInfo = processInfo } } - if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { - domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr) + if metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) { + domain, loaded := r.dnsTransport.FakeIP().Store().Lookup(metadata.Destination.Addr) if !loaded { - fatalErr = E.New("missing fakeip record, try to configure experimental.cache_file") + fatalErr = E.New("missing fakeip record, try enable `experimental.cache_file`") return } - metadata.OriginDestination = metadata.Destination - metadata.Destination = M.Socksaddr{ - Fqdn: domain, - Port: metadata.Destination.Port, + if domain != "" { + metadata.OriginDestination = metadata.Destination + metadata.Destination = M.Socksaddr{ + Fqdn: domain, + Port: metadata.Destination.Port, + } + metadata.FakeIP = true + r.logger.DebugContext(ctx, "found fakeip domain: ", domain) } - metadata.FakeIP = true - r.logger.DebugContext(ctx, "found fakeip domain: ", domain) - } - if r.dnsReverseMapping != nil && metadata.Domain == "" { - domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) + } else if metadata.Domain == "" { + domain, loaded := r.dns.LookupReverseMapping(metadata.Destination.Addr) if loaded { metadata.Domain = domain r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) @@ -360,9 +360,9 @@ func (r *Router) matchRule( packetBuffers = newPackerBuffers } } - if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { + if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS { fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{ - Strategy: dns.DomainStrategy(metadata.InboundOptions.DomainStrategy), + Strategy: C.DomainStrategy(metadata.InboundOptions.DomainStrategy), }) if fatalErr != nil { return @@ -651,13 +651,23 @@ func (r *Router) actionSniff( func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionResolve) error { if metadata.Destination.IsFqdn() { - metadata.DNSServer = action.Server - addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy) + var transport adapter.DNSTransport + if action.Server != "" { + var loaded bool + transport, loaded = r.dnsTransport.Transport(action.Server) + if !loaded { + return E.New("DNS server not found: ", action.Server) + } + } + addresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{ + Transport: transport, + Strategy: action.Strategy, + }) if err != nil { return err } metadata.DestinationAddresses = addresses - r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") + r.logger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") if metadata.Destination.IsIPv4() { metadata.IPVersion = 4 } else if metadata.Destination.IsIPv6() { diff --git a/route/route_dns.go b/route/route_dns.go deleted file mode 100644 index c5aced0992..0000000000 --- a/route/route_dns.go +++ /dev/null @@ -1,355 +0,0 @@ -package route - -import ( - "context" - "errors" - "net/netip" - "strings" - "time" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - R "github.com/sagernet/sing-box/route/rule" - "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/cache" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - M "github.com/sagernet/sing/common/metadata" - - mDNS "github.com/miekg/dns" -) - -type DNSReverseMapping struct { - cache *cache.LruCache[netip.Addr, string] -} - -func NewDNSReverseMapping() *DNSReverseMapping { - return &DNSReverseMapping{ - cache: cache.New[netip.Addr, string](), - } -} - -func (m *DNSReverseMapping) Save(address netip.Addr, domain string, ttl int) { - m.cache.StoreWithExpire(address, domain, time.Now().Add(time.Duration(ttl)*time.Second)) -} - -func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) { - domain, loaded := m.cache.Load(address) - return domain, loaded -} - -func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, isAddressQuery bool) (dns.Transport, dns.QueryOptions, adapter.DNSRule, int) { - metadata := adapter.ContextFrom(ctx) - if metadata == nil { - panic("no context") - } - var options dns.QueryOptions - var currentRuleIndex int - if ruleIndex != -1 { - currentRuleIndex = ruleIndex + 1 - } - for ; currentRuleIndex < len(r.dnsRules); currentRuleIndex++ { - currentRule := r.dnsRules[currentRuleIndex] - if currentRule.WithAddressLimit() && !isAddressQuery { - continue - } - metadata.ResetRuleCache() - if currentRule.Match(metadata) { - ruleDescription := currentRule.String() - if ruleDescription != "" { - r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) - } else { - r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) - } - switch action := currentRule.Action().(type) { - case *R.RuleActionDNSRoute: - transport, loaded := r.transportMap[action.Server] - if !loaded { - r.dnsLogger.ErrorContext(ctx, "transport not found: ", action.Server) - continue - } - _, isFakeIP := transport.(adapter.FakeIPTransport) - if isFakeIP && !allowFakeIP { - continue - } - if isFakeIP || action.DisableCache { - options.DisableCache = true - } - if action.RewriteTTL != nil { - options.RewriteTTL = action.RewriteTTL - } - if action.ClientSubnet.IsValid() { - options.ClientSubnet = action.ClientSubnet - } - if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { - options.Strategy = domainStrategy - } else { - options.Strategy = r.defaultDomainStrategy - } - r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) - return transport, options, currentRule, currentRuleIndex - case *R.RuleActionDNSRouteOptions: - if action.DisableCache { - options.DisableCache = true - } - if action.RewriteTTL != nil { - options.RewriteTTL = action.RewriteTTL - } - if action.ClientSubnet.IsValid() { - options.ClientSubnet = action.ClientSubnet - } - r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) - case *R.RuleActionReject: - r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) - return nil, options, currentRule, currentRuleIndex - } - } - } - if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded { - options.Strategy = domainStrategy - } else { - options.Strategy = r.defaultDomainStrategy - } - return r.defaultTransport, options, nil, -1 -} - -func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - if len(message.Question) != 1 { - r.dnsLogger.WarnContext(ctx, "bad question size: ", len(message.Question)) - responseMessage := mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Response: true, - Rcode: mDNS.RcodeFormatError, - }, - Question: message.Question, - } - return &responseMessage, nil - } - var ( - response *mDNS.Msg - cached bool - transport dns.Transport - err error - ) - response, cached = r.dnsClient.ExchangeCache(ctx, message) - if !cached { - var metadata *adapter.InboundContext - ctx, metadata = adapter.ExtendContext(ctx) - metadata.Destination = M.Socksaddr{} - metadata.QueryType = message.Question[0].Qtype - switch metadata.QueryType { - case mDNS.TypeA: - metadata.IPVersion = 4 - case mDNS.TypeAAAA: - metadata.IPVersion = 6 - } - metadata.Domain = fqdnToDomain(message.Question[0].Name) - var ( - options dns.QueryOptions - rule adapter.DNSRule - ruleIndex int - ) - ruleIndex = -1 - for { - dnsCtx := adapter.OverrideContext(ctx) - var addressLimit bool - transport, options, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) - if rule != nil { - switch action := rule.Action().(type) { - case *R.RuleActionReject: - switch action.Method { - case C.RuleActionRejectMethodDefault: - return &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Rcode: mDNS.RcodeRefused, - Response: true, - }, - Question: []mDNS.Question{message.Question[0]}, - }, nil - case C.RuleActionRejectMethodDrop: - return nil, &R.RejectedError{Cause: tun.ErrDrop} - } - } - } - r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()), " via ", transport.Name()) - if rule != nil && rule.WithAddressLimit() { - addressLimit = true - response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(responseAddrs []netip.Addr) bool { - metadata.DestinationAddresses = responseAddrs - return rule.MatchAddressLimit(metadata) - }) - } else { - addressLimit = false - response, err = r.dnsClient.Exchange(dnsCtx, transport, message, options) - } - var rejected bool - if err != nil { - if errors.Is(err, dns.ErrResponseRejectedCached) { - rejected = true - r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String())), " (cached)") - } else if errors.Is(err, dns.ErrResponseRejected) { - rejected = true - r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String()))) - } else if len(message.Question) > 0 { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String()))) - } else { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ")) - } - } - if addressLimit && rejected { - continue - } - break - } - } - if err != nil { - return nil, err - } - if r.dnsReverseMapping != nil && response != nil && len(response.Answer) > 0 { - if _, isFakeIP := transport.(adapter.FakeIPTransport); !isFakeIP { - for _, answer := range response.Answer { - switch record := answer.(type) { - case *mDNS.A: - r.dnsReverseMapping.Save(M.AddrFromIP(record.A), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) - case *mDNS.AAAA: - r.dnsReverseMapping.Save(M.AddrFromIP(record.AAAA), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) - } - } - } - } - return response, nil -} - -func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { - var ( - responseAddrs []netip.Addr - cached bool - err error - ) - printResult := func() { - if err == nil && len(responseAddrs) == 0 { - err = E.New("empty result") - } - if err != nil { - if errors.Is(err, dns.ErrResponseRejectedCached) { - r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)") - } else if errors.Is(err, dns.ErrResponseRejected) { - r.dnsLogger.DebugContext(ctx, "response rejected for ", domain) - } else { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) - } - } - } - responseAddrs, cached = r.dnsClient.LookupCache(ctx, domain, strategy) - if cached { - if len(responseAddrs) == 0 { - return nil, dns.RCodeNameError - } - return responseAddrs, nil - } - r.dnsLogger.DebugContext(ctx, "lookup domain ", domain) - ctx, metadata := adapter.ExtendContext(ctx) - metadata.Destination = M.Socksaddr{} - metadata.Domain = domain - if metadata.DNSServer != "" { - transport, loaded := r.transportMap[metadata.DNSServer] - if !loaded { - return nil, E.New("transport not found: ", metadata.DNSServer) - } - if strategy == dns.DomainStrategyAsIS { - if transportDomainStrategy, loaded := r.transportDomainStrategy[transport]; loaded { - strategy = transportDomainStrategy - } else { - strategy = r.defaultDomainStrategy - } - } - responseAddrs, err = r.dnsClient.Lookup(ctx, transport, domain, dns.QueryOptions{Strategy: strategy}) - } else { - var ( - transport dns.Transport - options dns.QueryOptions - rule adapter.DNSRule - ruleIndex int - ) - ruleIndex = -1 - for { - dnsCtx := adapter.OverrideContext(ctx) - var addressLimit bool - transport, options, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) - if strategy != dns.DomainStrategyAsIS { - options.Strategy = strategy - } - if rule != nil { - switch action := rule.Action().(type) { - case *R.RuleActionReject: - switch action.Method { - case C.RuleActionRejectMethodDefault: - return nil, nil - case C.RuleActionRejectMethodDrop: - return nil, &R.RejectedError{Cause: tun.ErrDrop} - } - } - } - if rule != nil && rule.WithAddressLimit() { - addressLimit = true - responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, options, func(responseAddrs []netip.Addr) bool { - metadata.DestinationAddresses = responseAddrs - return rule.MatchAddressLimit(metadata) - }) - } else { - addressLimit = false - responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, options) - } - if !addressLimit || err == nil { - break - } - printResult() - } - } - printResult() - if len(responseAddrs) > 0 { - r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " ")) - } - return responseAddrs, err -} - -func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) { - return r.Lookup(ctx, domain, dns.DomainStrategyAsIS) -} - -func (r *Router) ClearDNSCache() { - r.dnsClient.ClearCache() - if r.platformInterface != nil { - r.platformInterface.ClearDNSCache() - } -} - -func isAddressQuery(message *mDNS.Msg) bool { - for _, question := range message.Question { - if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA || question.Qtype == mDNS.TypeHTTPS { - return true - } - } - return false -} - -func fqdnToDomain(fqdn string) string { - if mDNS.IsFqdn(fqdn) { - return fqdn[:len(fqdn)-1] - } - return fqdn -} - -func formatQuestion(string string) string { - if strings.HasPrefix(string, ";") { - string = string[1:] - } - string = strings.ReplaceAll(string, "\t", " ") - for strings.Contains(string, " ") { - string = strings.ReplaceAll(string, " ", " ") - } - return string -} diff --git a/route/router.go b/route/router.go index b74af8b930..0872421ff9 100644 --- a/route/router.go +++ b/route/router.go @@ -2,17 +2,10 @@ package route import ( "context" - "net/netip" - "net/url" "os" "runtime" - "strings" - "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/common/geoip" - "github.com/sagernet/sing-box/common/geosite" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" @@ -20,13 +13,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" R "github.com/sagernet/sing-box/route/rule" - "github.com/sagernet/sing-box/transport/fakeip" - "github.com/sagernet/sing-dns" - "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/task" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" @@ -35,334 +22,71 @@ import ( var _ adapter.Router = (*Router)(nil) type Router struct { - ctx context.Context - logger log.ContextLogger - dnsLogger log.ContextLogger - inbound adapter.InboundManager - outbound adapter.OutboundManager - connection adapter.ConnectionManager - network adapter.NetworkManager - rules []adapter.Rule - needGeoIPDatabase bool - needGeositeDatabase bool - geoIPOptions option.GeoIPOptions - geositeOptions option.GeositeOptions - geoIPReader *geoip.Reader - geositeReader *geosite.Reader - geositeCache map[string]adapter.Rule - needFindProcess bool - dnsClient *dns.Client - defaultDomainStrategy dns.DomainStrategy - dnsRules []adapter.DNSRule - ruleSets []adapter.RuleSet - ruleSetMap map[string]adapter.RuleSet - defaultTransport dns.Transport - transports []dns.Transport - transportMap map[string]dns.Transport - transportDomainStrategy map[dns.Transport]dns.DomainStrategy - dnsReverseMapping *DNSReverseMapping - fakeIPStore adapter.FakeIPStore - processSearcher process.Searcher - pauseManager pause.Manager - trackers []adapter.ConnectionTracker - platformInterface platform.Interface - needWIFIState bool - started bool + ctx context.Context + logger log.ContextLogger + inbound adapter.InboundManager + outbound adapter.OutboundManager + dns adapter.DNSRouter + dnsTransport adapter.DNSTransportManager + connection adapter.ConnectionManager + network adapter.NetworkManager + rules []adapter.Rule + needFindProcess bool + ruleSets []adapter.RuleSet + ruleSetMap map[string]adapter.RuleSet + processSearcher process.Searcher + pauseManager pause.Manager + trackers []adapter.ConnectionTracker + platformInterface platform.Interface + needWIFIState bool + started bool } -func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) { - router := &Router{ - ctx: ctx, - logger: logFactory.NewLogger("router"), - dnsLogger: logFactory.NewLogger("dns"), - inbound: service.FromContext[adapter.InboundManager](ctx), - outbound: service.FromContext[adapter.OutboundManager](ctx), - connection: service.FromContext[adapter.ConnectionManager](ctx), - network: service.FromContext[adapter.NetworkManager](ctx), - rules: make([]adapter.Rule, 0, len(options.Rules)), - dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)), - ruleSetMap: make(map[string]adapter.RuleSet), - needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule), - needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule), - geoIPOptions: common.PtrValueOrDefault(options.GeoIP), - geositeOptions: common.PtrValueOrDefault(options.Geosite), - geositeCache: make(map[string]adapter.Rule), - needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, - defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy), - pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: service.FromContext[platform.Interface](ctx), - needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), +func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions) *Router { + return &Router{ + ctx: ctx, + logger: logFactory.NewLogger("router"), + inbound: service.FromContext[adapter.InboundManager](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + dns: service.FromContext[adapter.DNSRouter](ctx), + dnsTransport: service.FromContext[adapter.DNSTransportManager](ctx), + connection: service.FromContext[adapter.ConnectionManager](ctx), + network: service.FromContext[adapter.NetworkManager](ctx), + rules: make([]adapter.Rule, 0, len(options.Rules)), + 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), + needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), } - service.MustRegister[adapter.Router](ctx, router) - router.dnsClient = dns.NewClient(dns.ClientOptions{ - DisableCache: dnsOptions.DNSClientOptions.DisableCache, - DisableExpire: dnsOptions.DNSClientOptions.DisableExpire, - IndependentCache: dnsOptions.DNSClientOptions.IndependentCache, - CacheCapacity: dnsOptions.DNSClientOptions.CacheCapacity, - RDRC: func() dns.RDRCStore { - cacheFile := service.FromContext[adapter.CacheFile](ctx) - if cacheFile == nil { - return nil - } - if !cacheFile.StoreRDRC() { - return nil - } - return cacheFile - }, - Logger: router.dnsLogger, - }) - for i, ruleOptions := range options.Rules { - routeRule, err := R.NewRule(ctx, router.logger, ruleOptions, true) - if err != nil { - return nil, E.Cause(err, "parse rule[", i, "]") - } - router.rules = append(router.rules, routeRule) - } - for i, dnsRuleOptions := range dnsOptions.Rules { - dnsRule, err := R.NewDNSRule(ctx, router.logger, dnsRuleOptions, true) +} + +func (r *Router) Initialize(rules []option.Rule, ruleSets []option.RuleSet) error { + for i, options := range rules { + rule, err := R.NewRule(r.ctx, r.logger, options, false) if err != nil { - return nil, E.Cause(err, "parse dns rule[", i, "]") + return E.Cause(err, "parse rule[", i, "]") } - router.dnsRules = append(router.dnsRules, dnsRule) + r.rules = append(r.rules, rule) } - for i, ruleSetOptions := range options.RuleSet { - if _, exists := router.ruleSetMap[ruleSetOptions.Tag]; exists { - return nil, E.New("duplicate rule-set tag: ", ruleSetOptions.Tag) + for i, options := range ruleSets { + if _, exists := r.ruleSetMap[options.Tag]; exists { + return E.New("duplicate rule-set tag: ", options.Tag) } - ruleSet, err := R.NewRuleSet(ctx, router.logger, ruleSetOptions) + ruleSet, err := R.NewRuleSet(r.ctx, r.logger, options) if err != nil { - return nil, E.Cause(err, "parse rule-set[", i, "]") - } - router.ruleSets = append(router.ruleSets, ruleSet) - router.ruleSetMap[ruleSetOptions.Tag] = ruleSet - } - - transports := make([]dns.Transport, len(dnsOptions.Servers)) - dummyTransportMap := make(map[string]dns.Transport) - transportMap := make(map[string]dns.Transport) - transportTags := make([]string, len(dnsOptions.Servers)) - transportTagMap := make(map[string]bool) - transportDomainStrategy := make(map[dns.Transport]dns.DomainStrategy) - for i, server := range dnsOptions.Servers { - var tag string - if server.Tag != "" { - tag = server.Tag - } else { - tag = F.ToString(i) - } - if transportTagMap[tag] { - return nil, E.New("duplicate dns server tag: ", tag) - } - transportTags[i] = tag - transportTagMap[tag] = true - } - outboundManager := service.FromContext[adapter.OutboundManager](ctx) - for { - lastLen := len(dummyTransportMap) - for i, server := range dnsOptions.Servers { - tag := transportTags[i] - if _, exists := dummyTransportMap[tag]; exists { - continue - } - var detour N.Dialer - if server.Detour == "" { - detour = dialer.NewDefaultOutbound(outboundManager) - } else { - detour = dialer.NewDetour(outboundManager, server.Detour) - } - var serverProtocol string - switch server.Address { - case "local": - serverProtocol = "local" - default: - serverURL, _ := url.Parse(server.Address) - var serverAddress string - if serverURL != nil { - if serverURL.Scheme == "" { - serverProtocol = "udp" - } else { - serverProtocol = serverURL.Scheme - } - serverAddress = serverURL.Hostname() - } - if serverAddress == "" { - serverAddress = server.Address - } - notIpAddress := !M.ParseSocksaddr(serverAddress).Addr.IsValid() - if server.AddressResolver != "" { - if !transportTagMap[server.AddressResolver] { - return nil, E.New("parse dns server[", tag, "]: address resolver not found: ", server.AddressResolver) - } - if upstream, exists := dummyTransportMap[server.AddressResolver]; exists { - detour = dns.NewDialerWrapper(detour, router.dnsClient, upstream, dns.DomainStrategy(server.AddressStrategy), time.Duration(server.AddressFallbackDelay)) - } else { - continue - } - } else if notIpAddress && strings.Contains(server.Address, ".") { - return nil, E.New("parse dns server[", tag, "]: missing address_resolver") - } - } - var clientSubnet netip.Prefix - if server.ClientSubnet != nil { - clientSubnet = netip.Prefix(common.PtrValueOrDefault(server.ClientSubnet)) - } else if dnsOptions.ClientSubnet != nil { - clientSubnet = netip.Prefix(common.PtrValueOrDefault(dnsOptions.ClientSubnet)) - } - if serverProtocol == "" { - serverProtocol = "transport" - } - transport, err := dns.CreateTransport(dns.TransportOptions{ - Context: ctx, - Logger: logFactory.NewLogger(F.ToString("dns/", serverProtocol, "[", tag, "]")), - Name: tag, - Dialer: detour, - Address: server.Address, - ClientSubnet: clientSubnet, - }) - if err != nil { - return nil, E.Cause(err, "parse dns server[", tag, "]") - } - transports[i] = transport - dummyTransportMap[tag] = transport - if server.Tag != "" { - transportMap[server.Tag] = transport - } - strategy := dns.DomainStrategy(server.Strategy) - if strategy != dns.DomainStrategyAsIS { - transportDomainStrategy[transport] = strategy - } + return E.Cause(err, "parse rule-set[", i, "]") } - if len(transports) == len(dummyTransportMap) { - break - } - if lastLen != len(dummyTransportMap) { - continue - } - unresolvedTags := common.MapIndexed(common.FilterIndexed(dnsOptions.Servers, func(index int, server option.DNSServerOptions) bool { - _, exists := dummyTransportMap[transportTags[index]] - return !exists - }), func(index int, server option.DNSServerOptions) string { - return transportTags[index] - }) - if len(unresolvedTags) == 0 { - panic(F.ToString("unexpected unresolved dns servers: ", len(transports), " ", len(dummyTransportMap), " ", len(transportMap))) - } - return nil, E.New("found circular reference in dns servers: ", strings.Join(unresolvedTags, " ")) - } - var defaultTransport dns.Transport - if dnsOptions.Final != "" { - defaultTransport = dummyTransportMap[dnsOptions.Final] - if defaultTransport == nil { - return nil, E.New("default dns server not found: ", dnsOptions.Final) - } - } - if defaultTransport == nil { - if len(transports) == 0 { - transports = append(transports, common.Must1(dns.CreateTransport(dns.TransportOptions{ - Context: ctx, - Name: "local", - Address: "local", - Dialer: common.Must1(dialer.NewDefault(ctx, option.DialerOptions{})), - }))) - } - defaultTransport = transports[0] - } - if _, isFakeIP := defaultTransport.(adapter.FakeIPTransport); isFakeIP { - return nil, E.New("default DNS server cannot be fakeip") + r.ruleSets = append(r.ruleSets, ruleSet) + r.ruleSetMap[options.Tag] = ruleSet } - router.defaultTransport = defaultTransport - router.transports = transports - router.transportMap = transportMap - router.transportDomainStrategy = transportDomainStrategy - - if dnsOptions.ReverseMapping { - router.dnsReverseMapping = NewDNSReverseMapping() - } - - if fakeIPOptions := dnsOptions.FakeIP; fakeIPOptions != nil && dnsOptions.FakeIP.Enabled { - var inet4Range netip.Prefix - var inet6Range netip.Prefix - if fakeIPOptions.Inet4Range != nil { - inet4Range = *fakeIPOptions.Inet4Range - } - if fakeIPOptions.Inet6Range != nil { - inet6Range = *fakeIPOptions.Inet6Range - } - router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range) - } - return router, nil + return nil } func (r *Router) Start(stage adapter.StartStage) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { - case adapter.StartStateInitialize: - if r.fakeIPStore != nil { - monitor.Start("initialize fakeip store") - err := r.fakeIPStore.Start() - monitor.Finish() - if err != nil { - return err - } - } case adapter.StartStateStart: - if r.needGeoIPDatabase { - monitor.Start("initialize geoip database") - err := r.prepareGeoIPDatabase() - monitor.Finish() - if err != nil { - return err - } - } - if r.needGeositeDatabase { - monitor.Start("initialize geosite database") - err := r.prepareGeositeDatabase() - monitor.Finish() - if err != nil { - return err - } - } - if r.needGeositeDatabase { - for _, rule := range r.rules { - err := rule.UpdateGeosite() - if err != nil { - r.logger.Error("failed to initialize geosite: ", err) - } - } - for _, rule := range r.dnsRules { - err := rule.UpdateGeosite() - if err != nil { - r.logger.Error("failed to initialize geosite: ", err) - } - } - err := common.Close(r.geositeReader) - if err != nil { - return err - } - r.geositeCache = nil - r.geositeReader = nil - } - - monitor.Start("initialize DNS client") - r.dnsClient.Start() - monitor.Finish() - - for i, rule := range r.dnsRules { - monitor.Start("initialize DNS rule[", i, "]") - err := rule.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize DNS rule[", i, "]") - } - } - for i, transport := range r.transports { - monitor.Start("initialize DNS transport[", i, "]") - err := transport.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize DNS server[", i, "]") - } - } var cacheContext *adapter.HTTPStartContext if len(r.ruleSets) > 0 { monitor.Start("initialize rule-set") @@ -438,7 +162,7 @@ func (r *Router) Start(stage adapter.StartStage) error { r.started = true return nil case adapter.StartStateStarted: - for _, ruleSet := range r.ruleSetMap { + for _, ruleSet := range r.ruleSets { ruleSet.Cleanup() } runtime.GC() @@ -456,34 +180,6 @@ func (r *Router) Close() error { }) monitor.Finish() } - for i, rule := range r.dnsRules { - monitor.Start("close dns rule[", i, "]") - err = E.Append(err, rule.Close(), func(err error) error { - return E.Cause(err, "close dns rule[", i, "]") - }) - monitor.Finish() - } - for i, transport := range r.transports { - monitor.Start("close dns transport[", i, "]") - err = E.Append(err, transport.Close(), func(err error) error { - return E.Cause(err, "close dns transport[", i, "]") - }) - monitor.Finish() - } - if r.geoIPReader != nil { - monitor.Start("close geoip reader") - err = E.Append(err, r.geoIPReader.Close(), func(err error) error { - return E.Cause(err, "close geoip reader") - }) - monitor.Finish() - } - if r.fakeIPStore != nil { - monitor.Start("close fakeip store") - err = E.Append(err, r.fakeIPStore.Close(), func(err error) error { - return E.Cause(err, "close fakeip store") - }) - monitor.Finish() - } for i, ruleSet := range r.ruleSets { monitor.Start("close rule-set[", i, "]") err = E.Append(err, ruleSet.Close(), func(err error) error { @@ -494,10 +190,6 @@ func (r *Router) Close() error { return err } -func (r *Router) FakeIPStore() adapter.FakeIPStore { - return r.fakeIPStore -} - func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) { ruleSet, loaded := r.ruleSetMap[tag] return ruleSet, loaded @@ -517,7 +209,5 @@ func (r *Router) AppendTracker(tracker adapter.ConnectionTracker) { func (r *Router) ResetNetwork() { r.network.ResetNetwork() - for _, transport := range r.transports { - transport.Reset() - } + r.dns.ResetNetwork() } diff --git a/route/rule/rule_abstract.go b/route/rule/rule_abstract.go index 6a56934188..5be215e04f 100644 --- a/route/rule/rule_abstract.go +++ b/route/rule/rule_abstract.go @@ -51,18 +51,6 @@ func (r *abstractDefaultRule) Close() error { return nil } -func (r *abstractDefaultRule) UpdateGeosite() error { - for _, item := range r.allItems { - if geositeItem, isSite := item.(*GeositeItem); isSite { - err := geositeItem.Update() - if err != nil { - return err - } - } - } - return nil -} - func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { if len(r.allItems) == 0 { return true @@ -173,19 +161,6 @@ func (r *abstractLogicalRule) Type() string { return C.RuleTypeLogical } -func (r *abstractLogicalRule) UpdateGeosite() error { - for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (adapter.Rule, bool) { - rule, loaded := it.(adapter.Rule) - return rule, loaded - }) { - err := rule.UpdateGeosite() - if err != nil { - return err - } - } - return nil -} - func (r *abstractLogicalRule) Start() error { for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (interface { Start() error diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index ef71e232cb..acb67a5808 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -14,7 +14,6 @@ import ( "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -86,7 +85,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti return sniffAction, sniffAction.build() case C.RuleActionTypeResolve: return &RuleActionResolve{ - Strategy: dns.DomainStrategy(action.ResolveOptions.Strategy), + Strategy: C.DomainStrategy(action.ResolveOptions.Strategy), Server: action.ResolveOptions.Server, }, nil default: @@ -102,6 +101,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) return &RuleActionDNSRoute{ Server: action.RouteOptions.Server, RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ + Strategy: C.DomainStrategy(action.RouteOptions.Strategy), DisableCache: action.RouteOptions.DisableCache, RewriteTTL: action.RouteOptions.RewriteTTL, ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), @@ -109,6 +109,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) } case C.RuleActionTypeRouteOptions: return &RuleActionDNSRouteOptions{ + Strategy: C.DomainStrategy(action.RouteOptionsOptions.Strategy), DisableCache: action.RouteOptionsOptions.DisableCache, RewriteTTL: action.RouteOptionsOptions.RewriteTTL, ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)), @@ -215,6 +216,7 @@ func (r *RuleActionDNSRoute) String() string { } type RuleActionDNSRouteOptions struct { + Strategy C.DomainStrategy DisableCache bool RewriteTTL *uint32 ClientSubnet netip.Prefix @@ -383,7 +385,7 @@ func (r *RuleActionSniff) String() string { } type RuleActionResolve struct { - Strategy dns.DomainStrategy + Strategy C.DomainStrategy Server string } @@ -392,11 +394,11 @@ func (r *RuleActionResolve) Type() string { } func (r *RuleActionResolve) String() string { - if r.Strategy == dns.DomainStrategyAsIS && r.Server == "" { + if r.Strategy == C.DomainStrategyAsIS && r.Server == "" { return F.ToString("resolve") - } else if r.Strategy != dns.DomainStrategyAsIS && r.Server == "" { + } else if r.Strategy != C.DomainStrategyAsIS && r.Server == "" { return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ")") - } else if r.Strategy == dns.DomainStrategyAsIS && r.Server != "" { + } else if r.Strategy == C.DomainStrategyAsIS && r.Server != "" { return F.ToString("resolve(", r.Server, ")") } else { return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ",", r.Server, ")") diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 56cf32dc07..08e21e5f3a 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -123,19 +123,13 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.allItems = append(rule.allItems, item) } if len(options.Geosite) > 0 { - item := NewGeositeItem(router, logger, options.Geosite) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) - rule.allItems = append(rule.allItems, item) + return nil, E.New("geosite database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") } if len(options.SourceGeoIP) > 0 { - item := NewGeoIPItem(router, logger, true, options.SourceGeoIP) - rule.sourceAddressItems = append(rule.sourceAddressItems, item) - rule.allItems = append(rule.allItems, item) + return nil, E.New("geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") } if len(options.GeoIP) > 0 { - item := NewGeoIPItem(router, logger, false, options.GeoIP) - rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) - rule.allItems = append(rule.allItems, item) + return nil, E.New("geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") } if len(options.SourceIPCIDR) > 0 { item, err := NewIPCIDRItem(true, options.SourceIPCIDR) diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index d6eb7104f5..ccd487334a 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -114,19 +114,13 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if len(options.Geosite) > 0 { - item := NewGeositeItem(router, logger, options.Geosite) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) - rule.allItems = append(rule.allItems, item) + return nil, E.New("geosite database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") } if len(options.SourceGeoIP) > 0 { - item := NewGeoIPItem(router, logger, true, options.SourceGeoIP) - rule.sourceAddressItems = append(rule.sourceAddressItems, item) - rule.allItems = append(rule.allItems, item) + return nil, E.New("geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") } if len(options.GeoIP) > 0 { - item := NewGeoIPItem(router, logger, false, options.GeoIP) - rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) - rule.allItems = append(rule.allItems, item) + return nil, E.New("geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") } if len(options.SourceIPCIDR) > 0 { item, err := NewIPCIDRItem(true, options.SourceIPCIDR) @@ -154,6 +148,11 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } + if options.IPAcceptAny { + item := NewIPAcceptAnyItem() + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) + } if len(options.SourcePort) > 0 { item := NewPortItem(true, options.SourcePort) rule.sourcePortItems = append(rule.sourcePortItems, item) diff --git a/route/rule/rule_item_geoip.go b/route/rule/rule_item_geoip.go deleted file mode 100644 index 3c967fec5f..0000000000 --- a/route/rule/rule_item_geoip.go +++ /dev/null @@ -1,98 +0,0 @@ -package rule - -import ( - "net/netip" - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - N "github.com/sagernet/sing/common/network" -) - -var _ RuleItem = (*GeoIPItem)(nil) - -type GeoIPItem struct { - router adapter.Router - logger log.ContextLogger - isSource bool - codes []string - codeMap map[string]bool -} - -func NewGeoIPItem(router adapter.Router, logger log.ContextLogger, isSource bool, codes []string) *GeoIPItem { - codeMap := make(map[string]bool) - for _, code := range codes { - codeMap[code] = true - } - return &GeoIPItem{ - router: router, - logger: logger, - codes: codes, - isSource: isSource, - codeMap: codeMap, - } -} - -func (r *GeoIPItem) Match(metadata *adapter.InboundContext) bool { - var geoipCode string - if r.isSource && metadata.SourceGeoIPCode != "" { - geoipCode = metadata.SourceGeoIPCode - } else if !r.isSource && metadata.GeoIPCode != "" { - geoipCode = metadata.GeoIPCode - } - if geoipCode != "" { - return r.codeMap[geoipCode] - } - var destination netip.Addr - if r.isSource { - destination = metadata.Source.Addr - } else { - destination = metadata.Destination.Addr - } - if destination.IsValid() { - return r.match(metadata, destination) - } - for _, destinationAddress := range metadata.DestinationAddresses { - if r.match(metadata, destinationAddress) { - return true - } - } - return false -} - -func (r *GeoIPItem) match(metadata *adapter.InboundContext, destination netip.Addr) bool { - var geoipCode string - geoReader := r.router.GeoIPReader() - if !N.IsPublicAddr(destination) { - geoipCode = "private" - } else if geoReader != nil { - geoipCode = geoReader.Lookup(destination) - } - if geoipCode == "" { - return false - } - if r.isSource { - metadata.SourceGeoIPCode = geoipCode - } else { - metadata.GeoIPCode = geoipCode - } - return r.codeMap[geoipCode] -} - -func (r *GeoIPItem) String() string { - var description string - if r.isSource { - description = "source_geoip=" - } else { - description = "geoip=" - } - cLen := len(r.codes) - if cLen == 1 { - description += r.codes[0] - } else if cLen > 3 { - description += "[" + strings.Join(r.codes[:3], " ") + "...]" - } else { - description += "[" + strings.Join(r.codes, " ") + "]" - } - return description -} diff --git a/route/rule/rule_item_geosite.go b/route/rule/rule_item_geosite.go deleted file mode 100644 index 9e5e03c819..0000000000 --- a/route/rule/rule_item_geosite.go +++ /dev/null @@ -1,61 +0,0 @@ -package rule - -import ( - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - E "github.com/sagernet/sing/common/exceptions" -) - -var _ RuleItem = (*GeositeItem)(nil) - -type GeositeItem struct { - router adapter.Router - logger log.ContextLogger - codes []string - matchers []adapter.Rule -} - -func NewGeositeItem(router adapter.Router, logger log.ContextLogger, codes []string) *GeositeItem { - return &GeositeItem{ - router: router, - logger: logger, - codes: codes, - } -} - -func (r *GeositeItem) Update() error { - matchers := make([]adapter.Rule, 0, len(r.codes)) - for _, code := range r.codes { - matcher, err := r.router.LoadGeosite(code) - if err != nil { - return E.Cause(err, "read geosite") - } - matchers = append(matchers, matcher) - } - r.matchers = matchers - return nil -} - -func (r *GeositeItem) Match(metadata *adapter.InboundContext) bool { - for _, matcher := range r.matchers { - if matcher.Match(metadata) { - return true - } - } - return false -} - -func (r *GeositeItem) String() string { - description := "geosite=" - cLen := len(r.codes) - if cLen == 1 { - description += r.codes[0] - } else if cLen > 3 { - description += "[" + strings.Join(r.codes[:3], " ") + "...]" - } else { - description += "[" + strings.Join(r.codes, " ") + "]" - } - return description -} diff --git a/route/rule/rule_item_ip_accept_any.go b/route/rule/rule_item_ip_accept_any.go new file mode 100644 index 0000000000..1ca7125735 --- /dev/null +++ b/route/rule/rule_item_ip_accept_any.go @@ -0,0 +1,21 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*IPAcceptAnyItem)(nil) + +type IPAcceptAnyItem struct{} + +func NewIPAcceptAnyItem() *IPAcceptAnyItem { + return &IPAcceptAnyItem{} +} + +func (r *IPAcceptAnyItem) Match(metadata *adapter.InboundContext) bool { + return len(metadata.DestinationAddresses) > 0 +} + +func (r *IPAcceptAnyItem) String() string { + return "ip_accept_any=true" +} diff --git a/route/rule_conds.go b/route/rule_conds.go index 7644717674..55c4a058e2 100644 --- a/route/rule_conds.go +++ b/route/rule_conds.go @@ -3,7 +3,6 @@ package route import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" ) func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool { @@ -38,22 +37,6 @@ func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bo return false } -func isGeoIPRule(rule option.DefaultRule) bool { - return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) -} - -func isGeoIPDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) -} - -func isGeositeRule(rule option.DefaultRule) bool { - return len(rule.Geosite) > 0 -} - -func isGeositeDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.Geosite) > 0 -} - func isProcessRule(rule option.DefaultRule) bool { return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 } @@ -62,10 +45,6 @@ func isProcessDNSRule(rule option.DefaultDNSRule) bool { return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 } -func notPrivateNode(code string) bool { - return code != "private" -} - func isWIFIRule(rule option.DefaultRule) bool { return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 } diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index f39cd18725..605740d4e8 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -6,7 +6,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/json/badoption" @@ -34,7 +33,7 @@ func TestTUICDomainUDP(t *testing.T) { Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, InboundOptions: option.InboundOptions{ - DomainStrategy: option.DomainStrategy(dns.DomainStrategyUseIPv6), + DomainStrategy: option.DomainStrategy(C.DomainStrategyIPv6Only), }, }, Users: []option.TUICUser{{ diff --git a/transport/fakeip/server.go b/transport/fakeip/server.go deleted file mode 100644 index d1bbb2aadd..0000000000 --- a/transport/fakeip/server.go +++ /dev/null @@ -1,95 +0,0 @@ -package fakeip - -import ( - "context" - "net/netip" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-dns" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" - "github.com/sagernet/sing/service" - - mDNS "github.com/miekg/dns" -) - -var ( - _ dns.Transport = (*Transport)(nil) - _ adapter.FakeIPTransport = (*Transport)(nil) -) - -func init() { - dns.RegisterTransport([]string{"fakeip"}, func(options dns.TransportOptions) (dns.Transport, error) { - return NewTransport(options) - }) -} - -type Transport struct { - name string - router adapter.Router - store adapter.FakeIPStore - logger logger.ContextLogger -} - -func NewTransport(options dns.TransportOptions) (*Transport, error) { - router := service.FromContext[adapter.Router](options.Context) - if router == nil { - return nil, E.New("missing router in context") - } - return &Transport{ - name: options.Name, - router: router, - logger: options.Logger, - }, nil -} - -func (s *Transport) Name() string { - return s.name -} - -func (s *Transport) Start() error { - s.store = s.router.FakeIPStore() - if s.store == nil { - return E.New("fakeip not enabled") - } - return nil -} - -func (s *Transport) Reset() { -} - -func (s *Transport) Close() error { - return nil -} - -func (s *Transport) Raw() bool { - return false -} - -func (s *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - return nil, os.ErrInvalid -} - -func (s *Transport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { - var addresses []netip.Addr - if strategy != dns.DomainStrategyUseIPv6 { - inet4Address, err := s.store.Create(domain, false) - if err != nil { - return nil, err - } - addresses = append(addresses, inet4Address) - } - if strategy != dns.DomainStrategyUseIPv4 { - inet6Address, err := s.store.Create(domain, true) - if err != nil { - return nil, err - } - addresses = append(addresses, inet6Address) - } - return addresses, nil -} - -func (s *Transport) Store() adapter.FakeIPStore { - return s.store -} diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index c74e59aa79..828c9f09f5 100644 --- a/transport/v2rayhttp/server.go +++ b/transport/v2rayhttp/server.go @@ -165,7 +165,7 @@ func (s *Server) Serve(listener net.Listener) error { if len(s.tlsConfig.NextProtos()) == 0 { s.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"}) } else if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { - s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) + s.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, s.tlsConfig.NextProtos()...)) } listener = aTLS.NewListener(listener, s.tlsConfig) } From 988ac62a1bbd5c63296a28c472fcfccc635f618c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 12 Jan 2025 12:45:27 +0800 Subject: [PATCH 0358/1272] refactor: Outbound domain resolver --- adapter/network.go | 14 +-- box.go | 6 +- common/dialer/default.go | 7 -- common/dialer/detour.go | 16 ++-- common/dialer/dialer.go | 128 ++++++++++++++++++--------- common/dialer/resolve.go | 113 ++++++++++++++++++----- common/tls/reality_server.go | 2 +- dns/transport_adapter.go | 12 ++- dns/transport_dialer.go | 50 ++++++----- experimental/deprecated/constants.go | 26 +++++- option/dns.go | 50 ++++++----- option/outbound.go | 40 ++++++++- option/route.go | 1 + protocol/direct/outbound.go | 12 ++- protocol/http/outbound.go | 2 +- protocol/hysteria/outbound.go | 2 +- protocol/hysteria2/outbound.go | 2 +- protocol/shadowsocks/outbound.go | 2 +- protocol/shadowtls/inbound.go | 4 +- protocol/shadowtls/outbound.go | 2 +- protocol/socks/outbound.go | 2 +- protocol/ssh/outbound.go | 2 +- protocol/tor/outbound.go | 2 +- protocol/trojan/outbound.go | 2 +- protocol/tuic/outbound.go | 2 +- protocol/vless/outbound.go | 2 +- protocol/vmess/outbound.go | 2 +- protocol/wireguard/endpoint.go | 13 ++- protocol/wireguard/outbound.go | 13 ++- route/network.go | 13 ++- route/rule/rule_action.go | 2 +- route/rule/rule_dns.go | 2 +- route/rule/rule_item_outbound.go | 9 +- 33 files changed, 391 insertions(+), 166 deletions(-) diff --git a/adapter/network.go b/adapter/network.go index 3adfaaeb9d..506708314d 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -29,12 +29,14 @@ type NetworkManager interface { } type NetworkOptions struct { - NetworkStrategy *C.NetworkStrategy - NetworkType []C.InterfaceType - FallbackNetworkType []C.InterfaceType - FallbackDelay time.Duration - BindInterface string - RoutingMark uint32 + BindInterface string + RoutingMark uint32 + DomainResolver string + DomainResolveOptions DNSQueryOptions + NetworkStrategy *C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType + FallbackDelay time.Duration } type InterfaceUpdateListener interface { diff --git a/box.go b/box.go index e810b77148..3050f37cd3 100644 --- a/box.go +++ b/box.go @@ -187,7 +187,7 @@ func New(options Options) (*Box, error) { transportOptions.Options, ) if err != nil { - return nil, E.Cause(err, "initialize inbound[", i, "]") + return nil, E.Cause(err, "initialize DNS server[", i, "]") } } err = dnsRouter.Initialize(dnsOptions.Rules) @@ -217,7 +217,7 @@ func New(options Options) (*Box, error) { endpointOptions.Options, ) if err != nil { - return nil, E.Cause(err, "initialize inbound[", i, "]") + return nil, E.Cause(err, "initialize endpoint[", i, "]") } } for i, inboundOptions := range options.Inbounds { @@ -316,7 +316,7 @@ func New(options Options) (*Box, error) { } } if ntpOptions.Enabled { - ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) + ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain()) if err != nil { return nil, E.Cause(err, "create NTP service") } diff --git a/common/dialer/default.go b/common/dialer/default.go index ff524d083e..3b5e3dbf62 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -35,7 +35,6 @@ type DefaultDialer struct { udpListener net.ListenConfig udpAddr4 string udpAddr6 string - isWireGuardListener bool networkManager adapter.NetworkManager networkStrategy *C.NetworkStrategy defaultNetworkStrategy bool @@ -183,11 +182,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial } setMultiPathTCP(&dialer4) } - if options.IsWireGuardListener { - for _, controlFn := range WgControlFns { - listener.Control = control.Append(listener.Control, controlFn) - } - } tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen) if err != nil { return nil, err @@ -204,7 +198,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial udpListener: listener, udpAddr4: udpAddr4, udpAddr6: udpAddr6, - isWireGuardListener: options.IsWireGuardListener, networkManager: networkManager, networkStrategy: networkStrategy, defaultNetworkStrategy: defaultNetworkStrategy, diff --git a/common/dialer/detour.go b/common/dialer/detour.go index c1d40faa1d..e4a46049f0 100644 --- a/common/dialer/detour.go +++ b/common/dialer/detour.go @@ -29,16 +29,18 @@ func (d *DetourDialer) Start() error { } func (d *DetourDialer) Dialer() (N.Dialer, error) { - d.initOnce.Do(func() { - var loaded bool - d.dialer, loaded = d.outboundManager.Outbound(d.detour) - if !loaded { - d.initErr = E.New("outbound detour not found: ", d.detour) - } - }) + d.initOnce.Do(d.init) return d.dialer, d.initErr } +func (d *DetourDialer) init() { + var loaded bool + d.dialer, loaded = d.outboundManager.Outbound(d.detour) + if !loaded { + d.initErr = E.New("outbound detour not found: ", d.detour) + } +} + func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { dialer, err := d.Dialer() if err != nil { diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index f63e3864cb..812e557557 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -8,6 +8,7 @@ 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/option" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -15,60 +16,105 @@ import ( "github.com/sagernet/sing/service" ) -func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { - if options.IsWireGuardListener { - return NewDefault(ctx, options) - } +type Options struct { + Context context.Context + Options option.DialerOptions + RemoteIsDomain bool + DirectResolver bool + ResolverOnDetour bool +} + +// TODO: merge with NewWithOptions +func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) { + return NewWithOptions(Options{ + Context: ctx, + Options: options, + RemoteIsDomain: remoteIsDomain, + }) +} + +func NewWithOptions(options Options) (N.Dialer, error) { + dialOptions := options.Options var ( dialer N.Dialer err error ) - if options.Detour == "" { - dialer, err = NewDefault(ctx, options) - if err != nil { - return nil, err - } - } else { - outboundManager := service.FromContext[adapter.OutboundManager](ctx) + if dialOptions.Detour != "" { + outboundManager := service.FromContext[adapter.OutboundManager](options.Context) if outboundManager == nil { return nil, E.New("missing outbound manager") } - dialer = NewDetour(outboundManager, options.Detour) + dialer = NewDetour(outboundManager, dialOptions.Detour) + } else { + dialer, err = NewDefault(options.Context, dialOptions) + if err != nil { + return nil, err + } } - if options.Detour == "" { - router := service.FromContext[adapter.DNSRouter](ctx) - if router != nil { - dialer = NewResolveDialer( - router, - dialer, - options.Detour == "" && !options.TCPFastOpen, - C.DomainStrategy(options.DomainStrategy), - time.Duration(options.FallbackDelay)) + if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour) { + networkManager := service.FromContext[adapter.NetworkManager](options.Context) + dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context) + var defaultOptions adapter.NetworkOptions + if networkManager != nil { + defaultOptions = networkManager.DefaultOptions() } + var ( + server string + dnsQueryOptions adapter.DNSQueryOptions + resolveFallbackDelay time.Duration + ) + if dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "" { + var transport adapter.DNSTransport + if !options.DirectResolver { + var loaded bool + transport, loaded = dnsTransport.Transport(dialOptions.DomainResolver.Server) + if !loaded { + return nil, E.New("domain resolver not found: " + dialOptions.DomainResolver.Server) + } + } + var strategy C.DomainStrategy + if dialOptions.DomainResolver.Strategy != option.DomainStrategy(C.DomainStrategyAsIS) { + strategy = C.DomainStrategy(dialOptions.DomainResolver.Strategy) + } else if + //nolint:staticcheck + dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) { + //nolint:staticcheck + strategy = C.DomainStrategy(dialOptions.DomainStrategy) + } + server = dialOptions.DomainResolver.Server + dnsQueryOptions = adapter.DNSQueryOptions{ + Transport: transport, + Strategy: strategy, + DisableCache: dialOptions.DomainResolver.DisableCache, + RewriteTTL: dialOptions.DomainResolver.RewriteTTL, + ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}), + } + resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) + } else if options.DirectResolver { + return nil, E.New("missing domain resolver for domain server address") + } else if defaultOptions.DomainResolver != "" { + dnsQueryOptions = defaultOptions.DomainResolveOptions + transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver) + if !loaded { + return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver) + } + dnsQueryOptions.Transport = transport + resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) + } else { + deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) + } + dialer = NewResolveDialer( + options.Context, + dialer, + dialOptions.Detour == "" && !dialOptions.TCPFastOpen, + server, + dnsQueryOptions, + resolveFallbackDelay, + ) } return dialer, nil } -func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInterfaceDialer, error) { - if options.Detour != "" { - return nil, E.New("`detour` is not supported in direct context") - } - if options.IsWireGuardListener { - return NewDefault(ctx, options) - } - dialer, err := NewDefault(ctx, options) - if err != nil { - return nil, err - } - return NewResolveParallelInterfaceDialer( - service.FromContext[adapter.DNSRouter](ctx), - dialer, - true, - C.DomainStrategy(options.DomainStrategy), - time.Duration(options.FallbackDelay), - ), nil -} - type ParallelInterfaceDialer interface { N.Dialer DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index 3d667a6c83..66b74e3c55 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -3,14 +3,17 @@ package dialer import ( "context" "net" + "sync" "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) var ( @@ -18,21 +21,37 @@ var ( _ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil) ) +type ResolveDialer interface { + N.Dialer + QueryOptions() adapter.DNSQueryOptions +} + +type ParallelInterfaceResolveDialer interface { + ParallelInterfaceDialer + QueryOptions() adapter.DNSQueryOptions +} + type resolveDialer struct { + transport adapter.DNSTransportManager + router adapter.DNSRouter dialer N.Dialer parallel bool - router adapter.DNSRouter - strategy C.DomainStrategy + server string + initOnce sync.Once + initErr error + queryOptions adapter.DNSQueryOptions fallbackDelay time.Duration } -func NewResolveDialer(router adapter.DNSRouter, dialer N.Dialer, parallel bool, strategy C.DomainStrategy, fallbackDelay time.Duration) N.Dialer { +func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer { return &resolveDialer{ - dialer, - parallel, - router, - strategy, - fallbackDelay, + transport: service.FromContext[adapter.DNSTransportManager](ctx), + router: service.FromContext[adapter.DNSRouter](ctx), + dialer: dialer, + parallel: parallel, + server: server, + queryOptions: queryOptions, + fallbackDelay: fallbackDelay, } } @@ -41,41 +60,68 @@ type resolveParallelNetworkDialer struct { dialer ParallelInterfaceDialer } -func NewResolveParallelInterfaceDialer(router adapter.DNSRouter, dialer ParallelInterfaceDialer, parallel bool, strategy C.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer { +func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer { return &resolveParallelNetworkDialer{ resolveDialer{ - dialer, - parallel, - router, - strategy, - fallbackDelay, + transport: service.FromContext[adapter.DNSTransportManager](ctx), + router: service.FromContext[adapter.DNSRouter](ctx), + dialer: dialer, + parallel: parallel, + server: server, + queryOptions: queryOptions, + fallbackDelay: fallbackDelay, }, dialer, } } +func (d *resolveDialer) initialize() error { + d.initOnce.Do(d.initServer) + return d.initErr +} + +func (d *resolveDialer) initServer() { + if d.server == "" { + return + } + transport, loaded := d.transport.Transport(d.server) + if !loaded { + d.initErr = E.New("domain resolver not found: " + d.server) + return + } + d.queryOptions.Transport = transport +} + func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + err := d.initialize() + if err != nil { + return nil, err + } if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Strategy: d.strategy}) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) if err != nil { return nil, err } if d.parallel { - return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay) + return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay) } else { return N.DialSerial(ctx, d.dialer, network, destination, addresses) } } func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + err := d.initialize() + if err != nil { + return nil, err + } if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Strategy: d.strategy}) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) if err != nil { return nil, err } @@ -86,14 +132,24 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } +func (d *resolveDialer) QueryOptions() adapter.DNSQueryOptions { + return d.queryOptions +} + +func (d *resolveDialer) Upstream() any { + return d.dialer +} + func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + err := d.initialize() + if err != nil { + return nil, err + } if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{ - Strategy: d.strategy, - }) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) if err != nil { return nil, err } @@ -101,21 +157,28 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context fallbackDelay = d.fallbackDelay } if d.parallel { - return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } else { return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } } func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { + err := d.initialize() + if err != nil { + return nil, err + } if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Strategy: d.strategy}) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) if err != nil { return nil, err } + if fallbackDelay == 0 { + fallbackDelay = d.fallbackDelay + } conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) if err != nil { return nil, err @@ -123,6 +186,10 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } -func (d *resolveDialer) Upstream() any { +func (d *resolveParallelNetworkDialer) QueryOptions() adapter.DNSQueryOptions { + return d.queryOptions +} + +func (d *resolveParallelNetworkDialer) Upstream() any { return d.dialer } diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index 48a17e63bf..84b0979da5 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -105,7 +105,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb } } - handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain()) if err != nil { return nil, err } diff --git a/dns/transport_adapter.go b/dns/transport_adapter.go index 02c84621f9..4734570978 100644 --- a/dns/transport_adapter.go +++ b/dns/transport_adapter.go @@ -27,9 +27,14 @@ func NewTransportAdapter(transportType string, transportTag string, dependencies } func NewTransportAdapterWithLocalOptions(transportType string, transportTag string, localOptions option.LocalDNSServerOptions) TransportAdapter { + var dependencies []string + if localOptions.DomainResolver != nil && localOptions.DomainResolver.Server != "" { + dependencies = append(dependencies, localOptions.DomainResolver.Server) + } return TransportAdapter{ transportType: transportType, transportTag: transportTag, + dependencies: dependencies, strategy: C.DomainStrategy(localOptions.LegacyStrategy), clientSubnet: localOptions.LegacyClientSubnet, } @@ -37,8 +42,11 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter { var dependencies []string - if remoteOptions.AddressResolver != "" { - dependencies = []string{remoteOptions.AddressResolver} + if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" { + dependencies = append(dependencies, remoteOptions.DomainResolver.Server) + } + if remoteOptions.LegacyAddressResolver != "" { + dependencies = append(dependencies, remoteOptions.LegacyAddressResolver) } return TransportAdapter{ transportType: transportType, diff --git a/dns/transport_dialer.go b/dns/transport_dialer.go index 14e1188dab..0b15c7eaab 100644 --- a/dns/transport_dialer.go +++ b/dns/transport_dialer.go @@ -19,29 +19,39 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) ( if options.LegacyDefaultDialer { return dialer.NewDefaultOutbound(ctx), nil } else { - return dialer.New(ctx, options.DialerOptions) + return dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + DirectResolver: true, + }) } } func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) { - transportDialer, err := NewLocalDialer(ctx, options.LocalDNSServerOptions) - if err != nil { - return nil, err - } - if options.AddressResolver != "" { - transport := service.FromContext[adapter.DNSTransportManager](ctx) - resolverTransport, loaded := transport.Transport(options.AddressResolver) - if !loaded { - return nil, E.New("address resolver not found: ", options.AddressResolver) + if options.LegacyDefaultDialer { + transportDialer := dialer.NewDefaultOutbound(ctx) + if options.LegacyAddressResolver != "" { + transport := service.FromContext[adapter.DNSTransportManager](ctx) + resolverTransport, loaded := transport.Transport(options.LegacyAddressResolver) + if !loaded { + return nil, E.New("address resolver not found: ", options.LegacyAddressResolver) + } + transportDialer = newTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.LegacyAddressStrategy), time.Duration(options.LegacyAddressFallbackDelay)) + } else if options.ServerIsDomain() { + return nil, E.New("missing address resolver for server: ", options.Server) } - transportDialer = NewTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.AddressStrategy), time.Duration(options.AddressFallbackDelay)) - } else if M.IsDomainName(options.Server) { - return nil, E.New("missing address resolver for server: ", options.Server) + return transportDialer, nil + } else { + return dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain(), + DirectResolver: true, + }) } - return transportDialer, nil } -type TransportDialer struct { +type legacyTransportDialer struct { dialer N.Dialer dnsRouter adapter.DNSRouter transport adapter.DNSTransport @@ -49,8 +59,8 @@ type TransportDialer struct { fallbackDelay time.Duration } -func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *TransportDialer { - return &TransportDialer{ +func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer { + return &legacyTransportDialer{ dialer, dnsRouter, transport, @@ -59,7 +69,7 @@ func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport } } -func (d *TransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if destination.IsIP() { return d.dialer.DialContext(ctx, network, destination) } @@ -73,7 +83,7 @@ func (d *TransportDialer) DialContext(ctx context.Context, network string, desti return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay) } -func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if destination.IsIP() { return d.dialer.ListenPacket(ctx, destination) } @@ -88,6 +98,6 @@ func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksa return conn, err } -func (d *TransportDialer) Upstream() any { +func (d *legacyTransportDialer) Upstream() any { return d.dialer } diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index bf64836541..d5c3ca4831 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -148,10 +148,11 @@ var OptionTUNGSO = Note{ var OptionLegacyDNSTransport = Note{ Name: "legacy-dns-transport", - Description: "legacy DNS transport", + Description: "legacy DNS servers", DeprecatedVersion: "1.12.0", ScheduledVersion: "1.14.0", - EnvName: "LEGACY_DNS_TRANSPORT", + EnvName: "LEGACY_DNS_SERVERS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats", } var OptionLegacyDNSFakeIPOptions = Note{ @@ -159,6 +160,23 @@ var OptionLegacyDNSFakeIPOptions = Note{ Description: "legacy DNS fakeip options", DeprecatedVersion: "1.12.0", ScheduledVersion: "1.14.0", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats", +} + +var OptionOutboundDNSRuleItem = Note{ + Name: "outbound-dns-rule-item", + Description: "outbound DNS rule item", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", +} + +var OptionMissingDomainResolver = Note{ + Name: "missing-domain-resolver", + Description: "missing `route.default_domain_resolver` or `domain_resolver` in dial fields", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", } var Options = []Note{ @@ -172,4 +190,8 @@ var Options = []Note{ OptionWireGuardOutbound, OptionWireGuardGSO, OptionTUNGSO, + OptionLegacyDNSTransport, + OptionLegacyDNSFakeIPOptions, + OptionOutboundDNSRuleItem, + OptionMissingDomainResolver, } diff --git a/option/dns.go b/option/dns.go index 8c9b8bda6e..f441846870 100644 --- a/option/dns.go +++ b/option/dns.go @@ -4,7 +4,6 @@ import ( "context" "net/netip" "net/url" - "os" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/deprecated" @@ -121,11 +120,6 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { if o.Type != C.DNSTypeLegacy { return nil } - defer func() { - encoder := json.NewEncoder(os.Stderr) - encoder.SetIndent("", " ") - encoder.Encode(o) - }() options := o.Options.(*LegacyDNSServerOptions) serverURL, _ := url.Parse(options.Address) var serverType string @@ -139,18 +133,34 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { serverType = C.DNSTypeUDP } } - remoteOptions := RemoteDNSServerOptions{ - LocalDNSServerOptions: LocalDNSServerOptions{ - DialerOptions: DialerOptions{ - Detour: options.Detour, + var remoteOptions RemoteDNSServerOptions + if options.Detour == "" { + remoteOptions = RemoteDNSServerOptions{ + LocalDNSServerOptions: LocalDNSServerOptions{ + LegacyStrategy: options.Strategy, + LegacyDefaultDialer: options.Detour == "", + LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), }, - LegacyStrategy: options.Strategy, - LegacyDefaultDialer: options.Detour == "", - LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), - }, - AddressResolver: options.AddressResolver, - AddressStrategy: options.AddressStrategy, - AddressFallbackDelay: options.AddressFallbackDelay, + LegacyAddressResolver: options.AddressResolver, + LegacyAddressStrategy: options.AddressStrategy, + LegacyAddressFallbackDelay: options.AddressFallbackDelay, + } + } else { + remoteOptions = RemoteDNSServerOptions{ + LocalDNSServerOptions: LocalDNSServerOptions{ + DialerOptions: DialerOptions{ + Detour: options.Detour, + DomainResolver: &DomainResolveOptions{ + Server: options.AddressResolver, + Strategy: options.AddressStrategy, + }, + FallbackDelay: options.AddressFallbackDelay, + }, + LegacyStrategy: options.Strategy, + LegacyDefaultDialer: options.Detour == "", + LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), + }, + } } switch serverType { case C.DNSTypeLocal: @@ -291,9 +301,9 @@ type LocalDNSServerOptions struct { type RemoteDNSServerOptions struct { LocalDNSServerOptions ServerOptions - AddressResolver string `json:"address_resolver,omitempty"` - AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` - AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"` + LegacyAddressResolver string `json:"-"` + LegacyAddressStrategy DomainStrategy `json:"-"` + LegacyAddressFallbackDelay badoption.Duration `json:"-"` } type RemoteTLSDNSServerOptions struct { diff --git a/option/outbound.go b/option/outbound.go index dc7a6f526b..a523936f5b 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -77,12 +77,46 @@ type DialerOptions struct { TCPMultiPath bool `json:"tcp_multi_path,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + 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"` IsWireGuardListener bool `json:"-"` + + // Deprecated: migrated to domain resolver + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` +} + +type _DomainResolveOptions struct { + Server string `json:"server"` + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` +} + +type DomainResolveOptions _DomainResolveOptions + +func (o DomainResolveOptions) MarshalJSON() ([]byte, error) { + if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) && + !o.DisableCache && + o.RewriteTTL == nil && + o.ClientSubnet == nil { + return json.Marshal(o.Server) + } else { + return json.Marshal((_DomainResolveOptions)(o)) + } +} + +func (o *DomainResolveOptions) UnmarshalJSON(bytes []byte) error { + var stringValue string + err := json.Unmarshal(bytes, &stringValue) + if err == nil { + o.Server = stringValue + return nil + } + return json.Unmarshal(bytes, (*_DomainResolveOptions)(o)) } func (o *DialerOptions) TakeDialerOptions() DialerOptions { @@ -107,6 +141,10 @@ func (o ServerOptions) Build() M.Socksaddr { return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) } +func (o ServerOptions) ServerIsDomain() bool { + return M.IsDomainName(o.Server) +} + func (o *ServerOptions) TakeServerOptions() ServerOptions { return *o } diff --git a/option/route.go b/option/route.go index 1eb2294bfe..f4b6539156 100644 --- a/option/route.go +++ b/option/route.go @@ -13,6 +13,7 @@ type RouteOptions struct { OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` DefaultInterface string `json:"default_interface,omitempty"` DefaultMark FwMark `json:"default_mark,omitempty"` + DefaultDomainResolver *DomainResolveOptions `json:"default_domain_resolver,omitempty"` DefaultNetworkStrategy *NetworkStrategy `json:"default_network_strategy,omitempty"` DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"` DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"` diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index d173ec532f..9b454f58e7 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -42,16 +42,20 @@ type Outbound struct { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true - outboundDialer, err := dialer.NewDirect(ctx, options.DialerOptions) + if options.Detour != "" { + return nil, E.New("`detour` is not supported in direct context") + } + outboundDialer, err := dialer.New(ctx, options.DialerOptions, false) if err != nil { return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), - logger: logger, + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + logger: logger, + //nolint:staticcheck domainStrategy: C.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), - dialer: outboundDialer, + dialer: outboundDialer.(dialer.ParallelInterfaceDialer), // loopBack: newLoopBackDetector(router), } //nolint:staticcheck diff --git a/protocol/http/outbound.go b/protocol/http/outbound.go index c58f307138..0570dde5a9 100644 --- a/protocol/http/outbound.go +++ b/protocol/http/outbound.go @@ -30,7 +30,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/hysteria/outbound.go b/protocol/hysteria/outbound.go index e59b17a14b..7e20b005f8 100644 --- a/protocol/hysteria/outbound.go +++ b/protocol/hysteria/outbound.go @@ -46,7 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go index 74e87b37cf..c805f07e39 100644 --- a/protocol/hysteria2/outbound.go +++ b/protocol/hysteria2/outbound.go @@ -60,7 +60,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, E.New("unknown obfs type: ", options.Obfs.Type) } } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/shadowsocks/outbound.go b/protocol/shadowsocks/outbound.go index 7e7277ef95..875c9e69a3 100644 --- a/protocol/shadowsocks/outbound.go +++ b/protocol/shadowsocks/outbound.go @@ -44,7 +44,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index 5ae5656f48..1db191d854 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -47,7 +47,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if options.Version > 1 { handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) for serverName, serverOptions := range options.HandshakeForServerName { - handshakeDialer, err := dialer.New(ctx, serverOptions.DialerOptions) + handshakeDialer, err := dialer.New(ctx, serverOptions.DialerOptions, serverOptions.ServerIsDomain()) if err != nil { return nil, err } @@ -57,7 +57,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } } } - handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions, options.Handshake.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/shadowtls/outbound.go b/protocol/shadowtls/outbound.go index 2b480729e5..0731b0337b 100644 --- a/protocol/shadowtls/outbound.go +++ b/protocol/shadowtls/outbound.go @@ -68,7 +68,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig) } } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/socks/outbound.go b/protocol/socks/outbound.go index 323149e256..851412ff0f 100644 --- a/protocol/socks/outbound.go +++ b/protocol/socks/outbound.go @@ -46,7 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/ssh/outbound.go b/protocol/ssh/outbound.go index b329d4b3e6..aeb43d3427 100644 --- a/protocol/ssh/outbound.go +++ b/protocol/ssh/outbound.go @@ -50,7 +50,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/tor/outbound.go b/protocol/tor/outbound.go index 58824b53fb..9a0e2d6506 100644 --- a/protocol/tor/outbound.go +++ b/protocol/tor/outbound.go @@ -75,7 +75,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } startConf.TorrcFile = torrcFile } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, false) if err != nil { return nil, err } diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index 82889bc188..37a6933c8e 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -38,7 +38,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/tuic/outbound.go b/protocol/tuic/outbound.go index 49b01f96e9..a31d48509c 100644 --- a/protocol/tuic/outbound.go +++ b/protocol/tuic/outbound.go @@ -60,7 +60,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL case "quic": tuicUDPStream = true } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index 1d832a654d..e0208be9af 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -41,7 +41,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index d41b30d964..be05990ee1 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -41,7 +41,7 @@ type Outbound struct { } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (adapter.Outbound, error) { - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 1dead5c982..29acbf1258 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -48,7 +48,14 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL if options.Detour == "" { options.IsWireGuardListener = true } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: common.Any(options.Peers, func(it option.WireGuardPeer) bool { + return !M.ParseAddr(it.Address).IsValid() + }), + ResolverOnDetour: true, + }) if err != nil { return nil, err } @@ -76,9 +83,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL PrivateKey: options.PrivateKey, ListenPort: options.ListenPort, ResolvePeer: func(domain string) (netip.Addr, error) { - endpointAddresses, lookupErr := ep.dnsRouter.Lookup(ctx, domain, adapter.DNSQueryOptions{ - Strategy: C.DomainStrategy(options.DomainStrategy), - }) + endpointAddresses, lookupErr := ep.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions()) if lookupErr != nil { return netip.Addr{}, lookupErr } diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 47e1dac595..1e7a32c70b 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -51,7 +51,14 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } else if options.GSO { return nil, E.New("gso is conflict with detour") } - outboundDialer, err := dialer.New(ctx, options.DialerOptions) + 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 } @@ -89,9 +96,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL Address: options.LocalAddress, PrivateKey: options.PrivateKey, ResolvePeer: func(domain string) (netip.Addr, error) { - endpointAddresses, lookupErr := outbound.dnsRouter.Lookup(ctx, domain, adapter.DNSQueryOptions{ - Strategy: C.DomainStrategy(options.DomainStrategy), - }) + endpointAddresses, lookupErr := outbound.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions()) if lookupErr != nil { return netip.Addr{}, lookupErr } diff --git a/route/network.go b/route/network.go index ab1be76c18..a494ce0902 100644 --- a/route/network.go +++ b/route/network.go @@ -4,6 +4,7 @@ import ( "context" "errors" "net" + "net/netip" "os" "runtime" "strings" @@ -55,13 +56,21 @@ type NetworkManager struct { } func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { + defaultDomainResolver := common.PtrValueOrDefault(routeOptions.DefaultDomainResolver) nm := &NetworkManager{ logger: logger, interfaceFinder: control.NewDefaultInterfaceFinder(), autoDetectInterface: routeOptions.AutoDetectInterface, defaultOptions: adapter.NetworkOptions{ - BindInterface: routeOptions.DefaultInterface, - RoutingMark: uint32(routeOptions.DefaultMark), + BindInterface: routeOptions.DefaultInterface, + RoutingMark: uint32(routeOptions.DefaultMark), + DomainResolver: defaultDomainResolver.Server, + DomainResolveOptions: adapter.DNSQueryOptions{ + Strategy: C.DomainStrategy(defaultDomainResolver.Strategy), + DisableCache: defaultDomainResolver.DisableCache, + 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), diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index acb67a5808..21dbffa0b5 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -50,7 +50,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout), }, nil case C.RuleActionTypeDirect: - directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions)) + directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false) if err != nil { return nil, err } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index ccd487334a..30442abf9f 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -213,7 +213,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if len(options.Outbound) > 0 { - item := NewOutboundRule(options.Outbound) + item := NewOutboundRule(ctx, options.Outbound) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_item_outbound.go b/route/rule/rule_item_outbound.go index 3f37dee7fb..a13d059700 100644 --- a/route/rule/rule_item_outbound.go +++ b/route/rule/rule_item_outbound.go @@ -1,9 +1,11 @@ package rule import ( + "context" "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental/deprecated" F "github.com/sagernet/sing/common/format" ) @@ -15,7 +17,8 @@ type OutboundItem struct { matchAny bool } -func NewOutboundRule(outbounds []string) *OutboundItem { +func NewOutboundRule(ctx context.Context, outbounds []string) *OutboundItem { + deprecated.Report(ctx, deprecated.OptionOutboundDNSRuleItem) rule := &OutboundItem{outbounds: outbounds, outboundMap: make(map[string]bool)} for _, outbound := range outbounds { if outbound == "any" { @@ -28,8 +31,8 @@ func NewOutboundRule(outbounds []string) *OutboundItem { } func (r *OutboundItem) Match(metadata *adapter.InboundContext) bool { - if r.matchAny && metadata.Outbound != "" { - return true + if r.matchAny { + return metadata.Outbound != "" } return r.outboundMap[metadata.Outbound] } From 90ec9c8bcb4aeac2061cbc80943f2fb13a62752a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 26 Jan 2025 09:01:00 +0800 Subject: [PATCH 0359/1272] Add TLS fragment support --- adapter/inbound.go | 2 + common/process/searcher.go | 1 + common/process/searcher_windows.go | 191 ++--------------------------- common/tlsfragment/conn.go | 107 ++++++++++++++++ common/tlsfragment/index.go | 131 ++++++++++++++++++++ common/tlsfragment/wait_darwin.go | 93 ++++++++++++++ common/tlsfragment/wait_linux.go | 40 ++++++ common/tlsfragment/wait_stub.go | 14 +++ common/tlsfragment/wait_windows.go | 28 +++++ constant/timeout.go | 1 + go.mod | 2 +- go.sum | 4 +- option/rule_action.go | 3 + route/conn.go | 16 +++ route/route.go | 4 + route/rule/rule_action.go | 12 ++ transport/simple-obfs/http.go | 4 + transport/simple-obfs/tls.go | 4 + 18 files changed, 476 insertions(+), 181 deletions(-) create mode 100644 common/tlsfragment/conn.go create mode 100644 common/tlsfragment/index.go create mode 100644 common/tlsfragment/wait_darwin.go create mode 100644 common/tlsfragment/wait_linux.go create mode 100644 common/tlsfragment/wait_stub.go create mode 100644 common/tlsfragment/wait_windows.go diff --git a/adapter/inbound.go b/adapter/inbound.go index e1f2d8c9a3..f45ddb24c1 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -72,6 +72,8 @@ type InboundContext struct { UDPDisableDomainUnmapping bool UDPConnect bool UDPTimeout time.Duration + TLSFragment bool + TLSFragmentFallbackDelay time.Duration NetworkStrategy *C.NetworkStrategy NetworkType []C.InterfaceType diff --git a/common/process/searcher.go b/common/process/searcher.go index cee81068ca..d525b3c156 100644 --- a/common/process/searcher.go +++ b/common/process/searcher.go @@ -23,6 +23,7 @@ type Config struct { } type Info struct { + ProcessID uint32 ProcessPath string PackageName string User string diff --git a/common/process/searcher_windows.go b/common/process/searcher_windows.go index 6ea7c709bc..b7d89dda66 100644 --- a/common/process/searcher_windows.go +++ b/common/process/searcher_windows.go @@ -2,14 +2,11 @@ package process import ( "context" - "fmt" "net/netip" - "os" "syscall" - "unsafe" E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/winiphlpapi" "golang.org/x/sys/windows" ) @@ -26,201 +23,39 @@ func NewSearcher(_ Config) (Searcher, error) { return &windowsSearcher{}, nil } -var ( - modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") - procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable") - procGetExtendedUdpTable = modiphlpapi.NewProc("GetExtendedUdpTable") - modkernel32 = windows.NewLazySystemDLL("kernel32.dll") - procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW") -) - func initWin32API() error { - err := modiphlpapi.Load() - if err != nil { - return E.Cause(err, "load iphlpapi.dll") - } - - err = procGetExtendedTcpTable.Find() - if err != nil { - return E.Cause(err, "load iphlpapi::GetExtendedTcpTable") - } - - err = procGetExtendedUdpTable.Find() - if err != nil { - return E.Cause(err, "load iphlpapi::GetExtendedUdpTable") - } - - err = modkernel32.Load() - if err != nil { - return E.Cause(err, "load kernel32.dll") - } - - err = procQueryFullProcessImageNameW.Find() - if err != nil { - return E.Cause(err, "load kernel32::QueryFullProcessImageNameW") - } - - return nil + return winiphlpapi.LoadExtendedTable() } func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { - processName, err := findProcessName(network, source.Addr(), int(source.Port())) + pid, err := winiphlpapi.FindPid(network, source) if err != nil { return nil, err } - return &Info{ProcessPath: processName, UserId: -1}, nil -} - -func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { - family := windows.AF_INET - if ip.Is6() { - family = windows.AF_INET6 - } - - const ( - tcpTablePidConn = 4 - udpTablePid = 1 - ) - - var class int - var fn uintptr - switch network { - case N.NetworkTCP: - fn = procGetExtendedTcpTable.Addr() - class = tcpTablePidConn - case N.NetworkUDP: - fn = procGetExtendedUdpTable.Addr() - class = udpTablePid - default: - return "", os.ErrInvalid - } - - buf, err := getTransportTable(fn, family, class) + path, err := getProcessPath(pid) if err != nil { - return "", err + return &Info{ProcessID: pid, UserId: -1}, err } - - s := newSearcher(family == windows.AF_INET, network == N.NetworkTCP) - - pid, err := s.Search(buf, ip, uint16(srcPort)) - if err != nil { - return "", err - } - return getExecPathFromPID(pid) + return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil } -type searcher struct { - itemSize int - port int - ip int - ipSize int - pid int - tcpState int -} - -func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) { - n := int(readNativeUint32(b[:4])) - itemSize := s.itemSize - for i := 0; i < n; i++ { - row := b[4+itemSize*i : 4+itemSize*(i+1)] - - // according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian. - // this field can be illustrated as follows depends on different machine endianess: - // little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB) - // big endian: [ 0 0 MSB LSB ] interpret as native uint32 is ((MSB<<8)|LSB) - // so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32 - srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4]))) - if srcPort != port { - continue - } - - srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize]) - // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto - if ip != srcIP && (!srcIP.IsUnspecified()) { - continue - } - - pid := readNativeUint32(row[s.pid : s.pid+4]) - return pid, nil - } - return 0, ErrNotFound -} - -func newSearcher(isV4, isTCP bool) *searcher { - var itemSize, port, ip, ipSize, pid int - tcpState := -1 - switch { - case isV4 && isTCP: - // struct MIB_TCPROW_OWNER_PID - itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0 - case isV4 && !isTCP: - // struct MIB_UDPROW_OWNER_PID - itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8 - case !isV4 && isTCP: - // struct MIB_TCP6ROW_OWNER_PID - itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48 - case !isV4 && !isTCP: - // struct MIB_UDP6ROW_OWNER_PID - itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24 - } - - return &searcher{ - itemSize: itemSize, - port: port, - ip: ip, - ipSize: ipSize, - pid: pid, - tcpState: tcpState, - } -} - -func getTransportTable(fn uintptr, family int, class int) ([]byte, error) { - for size, buf := uint32(8), make([]byte, 8); ; { - ptr := unsafe.Pointer(&buf[0]) - err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0) - - switch err { - case 0: - return buf, nil - case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER): - buf = make([]byte, size) - default: - return nil, fmt.Errorf("syscall error: %d", err) - } - } -} - -func readNativeUint32(b []byte) uint32 { - return *(*uint32)(unsafe.Pointer(&b[0])) -} - -func getExecPathFromPID(pid uint32) (string, error) { - // kernel process starts with a colon in order to distinguish with normal processes +func getProcessPath(pid uint32) (string, error) { switch pid { case 0: - // reserved pid for system idle process return ":System Idle Process", nil case 4: - // reserved pid for windows kernel image return ":System", nil } - h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) + handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) if err != nil { return "", err } - defer windows.CloseHandle(h) - + defer windows.CloseHandle(handle) + size := uint32(syscall.MAX_LONG_PATH) buf := make([]uint16, syscall.MAX_LONG_PATH) - size := uint32(len(buf)) - r1, _, err := syscall.SyscallN( - procQueryFullProcessImageNameW.Addr(), - uintptr(h), - uintptr(0), - uintptr(unsafe.Pointer(&buf[0])), - uintptr(unsafe.Pointer(&size)), - ) - if r1 == 0 { + err = windows.QueryFullProcessImageName(handle, 0, &buf[0], &size) + if err != nil { return "", err } - return syscall.UTF16ToString(buf[:size]), nil + return windows.UTF16ToString(buf[:size]), nil } diff --git a/common/tlsfragment/conn.go b/common/tlsfragment/conn.go new file mode 100644 index 0000000000..6f2a3dad6a --- /dev/null +++ b/common/tlsfragment/conn.go @@ -0,0 +1,107 @@ +package tf + +import ( + "context" + "math/rand" + "net" + "strings" + "time" + + N "github.com/sagernet/sing/common/network" + + "golang.org/x/net/publicsuffix" +) + +type Conn struct { + net.Conn + tcpConn *net.TCPConn + ctx context.Context + firstPacketWritten bool + fallbackDelay time.Duration +} + +func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) { + tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn) + return &Conn{ + Conn: conn, + tcpConn: tcpConn, + ctx: ctx, + fallbackDelay: fallbackDelay, + }, nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if !c.firstPacketWritten { + defer func() { + c.firstPacketWritten = true + }() + serverName := indexTLSServerName(b) + if serverName != nil { + if c.tcpConn != nil { + err = c.tcpConn.SetNoDelay(true) + if err != nil { + return + } + } + splits := strings.Split(serverName.ServerName, ".") + currentIndex := serverName.Index + if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" { + splits = splits[:len(splits)-strings.Count(serverName.ServerName, ".")] + } + if len(splits) > 1 && splits[0] == "..." { + currentIndex += len(splits[0]) + 1 + splits = splits[1:] + } + var splitIndexes []int + for i, split := range splits { + splitAt := rand.Intn(len(split)) + splitIndexes = append(splitIndexes, currentIndex+splitAt) + currentIndex += len(split) + if i != len(splits)-1 { + currentIndex++ + } + } + for i := 0; i <= len(splitIndexes); i++ { + var payload []byte + if i == 0 { + payload = b[:splitIndexes[i]] + } else if i == len(splitIndexes) { + payload = b[splitIndexes[i-1]:] + } else { + payload = b[splitIndexes[i-1]:splitIndexes[i]] + } + if c.tcpConn != nil && i != len(splitIndexes) { + err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay) + if err != nil { + return + } + } else { + _, err = c.Conn.Write(payload) + if err != nil { + return + } + } + } + if c.tcpConn != nil { + err = c.tcpConn.SetNoDelay(false) + if err != nil { + return + } + } + return len(b), nil + } + } + return c.Conn.Write(b) +} + +func (c *Conn) ReaderReplaceable() bool { + return true +} + +func (c *Conn) WriterReplaceable() bool { + return c.firstPacketWritten +} + +func (c *Conn) Upstream() any { + return c.Conn +} diff --git a/common/tlsfragment/index.go b/common/tlsfragment/index.go new file mode 100644 index 0000000000..59031cecad --- /dev/null +++ b/common/tlsfragment/index.go @@ -0,0 +1,131 @@ +package tf + +import ( + "encoding/binary" +) + +const ( + recordLayerHeaderLen int = 5 + handshakeHeaderLen int = 6 + randomDataLen int = 32 + sessionIDHeaderLen int = 1 + cipherSuiteHeaderLen int = 2 + compressMethodHeaderLen int = 1 + extensionsHeaderLen int = 2 + extensionHeaderLen int = 4 + sniExtensionHeaderLen int = 5 + contentType uint8 = 22 + handshakeType uint8 = 1 + sniExtensionType uint16 = 0 + sniNameDNSHostnameType uint8 = 0 + tlsVersionBitmask uint16 = 0xFFFC + tls13 uint16 = 0x0304 +) + +type myServerName struct { + Index int + Length int + ServerName string +} + +func indexTLSServerName(payload []byte) *myServerName { + if len(payload) < recordLayerHeaderLen || payload[0] != contentType { + return nil + } + segmentLen := binary.BigEndian.Uint16(payload[3:5]) + if len(payload) < recordLayerHeaderLen+int(segmentLen) { + return nil + } + serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)]) + if serverName == nil { + return nil + } + serverName.Length += recordLayerHeaderLen + return serverName +} + +func indexTLSServerNameFromHandshake(hs []byte) *myServerName { + if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen { + return nil + } + if hs[0] != handshakeType { + return nil + } + handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3]) + if len(hs[4:]) != int(handshakeLen) { + return nil + } + tlsVersion := uint16(hs[4])<<8 | uint16(hs[5]) + if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 { + return nil + } + sessionIDLen := hs[38] + if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) { + return nil + } + cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):] + if len(cs) < cipherSuiteHeaderLen { + return nil + } + csLen := uint16(cs[0])<<8 | uint16(cs[1]) + if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen { + return nil + } + compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)]) + if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) { + return nil + } + currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen) + serverName := indexTLSServerNameFromExtensions(cs[currentIndex:]) + if serverName == nil { + return nil + } + serverName.Index += currentIndex + return serverName +} + +func indexTLSServerNameFromExtensions(exs []byte) *myServerName { + if len(exs) == 0 { + return nil + } + if len(exs) < extensionsHeaderLen { + return nil + } + exsLen := uint16(exs[0])<<8 | uint16(exs[1]) + exs = exs[extensionsHeaderLen:] + if len(exs) < int(exsLen) { + return nil + } + for currentIndex := extensionsHeaderLen; len(exs) > 0; { + if len(exs) < extensionHeaderLen { + return nil + } + exType := uint16(exs[0])<<8 | uint16(exs[1]) + exLen := uint16(exs[2])<<8 | uint16(exs[3]) + if len(exs) < extensionHeaderLen+int(exLen) { + return nil + } + sex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)] + + switch exType { + case sniExtensionType: + if len(sex) < sniExtensionHeaderLen { + return nil + } + sniType := sex[2] + if sniType != sniNameDNSHostnameType { + return nil + } + sniLen := uint16(sex[3])<<8 | uint16(sex[4]) + sex = sex[sniExtensionHeaderLen:] + return &myServerName{ + Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen, + Length: int(sniLen), + ServerName: string(sex), + } + } + exs = exs[4+exLen:] + currentIndex += 4 + int(exLen) + } + return nil +} diff --git a/common/tlsfragment/wait_darwin.go b/common/tlsfragment/wait_darwin.go new file mode 100644 index 0000000000..90c65ba2cd --- /dev/null +++ b/common/tlsfragment/wait_darwin.go @@ -0,0 +1,93 @@ +package tf + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing/common/control" + + "golang.org/x/sys/unix" +) + +/* +const tcpMaxNotifyAck = 10 + +type tcpNotifyAckID uint32 + + type tcpNotifyAckComplete struct { + NotifyPending uint32 + NotifyCompleteCount uint32 + NotifyCompleteID [tcpMaxNotifyAck]tcpNotifyAckID + } + +var sizeOfTCPNotifyAckComplete = int(unsafe.Sizeof(tcpNotifyAckComplete{})) + + func getsockoptTCPNotifyAckComplete(fd, level, opt int) (*tcpNotifyAckComplete, error) { + var value tcpNotifyAckComplete + vallen := uint32(sizeOfTCPNotifyAckComplete) + err := getsockopt(fd, level, opt, unsafe.Pointer(&value), &vallen) + return &value, err + } + +//go:linkname getsockopt golang.org/x/sys/unix.getsockopt +func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32) error + + func waitAck(ctx context.Context, conn *net.TCPConn, _ time.Duration) error { + const TCP_NOTIFY_ACKNOWLEDGEMENT = 0x212 + return control.Conn(conn, func(fd uintptr) error { + err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT, 1) + if err != nil { + if errors.Is(err, unix.EINVAL) { + return waitAckFallback(ctx, conn, 0) + } + return err + } + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + var ackComplete *tcpNotifyAckComplete + ackComplete, err = getsockoptTCPNotifyAckComplete(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT) + if err != nil { + return err + } + if ackComplete.NotifyPending == 0 { + return nil + } + time.Sleep(10 * time.Millisecond) + } + }) + } +*/ + +func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + _, err := conn.Write(payload) + if err != nil { + return err + } + return control.Conn(conn, func(fd uintptr) error { + start := time.Now() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + unacked, err := unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_NWRITE) + if err != nil { + return err + } + if unacked == 0 { + if time.Since(start) <= 20*time.Millisecond { + // under transparent proxy + time.Sleep(fallbackDelay) + } + return nil + } + time.Sleep(10 * time.Millisecond) + } + }) +} diff --git a/common/tlsfragment/wait_linux.go b/common/tlsfragment/wait_linux.go new file mode 100644 index 0000000000..517d6ea5ec --- /dev/null +++ b/common/tlsfragment/wait_linux.go @@ -0,0 +1,40 @@ +package tf + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing/common/control" + + "golang.org/x/sys/unix" +) + +func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + _, err := conn.Write(payload) + if err != nil { + return err + } + return control.Conn(conn, func(fd uintptr) error { + start := time.Now() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + tcpInfo, err := unix.GetsockoptTCPInfo(int(fd), unix.IPPROTO_TCP, unix.TCP_INFO) + if err != nil { + return err + } + if tcpInfo.Unacked == 0 { + if time.Since(start) <= 20*time.Millisecond { + // under transparent proxy + time.Sleep(fallbackDelay) + } + return nil + } + time.Sleep(10 * time.Millisecond) + } + }) +} diff --git a/common/tlsfragment/wait_stub.go b/common/tlsfragment/wait_stub.go new file mode 100644 index 0000000000..7e451a0414 --- /dev/null +++ b/common/tlsfragment/wait_stub.go @@ -0,0 +1,14 @@ +//go:build !(linux || darwin || windows) + +package tf + +import ( + "context" + "net" + "time" +) + +func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + time.Sleep(fallbackDelay) + return nil +} diff --git a/common/tlsfragment/wait_windows.go b/common/tlsfragment/wait_windows.go new file mode 100644 index 0000000000..118a204dbf --- /dev/null +++ b/common/tlsfragment/wait_windows.go @@ -0,0 +1,28 @@ +package tf + +import ( + "context" + "errors" + "net" + "time" + + "github.com/sagernet/sing/common/winiphlpapi" + + "golang.org/x/sys/windows" +) + +func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + start := time.Now() + err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload) + if err != nil { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + time.Sleep(fallbackDelay) + return nil + } + return err + } + if time.Since(start) <= 20*time.Millisecond { + time.Sleep(fallbackDelay) + } + return nil +} diff --git a/constant/timeout.go b/constant/timeout.go index 3b5a452bed..eb0fd34c05 100644 --- a/constant/timeout.go +++ b/constant/timeout.go @@ -16,6 +16,7 @@ const ( StopTimeout = 5 * time.Second FatalStopTimeout = 10 * time.Second FakeIPMetadataSaveInterval = 10 * time.Second + TLSFragmentFallbackDelay = 500 * time.Millisecond ) var PortProtocols = map[uint16]string{ diff --git a/go.mod b/go.mod index 8176d73e7b..2bf760a319 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.10 + github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.4 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index a6c9bda6fd..3abed22fd6 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4= -github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo= +github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.4.4 h1:qqOCLnzHbqKkj/wBcXEI3rhSyqoGlqDdv2S6mz2d/JA= diff --git a/option/rule_action.go b/option/rule_action.go index a715d2602b..f07d729872 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -150,6 +150,9 @@ type RawRouteOptionsActionOptions struct { UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` UDPConnect bool `json:"udp_connect,omitempty"` UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` + + TLSFragment bool `json:"tls_fragment,omitempty"` + TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"` } type RouteOptionsActionOptions RawRouteOptionsActionOptions diff --git a/route/conn.go b/route/conn.go index 319e463c35..582562eb8d 100644 --- a/route/conn.go +++ b/route/conn.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tlsfragment" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" @@ -78,6 +79,21 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co m.logger.ErrorContext(ctx, err) return } + if metadata.TLSFragment { + fallbackDelay := metadata.TLSFragmentFallbackDelay + if fallbackDelay == 0 { + fallbackDelay = C.TLSFragmentFallbackDelay + } + var newConn *tf.Conn + newConn, err = tf.NewConn(remoteConn, ctx, fallbackDelay) + if err != nil { + conn.Close() + remoteConn.Close() + m.logger.ErrorContext(ctx, err) + return + } + remoteConn = newConn + } m.access.Lock() element := m.connections.PushBack(conn) m.access.Unlock() diff --git a/route/route.go b/route/route.go index 91aa020abe..e70d57b409 100644 --- a/route/route.go +++ b/route/route.go @@ -446,6 +446,10 @@ match: if routeOptions.UDPTimeout > 0 { metadata.UDPTimeout = routeOptions.UDPTimeout } + if routeOptions.TLSFragment { + metadata.TLSFragment = true + metadata.TLSFragmentFallbackDelay = routeOptions.TLSFragmentFallbackDelay + } } switch action := currentRule.Action().(type) { case *R.RuleActionSniff: diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 21dbffa0b5..2b1c66d013 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -37,6 +37,8 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, UDPConnect: action.RouteOptions.UDPConnect, + TLSFragment: action.RouteOptions.TLSFragment, + TLSFragmentFallbackDelay: time.Duration(action.RouteOptions.TLSFragmentFallbackDelay), }, }, nil case C.RuleActionTypeRouteOptions: @@ -48,6 +50,8 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, UDPConnect: action.RouteOptionsOptions.UDPConnect, UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout), + TLSFragment: action.RouteOptionsOptions.TLSFragment, + TLSFragmentFallbackDelay: time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay), }, nil case C.RuleActionTypeDirect: directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false) @@ -143,6 +147,9 @@ func (r *RuleActionRoute) String() string { if r.UDPConnect { descriptions = append(descriptions, "udp-connect") } + if r.TLSFragment { + descriptions = append(descriptions, "tls-fragment") + } return F.ToString("route(", strings.Join(descriptions, ","), ")") } @@ -156,6 +163,8 @@ type RuleActionRouteOptions struct { UDPDisableDomainUnmapping bool UDPConnect bool UDPTimeout time.Duration + TLSFragment bool + TLSFragmentFallbackDelay time.Duration } func (r *RuleActionRouteOptions) Type() string { @@ -188,6 +197,9 @@ func (r *RuleActionRouteOptions) String() string { if r.UDPConnect { descriptions = append(descriptions, "udp-connect") } + if r.UDPTimeout > 0 { + descriptions = append(descriptions, "udp-timeout") + } return F.ToString("route-options(", strings.Join(descriptions, ","), ")") } diff --git a/transport/simple-obfs/http.go b/transport/simple-obfs/http.go index f77a63a8ef..df38768ec5 100644 --- a/transport/simple-obfs/http.go +++ b/transport/simple-obfs/http.go @@ -82,6 +82,10 @@ func (ho *HTTPObfs) Write(b []byte) (int, error) { return ho.Conn.Write(b) } +func (ho *HTTPObfs) Upstream() any { + return ho.Conn +} + // NewHTTPObfs return a HTTPObfs func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn { return &HTTPObfs{ diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index 51756fdb6f..9656481532 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -113,6 +113,10 @@ func (to *TLSObfs) write(b []byte) (int, error) { return len(b), err } +func (to *TLSObfs) Upstream() any { + return to.Conn +} + // NewTLSObfs return a SimpleObfs func NewTLSObfs(conn net.Conn, server string) net.Conn { return &TLSObfs{ From 17576e9f66794ac97950c3ae4791d0a7684e599e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 8 Jan 2025 10:34:45 +0800 Subject: [PATCH 0360/1272] Add certificate store --- Makefile | 3 + adapter/certificate.go | 21 + adapter/experimental.go | 16 +- adapter/router.go | 10 +- box.go | 16 +- cmd/internal/update_certificates/main.go | 68 + common/certificate/mozilla.go | 4359 +++++++++++++++++++++ common/certificate/store.go | 185 + common/tls/ech_client.go | 1 + common/tls/ech_server.go | 2 +- common/tls/reality_client.go | 14 +- common/tls/std_client.go | 2 + common/tls/std_server.go | 2 +- common/tls/utls_client.go | 2 + common/urltest/urltest.go | 20 +- constant/certificate.go | 7 + experimental/clashapi/api_meta_group.go | 2 +- experimental/clashapi/proxies.go | 8 +- experimental/clashapi/server.go | 6 +- experimental/clashapi/server_resources.go | 6 + experimental/libbox/command_urltest.go | 2 +- experimental/libbox/config.go | 4 + experimental/libbox/platform.go | 1 + experimental/libbox/platform/interface.go | 1 + experimental/libbox/service.go | 6 +- option/certificate.go | 36 + option/options.go | 1 + protocol/group/urltest.go | 9 +- route/router.go | 2 +- route/rule/rule_set_remote.go | 6 + 30 files changed, 4786 insertions(+), 32 deletions(-) create mode 100644 adapter/certificate.go create mode 100644 cmd/internal/update_certificates/main.go create mode 100644 common/certificate/mozilla.go create mode 100644 common/certificate/store.go create mode 100644 constant/certificate.go create mode 100644 option/certificate.go diff --git a/Makefile b/Makefile index 0561fa3c65..868e47bf74 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,9 @@ proto_install: go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +update_certificates: + go run ./cmd/internal/update_certificates + release: go run ./cmd/internal/build goreleaser release --clean --skip publish mkdir dist/release diff --git a/adapter/certificate.go b/adapter/certificate.go new file mode 100644 index 0000000000..0998e1302a --- /dev/null +++ b/adapter/certificate.go @@ -0,0 +1,21 @@ +package adapter + +import ( + "context" + "crypto/x509" + + "github.com/sagernet/sing/service" +) + +type CertificateStore interface { + LifecycleService + Pool() *x509.CertPool +} + +func RootPoolFromContext(ctx context.Context) *x509.CertPool { + store := service.FromContext[CertificateStore](ctx) + if store == nil { + return nil + } + return store.Pool() +} diff --git a/adapter/experimental.go b/adapter/experimental.go index 99d7c9a52d..de01d7be5e 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -6,7 +6,6 @@ import ( "encoding/binary" "time" - "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing/common/varbin" ) @@ -15,7 +14,20 @@ type ClashServer interface { ConnectionTracker Mode() string ModeList() []string - HistoryStorage() *urltest.HistoryStorage + HistoryStorage() URLTestHistoryStorage +} + +type URLTestHistory struct { + Time time.Time `json:"time"` + Delay uint16 `json:"delay"` +} + +type URLTestHistoryStorage interface { + SetHook(hook chan<- struct{}) + LoadURLTestHistory(tag string) *URLTestHistory + DeleteURLTestHistory(tag string) + StoreURLTestHistory(tag string, history *URLTestHistory) + Close() error } type V2RayServer interface { diff --git a/adapter/router.go b/adapter/router.go index 45e4f723f8..0b7c8f4f74 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -2,6 +2,7 @@ package adapter import ( "context" + "crypto/tls" "net" "net/http" "sync" @@ -9,6 +10,7 @@ import ( C "github.com/sagernet/sing-box/constant" 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/x/list" "go4.org/netipx" @@ -66,12 +68,14 @@ type RuleSetMetadata struct { ContainsIPCIDRRule bool } type HTTPStartContext struct { + ctx context.Context access sync.Mutex httpClientCache map[string]*http.Client } -func NewHTTPStartContext() *HTTPStartContext { +func NewHTTPStartContext(ctx context.Context) *HTTPStartContext { return &HTTPStartContext{ + ctx: ctx, httpClientCache: make(map[string]*http.Client), } } @@ -89,6 +93,10 @@ func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Clie DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) }, + TLSClientConfig: &tls.Config{ + Time: ntp.TimeFuncFromContext(c.ctx), + RootCAs: RootPoolFromContext(c.ctx), + }, }, } c.httpClientCache[detour] = httpClient diff --git a/box.go b/box.go index 3050f37cd3..db900a7a46 100644 --- a/box.go +++ b/box.go @@ -12,6 +12,7 @@ import ( "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/certificate" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/tls" @@ -141,6 +142,20 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "create log factory") } + var services []adapter.LifecycleService + certificateOptions := common.PtrValueOrDefault(options.Certificate) + if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem || + len(certificateOptions.Certificate) > 0 || + len(certificateOptions.CertificatePath) > 0 || + len(certificateOptions.CertificateDirectoryPath) > 0 { + certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions) + if err != nil { + return nil, err + } + service.MustRegister[adapter.CertificateStore](ctx, certificateStore) + services = append(services, certificateStore) + } + routeOptions := common.PtrValueOrDefault(options.Route) dnsOptions := common.PtrValueOrDefault(options.DNS) endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry) @@ -287,7 +302,6 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "initialize platform interface") } } - var services []adapter.LifecycleService if needCacheFile { cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile)) service.MustRegister[adapter.CacheFile](ctx, cacheFile) diff --git a/cmd/internal/update_certificates/main.go b/cmd/internal/update_certificates/main.go new file mode 100644 index 0000000000..cf597c867d --- /dev/null +++ b/cmd/internal/update_certificates/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "encoding/csv" + "io" + "net/http" + "os" + "strings" + + "github.com/sagernet/sing-box/log" + + "golang.org/x/exp/slices" +) + +func main() { + err := updateMozillaIncludedRootCAs() + if err != nil { + log.Error(err) + } +} + +func updateMozillaIncludedRootCAs() error { + response, err := http.Get("https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV") + if err != nil { + return err + } + defer response.Body.Close() + reader := csv.NewReader(response.Body) + header, err := reader.Read() + if err != nil { + return err + } + geoIndex := slices.Index(header, "Geographic Focus") + nameIndex := slices.Index(header, "Common Name or Certificate Name") + certIndex := slices.Index(header, "PEM Info") + + generated := strings.Builder{} + generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT. + +package certificate + +import "crypto/x509" + +var mozillaIncluded *x509.CertPool + +func init() { + mozillaIncluded = x509.NewCertPool() +`) + for { + record, err := reader.Read() + if err == io.EOF { + break + } else if err != nil { + return err + } + if record[geoIndex] == "China" { + continue + } + generated.WriteString("\n // ") + generated.WriteString(record[nameIndex]) + generated.WriteString("\n") + generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`") + generated.WriteString(record[certIndex]) + generated.WriteString("`))\n") + } + generated.WriteString("}\n") + return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644) +} diff --git a/common/certificate/mozilla.go b/common/certificate/mozilla.go new file mode 100644 index 0000000000..3bf1d17502 --- /dev/null +++ b/common/certificate/mozilla.go @@ -0,0 +1,4359 @@ +// Code generated by 'make update_certificates'. DO NOT EDIT. + +package certificate + +import "crypto/x509" + +var mozillaIncluded *x509.CertPool + +func init() { + mozillaIncluded = x509.NewCertPool() + + // Actalis Authentication Root CA + mozillaIncluded.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-----'`)) + + // TunTrust Root CA + mozillaIncluded.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-----'`)) + + // Amazon Root CA 1 + mozillaIncluded.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-----'`)) + + // Amazon Root CA 2 + mozillaIncluded.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-----'`)) + + // Amazon Root CA 3 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE-----'`)) + + // Amazon Root CA 4 + mozillaIncluded.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-----'`)) + + // Starfield Services Root Certificate Authority - G2 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE-----'`)) + + // Certum CA + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E +jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo +ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI +ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu +Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg +AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7 +HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA +uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa +TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg +xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q +CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x +O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs +6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE-----'`)) + + // Certum EC-384 CA + mozillaIncluded.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-----'`)) + + // Certum Trusted Network CA + mozillaIncluded.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-----'`)) + + // Certum Trusted Network CA 2 + mozillaIncluded.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-----'`)) + + // Certum Trusted Root CA + mozillaIncluded.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-----'`)) + + // Autoridad de Certificacion Firmaprofesional CIF A62634068 + mozillaIncluded.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-----'`)) + + // FIRMAPROFESIONAL CA ROOT-A WEB + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf +e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C +cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O +BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO +PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw +hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG +XSaQpYXFuXqUPoeovQA= +-----END CERTIFICATE-----'`)) + + // ANF Secure Server Root CA + mozillaIncluded.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-----'`)) + + // Buypass Class 2 Root CA + mozillaIncluded.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-----'`)) + + // Buypass Class 3 Root CA + mozillaIncluded.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-----'`)) + + // Certainly Root E1 + mozillaIncluded.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-----'`)) + + // Certainly Root R1 + mozillaIncluded.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-----'`)) + + // Certigna + mozillaIncluded.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-----'`)) + + // Certigna Root CA + mozillaIncluded.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-----'`)) + + // certSIGN ROOT CA + mozillaIncluded.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-----'`)) + + // certSIGN ROOT CA G2 + mozillaIncluded.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-----'`)) + + // ePKI Root Certification Authority + mozillaIncluded.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-----'`)) + + // HiPKI Root CA - G1 + mozillaIncluded.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-----'`)) + + // CommScope Public Trust ECC Root-01 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMw +TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t +bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNa +Fw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv +cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDEw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLxeP0C +flfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJE +hRGnSjot6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq +hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg +2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uS +Um9poIyNStDuiw7LR47QjRE= +-----END CERTIFICATE-----'`)) + + // CommScope Public Trust ECC Root-02 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMw +TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t +bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRa +Fw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv +cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDIw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/MMDAL +j2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmU +v4RDsNuESgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq +hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/n +ich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AV +mkzw5l4lIhVtwodZ0LKOag== +-----END CERTIFICATE-----'`)) + + // CommScope Public Trust RSA Root-01 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi +Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1 +NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t +U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt +MDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45FtnYSk +YZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslh +suitQDy6uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0al +DrJLpA6lfO741GIDuZNqihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3Oj +WiE260f6GBfZumbCk6SP/F2krfxQapWsvCQz0b2If4b19bJzKo98rwjyGpg/qYFl +P8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/cZip8UlF1y5mO6D1cv547 +KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTifBSeolz7p +UcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/ +kQO9lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JO +Hg9O5j9ZpSPcPYeoKFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkB +Ea801M/XrmLTBQe0MXXgDW1XT2mH+VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6U +CBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm45P3luG0wDQYJ +KoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 +NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQ +nmhUQo8mUuJM3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+ +QgvfKNmwrZggvkN80V4aCRckjXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2v +trV0KnahP/t1MJ+UXjulYPPLXAziDslg+MkfFoom3ecnf+slpoq9uC02EJqxWE2a +aE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/WNyVntHKLr4W96ioD +j8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+o/E4 +Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0w +lREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn +YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVoc +icCMb3SgazNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw +-----END CERTIFICATE-----'`)) + + // CommScope Public Trust RSA Root-02 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi +Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2 +NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t +U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt +MDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3VrCLE +NQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0 +kyI9p+Kx7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1C +rWDaSWqVcN3SAOLMV2MCe5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxz +hkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2WWy09X6GDRl224yW4fKcZgBzqZUPckXk2 +LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rpM9kzXzehxfCrPfp4sOcs +n/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIfhs1w/tku +FT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5 +kQMreyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3 +wNemKfrb3vOTlycEVS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6v +wQcQeKwRoi9C8DfF8rhW3Q5iLc4tVn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs +5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7GxcJXvYXowDQYJ +KoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB +KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3 ++VGXu6TwYofF1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbyme +APnCKfWxkxlSaRosTKCL4BWaMS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3Nyq +pgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xdgSGn2rtO/+YHqP65DSdsu3BaVXoT +6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2OHG1QAk8mGEPej1WF +sQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+NmYWvt +PjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2d +lklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670 +v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17O +rg3bhzjlP1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7 +-----END CERTIFICATE-----'`)) + + // SecureSign Root CA12 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw +NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF +KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt +p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd +J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur +FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J +hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K +h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF +AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld +mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ +mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA +8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV +55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ +yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE-----'`)) + + // SecureSign Root CA14 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw +NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/ +FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg +vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy +6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo +/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J +kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ +0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib +y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac +18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs +0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB +SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL +ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk +86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib +ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT +zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS +DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4 +2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo +FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy +K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6 +dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl +Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB +365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c +JRNItX+S +-----END CERTIFICATE-----'`)) + + // SecureSign Root CA15 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw +UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM +dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy +NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl +cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290 +IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4 +wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR +ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT +9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp +4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 +bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE-----'`)) + + // D-TRUST BR Root CA 1 2020 + mozillaIncluded.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-----'`)) + + // D-TRUST EV Root CA 1 2020 + mozillaIncluded.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-----'`)) + + // D-TRUST Root CA 3 2013 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIEDjCCAvagAwIBAgIDD92sMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxHzAdBgNVBAMMFkQtVFJVU1QgUm9vdCBD +QSAzIDIwMTMwHhcNMTMwOTIwMDgyNTUxWhcNMjgwOTIwMDgyNTUxWjBFMQswCQYD +VQQGEwJERTEVMBMGA1UECgwMRC1UcnVzdCBHbWJIMR8wHQYDVQQDDBZELVRSVVNU +IFJvb3QgQ0EgMyAyMDEzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xHtCkoIf7O1UmI4SwMoJ35NuOpNcG+QQd55OaYhs9uFp8vabomGxvQcgdJhl8Ywm +CM2oNcqANtFjbehEeoLDbF7eu+g20sRoNoyfMr2EIuDcwu4QRjltr5M5rofmw7wJ +ySxrZ1vZm3Z1TAvgu8XXvD558l++0ZBX+a72Zl8xv9Ntj6e6SvMjZbu376Ml1wrq +WLbviPr6ebJSWNXwrIyhUXQplapRO5AyA58ccnSQ3j3tYdLl4/1kR+W5t0qp9x+u +loYErC/jpIF3t1oW/9gPP/a3eMykr/pbPBJbqFKJcu+I89VEgYaVI5973bzZNO98 +lDyqwEHC451QGsDkGSL8swIDAQABo4IBBTCCAQEwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUP5DIfccVb/Mkj6nDL0uiDyGyL+cwDgYDVR0PAQH/BAQDAgEGMIG+ +BgNVHR8EgbYwgbMwdKByoHCGbmxkYXA6Ly9kaXJlY3RvcnkuZC10cnVzdC5uZXQv +Q049RC1UUlVTVCUyMFJvb3QlMjBDQSUyMDMlMjAyMDEzLE89RC1UcnVzdCUyMEdt +YkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MDugOaA3hjVodHRwOi8v +Y3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2FfM18yMDEzLmNybDAN +BgkqhkiG9w0BAQsFAAOCAQEADlkOWOR0SCNEzzQhtZwUGq2aS7eziG1cqRdw8Cqf +jXv5e4X6xznoEAiwNStfzwLS05zICx7uBVSuN5MECX1sj8J0vPgclL4xAUAt8yQg +t4RVLFzI9XRKEBmLo8ftNdYJSNMOwLo5qLBGArDbxohZwr78e7Erz35ih1WWzAFv +m2chlTWL+BD8cRu3SzdppjvW7IvuwbDzJcmPkn2h6sPKRL8mpXSSnON065102ctN +h9j8tGlsi6BDB2B4l+nZk3zCRrybN1Kj7Yo8E6l7U0tJmhEFLAtuVqwfLoJs4Gln +tQ5tLdnkwBXxP/oYcuEVbSdbLTAoK59ImmQrme/ydUlfXA== +-----END CERTIFICATE-----'`)) + + // D-TRUST Root Class 3 CA 2 2009 + mozillaIncluded.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-----'`)) + + // D-TRUST Root Class 3 CA 2 EV 2009 + mozillaIncluded.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-----'`)) + + // D-Trust SBR Root CA 1 2022 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICXjCCAeOgAwIBAgIQUs/kjG2gSvc/gpcMgAmMlTAKBggqhkjOPQQDAzBJMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpELVRy +dXN0IFNCUiBSb290IENBIDEgMjAyMjAeFw0yMjA3MDYxMTMwMDBaFw0zNzA3MDYx +MTI5NTlaMEkxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxIzAh +BgNVBAMTGkQtVHJ1c3QgU0JSIFJvb3QgQ0EgMSAyMDIyMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEWZM59oxJZijXYQzIq38Moy3foqR8kito1S5+HkDLtGhJfxKhq39X +nxkuYy5b/mZxDDMPud5rxIjDse/sOUDjlqvb5XuuH9z5r0aaakYGL8c3ZIsXYv6W +w6LuhOCwlzm8o4GPMIGMMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPEpox4B +Eh09dVZNx1B8xRmqDxi3MA4GA1UdDwEB/wQEAwIBBjBKBgNVHR8EQzBBMD+gPaA7 +hjlodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Nicl9yb290X2Nh +XzFfMjAyMi5jcmwwCgYIKoZIzj0EAwMDaQAwZgIxAJf53q5Lj5i1HkB/Mn1NVEPa +ic3CqpI80YIec8/6TJIg+2MnxfVzPQk996dhhozzagIxAOcvfLj1JYw7OR82q431 +hqIu4Xpk2mc5Av7+Mz/Zc7ZYWzr8sqTZYHh3zHmnpq5VvQ== +-----END CERTIFICATE-----'`)) + + // D-Trust SBR Root CA 2 2022 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFrDCCA5SgAwIBAgIQVNWjlR49lbpyG5rQMSFKujANBgkqhkiG9w0BAQ0FADBJ +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpE +LVRydXN0IFNCUiBSb290IENBIDIgMjAyMjAeFw0yMjA3MDcwNzMwMDBaFw0zNzA3 +MDcwNzI5NTlaMEkxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgx +IzAhBgNVBAMTGkQtVHJ1c3QgU0JSIFJvb3QgQ0EgMiAyMDIyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAryy8jjaM62SvUWrWbjxekTrqmsPKbPuqJ55k +IqlA37koRVrsU2EWKJjCiqR1eFCE3fogSJIHZUE1ZlESdGGdBwaFOTFXeyg/1Zyl +7FrpHEsnn84nBvM39VLYETMWQTof9WN4ZWOGyb/IAQQfbu7i7KwM7oKS4vYaDT85 ++Z1lk634uQXBPfg3gVbDoP4F7OCUFjojFgTapgqThXJtYTuhjUXW43++Fb02hAj2 +C4NrJqqiveCw56rgrmfE04KlDKmk8DN5DVA/8O+QPSS5f9IgbOqX87+c3EfeCWG9 +lHmVWgJ2NWDERyIN93ZjA9PG+4PGXaut7WklKwNbTSUAQeOMhxdSqOAFK0NNFBPK +5z9DIrw3pHXx9r867zIeru5YhpByugSsQEjvXMR4p6mPJ1rLeuxY8sIIWJBtTQOF +eXEVBQ5OPvnfDwX3XxRIViENM5KxrIzlGP6/D+7gBKq9IfJYtlyJCosYCSIaszXG +ZsL1MxWZgOAI+ZYvE4zu2reIxOk3tddq1zqETatwjNNOFFWgohD8ZNpn6PHLM93J +moqPli9Ygdn4mgBDzJD7VXb7huM3ASgMb/TpWU0Vd1FCSsw0uIBDUIHvV6UT26eU +eQ9Lyn4Xfa+jIWTocVVWjwawR+xZD11wWywWQvCGnnXea01ImITiVxi2nIKZZTqL +gHhXDEkCAwEAAaOBjzCBjDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRds4CU +G+WGv2i6FDSk9u5t8t3f5zAOBgNVHQ8BAf8EBAMCAQYwSgYDVR0fBEMwQTA/oD2g +O4Y5aHR0cDovL2NybC5kLXRydXN0Lm5ldC9jcmwvZC10cnVzdF9zYnJfcm9vdF9j +YV8yXzIwMjIuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA0VC5YGFbNSr2X0/V9K9yv +D1HhTbwhS5P0AEQTBxALJRg+SFmW96Hhk5B4Zho9I+siqwGmjgxRM+ZtjDHurKQB +cDlI3sdmLGsNy3Ofh5LpPkcfuO8v7rdWjEiJ8DinFTmy7sA/F6RzAgicvAaKpMK3 +YWH5w9vE0Hp8Yd6xWJH13WVMLwv46z217Yq+dxy6WQISZnHlmCfODj2vUaJF+YL7 +WqWUcPeLhMNMZSWbe+IfMHCzQI467r3052jFnckpR3EOk8i1SE71ZrsHiHFpa3tI +jm/wEcS0yXAUmCC97afqAdpupZsS/j5EMLPw63VSwPTD+ncmpHeCLW/zKB5OlfAw +94n4LKJQW/K+Mn5sVNtyySpa4By2C9hSmlmh47ABJ8WgFlBm3OuubfSbWz2EbVuH +56mJu2644JtTicD/LkAaiUQuGENnOOR8cl/ZoyklQUE9HHcbZKjDVe5jcWZig/R/ +JpmgVDuhEm1wYs7T+bi9IvzUmtS74jgWL7d9OcKwqQPpnM9+GI123F8Ru+tC7FAJ +PlzskDHYGnK6P2kH7pg0wjSk1toT1qmE8gCGwFS6HhGw4rnEB7SR56rmMVZvsUTE +KmK8ybBlnDT8DBpT3yEXu8JtoQrm8bCqRAlQSTh6XXHiMS4ZsN+VQgR9hIjOCiNn +azidFt4G/ihwOKVarvyD7Q== +-----END CERTIFICATE-----'`)) + + // T-TeleSec GlobalRoot Class 2 + mozillaIncluded.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-----'`)) + + // T-TeleSec GlobalRoot Class 3 + mozillaIncluded.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-----'`)) + + // Telekom Security SMIME ECC Root 2021 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICRzCCAc2gAwIBAgIQFSrdFMkY0aRWQIamJa8HXzAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIEVDQyBSb290IDIw +MjEwHhcNMjEwMzE4MTEwODMwWhcNNDYwMzE3MjM1OTU5WjBlMQswCQYDVQQGEwJE +RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMS0wKwYD +VQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIEVDQyBSb290IDIwMjEwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAASwGY+ia7XHzQ8wmTcMw2Bb8fEnIFU9wJKLq1ehb3OD +IcJDEwxeiarHBTV5k2KQ1l0TH9F6oLyeEKdmfEYKsFdsv+ZUOTghbBJccczTWl9t +t6eG37Pf7sLniUGWNfYvSrWjQjBAMB0GA1UdDgQWBBQrywEMY8NTEqWoV6/QnIP7 +vZA6SzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD +AwNoADBlAjEA1rxIkodHA8dwOyW2H65GZ3N0ACdL5KUEogPfXiitbl4DyN1onLa/ +lBBIlS8P/xiLAjABQDOel5dNBfJ0VAzNOf1qawnBJD9hjjiht+jXRBURYv8OYTdH +S0B/Sl+yZ1pzdcI= +-----END CERTIFICATE-----'`)) + + // Telekom Security SMIME RSA Root 2023 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgIQDH5i9XlzO51Djotj7ZGVuDANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIFJTQSBSb290 +IDIwMjMwHhcNMjMwMzI4MTIwOTIyWhcNNDgwMzI3MjM1OTU5WjBlMQswCQYDVQQG +EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMS0w +KwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIFJTQSBSb290IDIwMjMwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDvxQ6LvjLSZ0f/Ckxnsyq/yMPF +keu1xx6R4WaoiItVIIAfUV53l54ZClzHazchfAM2AfSIJdmoLkGq/Ngm4JZAYnmu +V54DOBocsncUPumhctDk4DfRF0btUFx6WMX4K/d1L8+BnlostzqsoFmYBFEM/0nF +UP0e00eFSzNPoje1rwSaJzKdVtU/VWHji2+uUf6X/mkH+mJbJuYUeRWlEziuXze+ +lErWDYAWaaSRsjpJmHWdRhCKXHp/hKXorx7Hq7NaRrWjS/WmIzYARrHbBbYbzp56 +Mlya1XLDnYZNK4TTHrWI2hB4nCLDOyO16xMHvW9T7Jvsm9Nl9QcJ412nmbV+ho7V +Av+3hQnjRxTdlmYYNN4I1d/LGJliCyvsAF1SRNPGlvwyViWRz80ZO5U5PgKHmWO2 +1T40eg8RdYG8fQTKYLQoddcCUd1SAC7H/YnxXPPLpCcSOI+7+4nw5MQ4LL6CoHFh +YpGPSAwvK6mw8csQBOd0vzeQ708qQzWXEsYqcA3eLFVHeWMp9cofagZSHK4tJCKD +Iq/QqjC3Kh//ZSNYZZPIjn1AEDGGeNlVyzww8N5RKgA20idFX9jooSE9fkZWOylF +8R0FCc62QzDcRZAQMEyka4aLPz0vMZFx7ya59r6dsGzfEe5YP0N5hjmA8SYXB5jw +maowLENZFM7t4kAThQIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FJrOrCrsAfplcN6XnfHSAIylo2S7MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw +FoAUms6sKuwB+mVw3ped8dIAjKWjZLswDQYJKoZIhvcNAQEMBQADggIBAONQ/fVA +FiIJljoNqe+B5y4y8KHxSV57iA0Ecte+Z6i6He5Qu3JuetG7DHIwRsjV1wISFplO +Ht9alu6Pkb6uhvgQd6XEbkdhwPIm2U9haAVIdQgVpaF71biziXnm7fHzYQCGey4x +/qNc+Hk9tFuIe+Ajuw2hF/rLaA2Yd3EI4m1DdGvENsWUQaQA1lctmYqLIBIVAjIO +0knsgUjFaidS17JzVVOWPJ5PTLWg0E9X0GcoSGS+xri67GTPyHvFaucq5llXttbU +1sBnXNmeKAlAv/OpNTFlYAPLGWyClQMeXz/hvepJceVbtwtHFhsgiW2UmQx+iGwd +DfS3IRpZl6zL6L4XH5V8U5uvUFKqjQsur1rXYPIqaSq57lRwGKq99aE/0t2hYxkA ++KcM66N58nBZo/iiEgPsE//kAoY218HDpLXUpMI3RbaUcD3FveujFR3jNnoVaSpW +NDnPpZo2qsjtebzP9s4EUwvaslAjfLw+Jq3wDkO7JsuuwkDeNx8KoFHNY522T9jG +R3y82LTtnovzEeKotT7srnA+fiK7NUgXYGIUkTCjdj2mUTaLHw3dajEcpe3dlqNu +cg8TTaqnqVx4+QMSGJM3RRKJPfi+yr3ZvgzZGGSnyEE+dYIhOH1l9KDUE0sHeCn5 +nX7Mhz/E2i6I3eML3FpRWunZEk+eAtv3BSVR +-----END CERTIFICATE-----'`)) + + // Telekom Security TLS ECC Root 2020 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE-----'`)) + + // Telekom Security TLS RSA Root 2023 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE-----'`)) + + // Baltimore CyberTrust Root + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE-----'`)) + + // DigiCert Assured ID Root CA + mozillaIncluded.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-----'`)) + + // DigiCert Assured ID Root G2 + mozillaIncluded.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-----'`)) + + // DigiCert Assured ID Root G3 + mozillaIncluded.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-----'`)) + + // DigiCert Global Root CA + mozillaIncluded.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-----'`)) + + // DigiCert Global Root G2 + mozillaIncluded.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-----'`)) + + // DigiCert Global Root G3 + mozillaIncluded.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-----'`)) + + // DigiCert High Assurance EV Root CA + mozillaIncluded.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-----'`)) + + // DigiCert SMIME ECC P384 Root G5 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIQBT9uoAYBcn3tP8OjtqPW7zAKBggqhkjOPQQDAzBQMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKDAmBgNVBAMTH0Rp +Z2lDZXJ0IFNNSU1FIEVDQyBQMzg0IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBQMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xKDAmBgNVBAMTH0RpZ2lDZXJ0IFNNSU1FIEVDQyBQMzg0IFJvb3QgRzUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQWnVXlttT7+2drGtShqtJ3lT6I5QeftnBm +ICikiOxwNa+zMv83E0qevAED3oTBuMbmZUeJ8hNVv82lHghgf61/6GGSKc8JR14L +HMAfpL/yW7yY75lMzHBrtrrQKB2/vgSjQjBAMB0GA1UdDgQWBBRzemuW20IHi1Jm +wmQyF/7gZ5AurTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNnADBkAjA3RPUygONx6/Rtz3zMkZrDbnHY0iNdkk2CQm1cYZX2kfWn +CPZql+mclC2YcP0ztgkCMAc8L7lYgl4Po2Kok2fwIMNpvwMsO1CnO69BOMlSSJHW +Dvu8YDB8ZD8SHkV/UT70pg== +-----END CERTIFICATE-----'`)) + + // DigiCert SMIME RSA4096 Root G5 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQBfa6BCODRst9XOa5W7ocVTANBgkqhkiG9w0BAQwFADBP +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJzAlBgNVBAMT +HkRpZ2lDZXJ0IFNNSU1FIFJTQTQwOTYgUm9vdCBHNTAeFw0yMTAxMTUwMDAwMDBa +Fw00NjAxMTQyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy +dCwgSW5jLjEnMCUGA1UEAxMeRGlnaUNlcnQgU01JTUUgUlNBNDA5NiBSb290IEc1 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Gpb2fj5fey1e+9f3Vw0 +2Npd0ctldashfFsA1IJvRYVBiqkSAnIy8BT1A3W7Y5dJD0CZCxoeVqfS0OGr3eUE +G+MfFBICiPWggAn2J5pQ8LrjouCsahSRtWs4EHqiMeGRG7e58CtbyHcJdrdRxDYK +mVNURCW3CTWGFwVWkz1BtwLXYh+KkhGH6hFt6ggR3LF4SEmS9rRRgHgj2P7hVho6 +kBNWNInV4pWLX96yzPs/OLeF9+qevy6hLi9NfWoRLjag/xEIBJVV4Bs7Z5OplFXq +Mu0GOn/Cf+OtEyfRNEGzMMO/tIj4A4Kk3z6reHegWZNx593rAAR7zEg5KOAeoxVp +yDayoQuX31XW75GcpPYW91EK7gMjkdwE/+DdOPYiAwDCB3EaEsnXRiqUG83Wuxvu +v75NUFiwC80wdin1z+W2ai92sLBpatBtZRg1fpO8chfBVULNL8Ilu/T9HaFkIlRd +4p5yQYRucZbqRQe2XnpKhp1zZHc4A9IPU6VVIMRN/2hvVanq3XHkT9mFo3xOKQKe +CwnyGlPMAKbd0TT2DcEwsZwCZKw17aWwKbHSlTMP0iAzvewjS/IZ+dqYZOQsMR8u +4Y0cBJUoTYxYzUvlc4KGjOyo1nlc+2S73AxMKPYXr+Jo1haGmNv8AdwxuvicDvko +Rkrh/ZYGRXkRaBdlXIsmh1sCAwEAAaNCMEAwHQYDVR0OBBYEFNGj1FcdT1XbdUxc +Qp5jFs60xjsfMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBDAUAA4ICAQAHpwreU7ua63C/sjaQzeSnuPEM5F1aHXhl/Mm4HiMRV3xp +NW0B/1NQvwcOuscBP1gqlHUDqxwLI9wbih43PR1Yj3PZsypv3xCgWwynyrB/uSSi +ATUy5V5GQevYf3PnQumkUSZ3gQqo6w8KUJ1+iiBn/AuOOhHTxYxgGNlLsfzU8bRJ +Tq6H4dH7dqFf8wbPl5YM6Z51gVxTDSL8NuZJbnTbAIWNfCKgjvsQTNRiE1vvS3Im +i/xOio/+lxBTxXiLQmQbX+CJ/bsJf1DgVIUmEWodZflJKdx8Nt/7PffSrO4yjW6m +fTmcRcTKDfU7tHlTpS9Wx1HFikxkXZBDI45rTBd4zOi/9TvkqEjPrZsM3zJK09kS +jiN4DS2vn6+ePAnClwDtOmkccT8539OPxGb17zaUD/PdkraWX5Cm3XOqpiCUlCVq +CQxy5BMjYEyjyhcue2cA29DN6nofOSZXiTB3y07llUVPX/s2XD35ILU6ECVPkzJa +7sGW6OlWBLBJYU3seKidGMH/2OovVu+VK3sEXmfjVUDtOQT5C3n1aoxcD4makMfN +i97bJjWhbs2zQvKiDzsMjpP/FM/895P35EEIbhlSEQ9TGXN4DM/YhYH4rVXIsJ5G +Y6+cUu5cv/DAWzceCSDSPiPGoRVKDjZ+MMV5arwiiNkMUkAf3U4PZyYW0q0XHA== +-----END CERTIFICATE-----'`)) + + // DigiCert TLS ECC P384 Root G5 + mozillaIncluded.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-----'`)) + + // DigiCert TLS RSA4096 Root G5 + mozillaIncluded.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-----'`)) + + // DigiCert Trusted Root G4 + mozillaIncluded.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-----'`)) + + // DIGITALSIGN GLOBAL ROOT ECDSA CA + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICajCCAfCgAwIBAgIUNi2PcoiiKCfkAP8kxi3k6/qdtuEwCgYIKoZIzj0EAwMw +ZDELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmljYWRv +cmEgRGlnaXRhbDEpMCcGA1UEAwwgRElHSVRBTFNJR04gR0xPQkFMIFJPT1QgRUNE +U0EgQ0EwHhcNMjEwMTIxMTEwNzUwWhcNNDYwMTE1MTEwNzUwWjBkMQswCQYDVQQG +EwJQVDEqMCgGA1UECgwhRGlnaXRhbFNpZ24gQ2VydGlmaWNhZG9yYSBEaWdpdGFs +MSkwJwYDVQQDDCBESUdJVEFMU0lHTiBHTE9CQUwgUk9PVCBFQ0RTQSBDQTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABG4Lo6szTRzqSuj8BI0UoH3wCCxfg6uT0dJ7utdJ +fY/sElBf1LnL5fD5M2MfyVfsQNgRC5foUhbMKY70BoYeONw9V8Tuqr3IVAQmWicT +UUc9Hx8ajqiVpDPQzEfMbbj8SKNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBTOr0qLGnXi8TjnAvAWrV7qZNV7tDAdBgNVHQ4EFgQUzq9Kixp14vE45wLw +Fq1e6mTVe7QwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMAqIxHGc +RANNjbTHvKiu2TAnNWprFmPX/OdZ4aeJG0wxmiNVRObzQyHVRydvbVcBqgIxAPuy +6uKXf1G1n0jrvG81iahkcKtXds3AxhRgyn/iggBz98w16o4km+UIWccEjHN4/g== +-----END CERTIFICATE-----'`)) + + // DIGITALSIGN GLOBAL ROOT RSA CA + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIUXVnIyqsJV/XmtdoplARq/8XUlYcwDQYJKoZIhvcNAQEN +BQAwYjELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmlj +YWRvcmEgRGlnaXRhbDEnMCUGA1UEAwweRElHSVRBTFNJR04gR0xPQkFMIFJPT1Qg +UlNBIENBMB4XDTIxMDEyMTEwNTAzNFoXDTQ2MDExNTEwNTAzNFowYjELMAkGA1UE +BhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmljYWRvcmEgRGlnaXRh +bDEnMCUGA1UEAwweRElHSVRBTFNJR04gR0xPQkFMIFJPT1QgUlNBIENBMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyIe2ONMc8N4S+IPHxIriibi0Inp4 ++AxmUWh2NwrVT8JaCLgWXPdyAQk3hIEqVGvXktBs+qinQxI06w7bNw8p/ooxUULo +S5yQqMgsEdP9oCl+zt6U9oLgWLRORSXxIvI90w97VBrcMrbWUU5+QbRXuCzGuQ4u +ylfx1cjTWOel6UIRrtMgJZRp14/Kog3D058HaD8V0mcuU/12gpsLc6kpDZ4RkxQI +mOyeVBJKVqIGFexrbC6SYC6GDa6CH1FN47IH1xAZVyL2qWlEhPPZPaAGv8yIfn/1 +zlulwipqdELqb6b/+Wix0F+9kdJVbzNXTB6d5OKLwYVloOBqnAAAiJLdWAgW8nAx +qBzh3r1OcenWvn61oVrDTfe/m72UpP31qlOTRskmAQRwxKBxus4lZvuRflVw7kkK +TWJ/wlCacvIYZ53pRag0hOj4gfbRWiIeB087s3/dEaVz3L6pGTppqW0bMuKJqqUn +C1p+dOIPZDldfly5wRf8x41eyewk7dLyP3qERTcCvj5rWcTmWxZtwKqeqrVZLixw +VZzMmZaYJFTRjtrKtBG0t3BDH2+QCyCgqHYTZdvbI1p1S6ELMXcK7n1oYRoTjOpR +flxWo1dMXaHrE2W/VBTM8+7c1+w8l/J4Vrjfclxw/M4G3Z/SBzHv51KRns2618AY +RAcxZUkyaRNK648CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAW +gBS1Nrw8jBqrLPZZGS2DFNqTJRXWhjAdBgNVHQ4EFgQUtTa8PIwaqyz2WRktgxTa +kyUV1oYwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQAU+zElODH4 +ygiyI3Y4rfjTWfXMtFcl4US+fvwW7K76Jp9PZxZKVvD97ccZATSOkFot1oBc7HHS +gSWCHgBx35rR1R0iu9Gl82IPtOvcJHP+plbNmhTFBDUWMaIH66UA4rb4X3L9P2FJ +jt5+TTjXeh50N2xR3L4ABLg4FPMgwe2bpyP9DUKEHX/yc8PQeGPxn+zXW+nxvmyg +SwOejWnhFNqIEIEjU//aVCsLxrmWlQQYRvN7qJfYW2ik5DgcDkXlmNMJrppe7LN5 +DTly8vSUnQ6eYCLmqPZMhc0HgjpoOc09X+M49LavO2tKn2BRRaJAAuWqDOM+0XjU +onScJroFmihwSj6mC9AdSfC6+K5BEH6kBxK9qM8pPVe7x/FDRwA+rnAYWiB7Ccs6 +OnCA5UxgmMEVwR1K98jwm+FyreddaFgLBLGMvJ+3+26LWwRV++sjVdd4UNoly74n +NrskGnkcUdH+E7v/eCzcpL4v9sVLU8+nTJlecKxZiASuZAS/e6Z6TdPod72hflAV +8+9JMIVNIVeq2yx1l62BAYeisXCdHgZaA2CxP6ZtgizUFLGBpeg9iB20cixYN4qO +OJS4c92p4Lj2d6KzfFjermk6tYulGrvy2HQGnP1icyAhdrF+cJ4Z1OsXYhk4mc02 +K0f+McvfueSsCNPYpuvUnn5LZKRVXSsXyQ== +-----END CERTIFICATE-----'`)) + + // CA Disig Root R2 + mozillaIncluded.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-----'`)) + + // GLOBALTRUST 2020 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG +A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw +FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx +MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u +aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b +RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z +YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3 +QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw +yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+ +BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ +SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH +r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0 +4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me +dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw +q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2 +nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu +H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC +XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd +6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf ++I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi +kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7 +wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB +TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C +MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn +4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I +aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy +qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE-----'`)) + + // emSign ECC Root CA - C3 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE-----'`)) + + // emSign ECC Root CA - G3 + mozillaIncluded.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-----'`)) + + // emSign Root CA - C1 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE-----'`)) + + // emSign Root CA - G1 + mozillaIncluded.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-----'`)) + + // AffirmTrust Commercial + mozillaIncluded.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-----'`)) + + // AffirmTrust Networking + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE-----'`)) + + // AffirmTrust Premium + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE-----'`)) + + // AffirmTrust Premium ECC + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE-----'`)) + + // Entrust Root Certification Authority + mozillaIncluded.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-----'`)) + + // Entrust Root Certification Authority - EC1 + mozillaIncluded.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-----'`)) + + // Entrust Root Certification Authority - G2 + mozillaIncluded.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-----'`)) + + // Entrust Root Certification Authority - G4 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE-----'`)) + + // Entrust.net Certification Authority (2048) + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE-----'`)) + + // Atos TrustedRoot 2011 + mozillaIncluded.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-----'`)) + + // Atos TrustedRoot Root CA ECC G2 2020 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICMTCCAbagAwIBAgIMC3MoERh0MBzvbwiEMAoGCCqGSM49BAMDMEsxCzAJBgNV +BAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRSb290 +IFJvb3QgQ0EgRUNDIEcyIDIwMjAwHhcNMjAxMjE1MDgzOTEwWhcNNDAxMjEwMDgz +OTA5WjBLMQswCQYDVQQGEwJERTENMAsGA1UECgwEQXRvczEtMCsGA1UEAwwkQXRv +cyBUcnVzdGVkUm9vdCBSb290IENBIEVDQyBHMiAyMDIwMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEyFyAyk7CKB9XvzjmYSP80KlblhYWwwxeFaWQCf84KLR6HgrWUyrB +u5BAdDfpgeiNL2gBNXxSLtj0WLMRHFvZhxiTkS3sndpsnm2ESPzCiQXrmBMCAWxT +Hg5JY1hHsa/Co2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFFsfxHFs +shufvlwfjP2ztvuzDgmHMB0GA1UdDgQWBBRbH8RxbLIbn75cH4z9s7b7sw4JhzAO +BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOzgmf3d5FTByx/oPijX +FVlKgspTMOzrNqW5yM6TR1bIYabhbZJTlY/241VT8N165wIxALCH1RuzYPyRjYDK +ohtRSzhUy6oee9flRJUWLzxEeC4luuqQ5OxS7lfsA4TzXtsWDQ== +-----END CERTIFICATE-----'`)) + + // Atos TrustedRoot Root CA ECC TLS 2021 + mozillaIncluded.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-----'`)) + + // Atos TrustedRoot Root CA RSA G2 2020 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIMR7opRlU+FpKXsKtAMA0GCSqGSIb3DQEBDAUAMEsxCzAJ +BgNVBAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRS +b290IFJvb3QgQ0EgUlNBIEcyIDIwMjAwHhcNMjAxMjE1MDg0MTIzWhcNNDAxMjEw +MDg0MTIyWjBLMQswCQYDVQQGEwJERTENMAsGA1UECgwEQXRvczEtMCsGA1UEAwwk +QXRvcyBUcnVzdGVkUm9vdCBSb290IENBIFJTQSBHMiAyMDIwMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAljGFSqoPMv554UOHnPsjt45/DVS9x2KTd+Qc +NQR2owOLIu7EhN2lk25uso4JA+tRFjEXqmkVGA5ndCNe6pp9tTk+PYKpa+H+qRyw +rVpNTHiDQYvP8h1impgEnGPpq2X+SB0kZQdHPrmRLumdm38aNak0sLflcDPvSnJR +tge/YD8qn51U3/PXlElRA1pAqWjdEVlc+HamvFBSEO2s7JXg1INrSdoKT5mD3jKD +SINnlbJ+54GFPc2C98oC7W2IXQiNuDW/KmkwmbtL0UHbRaCTmVGBkDYIqoq26I+z +y+7lRg1ydfVJbOGify+87YSmN+7ewk85Tvae8MnRmzCdSW3h2v8SEIzW5Zl7BbZ9 +sAnHpPiyHDmVOTP0Nc4lYnuwXyDzy234bFIUZESP08ipdgflr3GZLS0EJUh2r8Pn +zEPyB7xKJCQ33fpulAlvTF4BtP5U7COWpV7dhv/pRirx6NzspT2vb6oOD7R1+j4I +uSZFT2aGTLwZuOHVNe6ChMjTqxLnzXMzYnf0F8u9NHYqBc6V5Xh5S56wjfk8WDiR +6l6HOMC3Qv2qTIcjrQQgsX52Qtq7tha6V8iOE/p11QhMrziRqu+P+p9JLlR8Clax +evrETi/Uo/oWitCV5Zem/8P8fA5HWPN/B3sS3Fc/LeOhTVtSTDOHmagJe2x+DvLP +VkKe6wUCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQgJfMH +/adv8ZbukRBpzJrvfchoeDAdBgNVHQ4EFgQUICXzB/2nb/GW7pEQacya733IaHgw +DgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAkK06Y8h0X7dl2JrYw +M+hpRaFRS1LYejowtuQS6r+fTOAEpPY1xv6hMPdThZKtVAVXX5LlKt42J557E0fJ +anWv/PM35wz1PQFztWlR+L1Z0boL+Lq6ZCdDs3yDlYrnnhOW129KlkFJiw4grRbG +96aHW4gSiYuJyhLSVq8iASFG6auYP6eI3uTLKpp1Gfo5XgkF1wMyGrgXUQjHAEB9 +9L74DFn0aXZu06RYW14mc+RCVQZeeEAP0zif7yZRcHSR8XdiAejZy+uh3zkyHbtr +/XH+68+l5hT9AIATxpoASLCZBemugEj7CT9RFLW552BNTcovgSHuUgxletz1iUlM +MJI0WIAyWbEN/yRhD+cKQtB7vPiOJ0c/cJ0n2bYGPaW7y16Prg5Tx5xqbztMD6NA +cKiaB87UblsHotLiVLa9bzNyY61RmOGPdvFqBzgl/vZizl/bY8Jume8G3LneGRro +VD190nZ12V4+MkinjPKecgz4uFi4FyOlFId1WHoAgQciOWpMlKC1otunLMGw8aOb +wEz3bXDqMZ/xrn0+cyjZod/6k/CbsPDizSUgde/ifTIFyZt27su9MR75lJhLJFhW +SMDeBky9pjRd7RZhY3P7GeL6W9iXddRtnmA5XpSLAizrmc5gKm4bjKdLvP025pgf +ZfJ/8eOPTIBGNli2oWXLzhxEdQ== +-----END CERTIFICATE-----'`)) + + // Atos TrustedRoot Root CA RSA TLS 2021 + mozillaIncluded.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-----'`)) + + // GlobalSign + mozillaIncluded.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-----'`)) + + // GlobalSign + mozillaIncluded.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-----'`)) + + // GlobalSign + mozillaIncluded.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-----'`)) + + // GlobalSign Root CA + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE-----'`)) + + // GlobalSign Root E46 + mozillaIncluded.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-----'`)) + + // GlobalSign Root R46 + mozillaIncluded.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-----'`)) + + // GlobalSign Secure Mail Root E45 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICITCCAaegAwIBAgIQdlP+qicdlUZd1vGe5biQCjAKBggqhkjOPQQDAzBSMQsw +CQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UEAxMf +R2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IEU0NTAeFw0yMDAzMTgwMDAwMDBa +Fw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxT +aWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3VyZSBNYWlsIFJvb3Qg +RTQ1MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+XmLgUc3iZY/RUlQfxomC5Myfi7A +wKcImsNuj5s+CyLsN1O3b4qwvCc3S22pRjvZH/+loUS7LXO/nkEHXFObUQg6Wrtv +OMcWkXjCShNpHYLfWi8AiJaiLhx0+Z1+ZjeKo0IwQDAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3xNei1/CQAL9VreUTLYe1aaxFJYw +CgYIKoZIzj0EAwMDaAAwZQIwE7C+13EgPuSrnM42En1fTB8qtWlFM1/TLVqy5IjH +3go2QjJ5naZruuH5RCp7isMSAjEAoGYcToedh8ntmUwbCu4tYMM3xx3NtXKw2cbv +vPL/P/BS3QjnqmR5w+RpV5EvpMt8 +-----END CERTIFICATE-----'`)) + + // GlobalSign Secure Mail Root R45 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIQdlP+qExQq5+NMrUdA49X3DANBgkqhkiG9w0BAQwFADBS +MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UE +AxMfR2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IFI0NTAeFw0yMDAzMTgwMDAw +MDBaFw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3VyZSBNYWlsIFJv +b3QgUjQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3HnMbQb5bbvg +VgRsf+B1zC0FSehL3FTsW3eVcr9/Yp2FqYokUF9T5dt0b6QpWxMqCa2axS/C93Y7 +oUVGqkPmJP4rsG8ycBlGWnkmL/w9fV9ky1fMYWGo2ZVu45Wgbn9HEhjW7wPJ+4r6 +mr2CFalVd0sRT1nga8Nx8wzYVNWBaD4TuRUuh4o8RCc2YiRu+CwFcjBhvUKRI8Sd +JafZVJoUozGtgHkMp2NsmKOsV0czH2WW4dDSNdr5cfehpiW1QV3fPmDY0fafpfK4 +zBOqj/mybuGDLZPdPoUa3eixXCYBy0mF/PzS1H+FYoZ0+cvsNSKiDDCPO6t561by ++kLz7fkfRYlAKa3qknTqUv1WtCvaou11wm6rzlKQS/be8EmPmkjUiBltRebMjLnd +ZGBgAkD4uc+8WOs9hbnGCtOcB2aPxxg5I0bhPB6jL1Bhkgs9K2zxo0c4V5GrDY/G +nU0E0iZSXOWl/SotFioBaeepfeE2t7Eqxdmxjb25i87Mi6E+C0jNUJU0xNgIWdhr +JvS+9dQiFwBXya6bBDAznwv731aiyW5Udtqxl2InWQ8RiiIbZJY/qPG3JEqNPFN8 +bYN2PbImSHP1RBYBLQkqjhaWUNBzBl27IkiCTApGWj+A/1zy8pqsLAjg1urwEjiB +T6YQ7UarzBacC89kppkChURnRq39TecCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgGG +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKCTFShu7o8IsjXGnmJ5dKexDit7 +MA0GCSqGSIb3DQEBDAUAA4ICAQBFCvjRXKxigdAE17b/V1GJCwzL3iRlN/urnu1m +9OoMGWmJuBmxMFa02fb3vsaul8tF9hGMOjBkTMGfWcBGQggGR2QXeOCVBwbWjKKs +qdk/03tWT/zEhyjftisWI8CfH1vj1kReIk8jBIw1FrV5B4ZcL5fi9ghkptzbqIrj +pHt3DdEpkyggtFOjS05f3sH2dSP8Hzx4T3AxeC+iNVRxBKzIxG3D9pGx/s3uRG6B +9kDFPioBv6tMsQM/DRHkD9Ik4yKIm59fRz1RSeAJN34XITF2t2dxSChLJdcQ6J9h +WRbFPjJOHwzOo8wP5McRByIvOAjdW5frQmxZmpruetCd38XbCUMuCqoZPWvoajB6 +V+a/s2o5qY/j8U9laLa9nyiPoRZaCVA6Mi4dL0QRQqYA5jGY/y2hD+akYFbPedey +Ttew+m4MVyPHzh+lsUxtGUmeDn9wj3E/WCifdd1h4Dq3Obbul9Q1UfuLSWDIPGau +l+6NJllXu3jwelAwCbBgqp9O3Mk+HjrcYpMzsDpUdG8sMUXRaxEyamh29j32ahNe +JJjn6h2az3iCB2D3TRDTgZpFjZ6vm9yAx0OylWikww7oCkcVv1Qz3AHn1aYec9h6 +sr8vreNVMJ7fDkG84BH1oQyoIuHjAKNOcHyS4wTRekKKdZBZ45vRTKJkvXN5m2/y +s8H2PA== +-----END CERTIFICATE-----'`)) + + // Go Daddy Class 2 Certification Authority + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE-----'`)) + + // Go Daddy Root Certificate Authority - G2 + mozillaIncluded.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-----'`)) + + // Starfield Class 2 Certification Authority + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE-----'`)) + + // Starfield Root Certificate Authority - G2 + mozillaIncluded.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-----'`)) + + // GlobalSign + mozillaIncluded.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-----'`)) + + // GTS Root R1 + mozillaIncluded.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-----'`)) + + // GTS Root R2 + mozillaIncluded.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-----'`)) + + // GTS Root R3 + mozillaIncluded.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-----'`)) + + // GTS Root R4 + mozillaIncluded.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-----'`)) + + // Hongkong Post Root CA 3 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ +SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n +a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 +NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT +CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u +Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO +dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI +VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV +9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY +2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY +vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt +bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb +x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ +l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK +TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj +Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw +DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG +7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk +MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr +gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk +GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS +3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm +Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ +l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c +JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP +L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa +LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG +mpv0 +-----END CERTIFICATE-----'`)) + + // ACCVRAIZ1 + mozillaIncluded.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-----'`)) + + // AC RAIZ FNMT-RCM + mozillaIncluded.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-----'`)) + + // AC RAIZ FNMT-RCM SERVIDORES SEGUROS + mozillaIncluded.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-----'`)) + + // Staat der Nederlanden Root CA - G3 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE-----'`)) + + // TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 + mozillaIncluded.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-----'`)) + + // HARICA Client ECC Root CA 2021 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICWjCCAeGgAwIBAgIQMWjZ2OFiVx7SGUSI5hB98DAKBggqhkjOPQQDAzBvMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBFQ0Mg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDMzNFoXDTQ1MDIxMzExMDMzM1owbzEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQgRUND +IFJvb3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABAcYrZWWlNBcD4L3 +KkD6AsnJPTamowRqwW2VAYhgElRsXKIrbhM6iJUMHCaGNkqJGbcY3jvoqFAfyt9b +v0mAFdvjMOEdWscqigEH/m0sNO8oKJe8wflXhpWLNc+eWtFolaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUUgjSvjKBJf31GpfsTl8au1PNkK0wDgYDVR0P +AQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMEwxRUZPqOa+w3eyGhhLLYh7WOar +lGtEA7AX/9+Cc0RRLP2THQZ7FNKJ7EAM7yEBLgIwL8kuWmwsHdmV4J6wuVxSfPb4 +OMou8dQd8qJJopX4wVheT/5zCu8xsKsjWBOMi947 +-----END CERTIFICATE-----'`)) + + // HARICA Client RSA Root CA 2021 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFqjCCA5KgAwIBAgIQVVL4HtsbJCyeu5YYzQIoPjANBgkqhkiG9w0BAQsFADBv +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBS +U0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTg0NloXDTQ1MDIxMzEwNTg0NVow +bzELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBS +ZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQg +UlNBIFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AIHbV0KQLHQ19Pi4dBlNqwlad0WBc2KwNZ/40LczAIcTtparDlQSMAe8m7dI19EZ +g66O2KnxqQCEsIxenugMj1Rpv/bUCE8mcP4YQWMaszKLQPgHq1cx8MYWdmeatN0v +8tFrxdCShJFxbg8uY+kfU6TdUhPMCYMpgQzFU3VEsQ5nUxjQwx+IS5+UJLQpvLvo +Tv1v0hUdSdyNcPIRGiBRVRG6iG/E91B51qox4oQ9XjLIdypQceULL+m26u+rCjM5 +Dv2PpWdDgo6YaQkJG0DNOGdH6snsl3ES3iT1cjzR90NMJveQsonpRUtVPTEFekHi +lbpDwBfFtoU9GY1kcPNbrM2f0yl1h0uVZ2qm+NHdvJCGiUMpqTdb9V2wJlpTQnaQ +K8+eVmwrVM9cmmXfW4tIYDh8+8ULz3YEYwIzKn31g2fn+sZD/SsP1CYvd6QywSTq +ZJ2/szhxMUTyR7iiZkGh+5t7vMdGanW/WqKM6GpEwbiWtcAyCC17dDVzssrG/q8R +chj258jCz6Uq6nvWWeh8oLJqQAlpDqWW29EAufGIbjbwiLKd8VLyw3y/MIk8Cmn5 +IqRl4ZvgdMaxhZeWLK6Uj1CmORIfvkfygXjTdTaefVogl+JSrpmfxnybZvP+2M/u +vZcGHS2F3D42U5Z7ILroyOGtlmI+EXyzAISep0xxq0o3AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFKDWBz1eJPd7oEQuJFINGaorBJGnMA4GA1Ud +DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEADUf5CWYxUux57sKo8mg+7ZZF +yzqmmGM/6itNTgPQHILhy9Pl1qtbZyi8nf4MmQqAVafOGyNhDbBX8P7gyr7mkNuD +LL6DjvR5tv7QDUKnWB9p6oH1BaX+RmjrbHjJ4Orn5t4xxdLVLIJjKJ1dqBp+iObn +K/Es1dAFntwtvTdm1ASip62/OsKoO63/jZ0z4LmahKGHH3b0gnTXDvkwSD5biD6q +XGvWLwzojnPCGJGDObZmWtAfYCddTeP2Og1mUJx4e6vzExCuDy+r6GSzGCCdRjVk +JXPqmxBcWDWJsUZIp/Ss1B2eW8yppRoTTyRQqtkbbbFA+53dWHTEwm8UcuzbNZ+4 +VHVFw6bIGig1Oq5l8qmYzq9byTiMMTt/zNyW/eJb1tBZ9Ha6C8tPgxDHQNAdYOkq +5UhYdwxFab4ZcQQk4uMkH0rIwT6Z9ZaYOEgloRWwG9fihBhb9nE1mmh7QMwYXAwk +ndSV9ZmqRuqurL/0FBkk6Izs4/W8BmiKKgwFXwqXdafcfsD913oY3zDROEsfsJhw +v8x8c/BuxDGlpJcdrL/ObCFKvicjZ/MGVoEKkY624QMFMyzaNAhNTlAjrR+lxdR6 +/uoJ7KcoYItGfLXqm91P+edrFcaIz0Pb5SfcBFZub0YV8VYt6FwMc8MjgTggy8kM +ac8sqzuEYDMZUv1pFDM= +-----END CERTIFICATE-----'`)) + + // HARICA TLS ECC Root CA 2021 + mozillaIncluded.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-----'`)) + + // HARICA TLS RSA Root CA 2021 + mozillaIncluded.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-----'`)) + + // Hellenic Academic and Research Institutions ECC RootCA 2015 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE-----'`)) + + // Hellenic Academic and Research Institutions RootCA 2015 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE-----'`)) + + // IdenTrust Commercial Root CA 1 + mozillaIncluded.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-----'`)) + + // IdenTrust Public Sector Root CA 1 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE-----'`)) + + // ISRG Root X1 + mozillaIncluded.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-----'`)) + + // ISRG Root X2 + mozillaIncluded.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-----'`)) + + // Izenpe.com + mozillaIncluded.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-----'`)) + + // SZAFIR ROOT CA2 + mozillaIncluded.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-----'`)) + + // LAWtrust Root CA2 (4096) + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFmDCCA4CgAwIBAgIEVRpusTANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJa +QTERMA8GA1UEChMITEFXdHJ1c3QxITAfBgNVBAMTGExBV3RydXN0IFJvb3QgQ0Ey +ICg0MDk2KTAgFw0yMzAyMTQwOTE5MzhaGA8yMDUzMDIxNDA5NDkzOFowQzELMAkG +A1UEBhMCWkExETAPBgNVBAoTCExBV3RydXN0MSEwHwYDVQQDExhMQVd0cnVzdCBS +b290IENBMiAoNDA5NikwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +F8srQ7ps+cmTimUNEkzsJxS3E3ng1NUtGFbx+eoqEBZObETHamVG85qJNdGH+DOJ +L4gJGpIQkZDBa58Obn8mihNdGKxoAQ0QeGVw2I6PhFqXMBjQEQ5KjVIQpYErUSj1 +Y8S27ECzAeWtd73lOO+8jbPdGaB7DY2022r7JTNa+pGvxHFFMPiIKXvLv9W6JwSO +3bIA98pcmTUU6v11BhUIu8pXaPs/+7Q0c2PR1ePIOFppfWp6RAwNik7tkh0Qjzsi +LLbf7cXG8Il5VGVeXxu9j33fubft6+TFB9FnPJU7kf5CelJAgATSOVdL9JJ9/5vv +5Z3JCbKREjimKQg7ruvKzO1N504hAQf8bzLOaYyEUsZ36icwCt6lrzAraB+s1Owh +rSJJds4PwvIHKvlqEoOaOwSuGXr+oYYk+kFeJXxArCe24yk2bzXiV9AZWN//ZPbD +AUl22yu+vLlPFArVG1gh9hwuAHz4lLXLNxoU5DK5FtRg7AWqXzL6aiMSrNQQu9Ki +grRLDotwJ6rWB8FniPqEwwjJioTI0jdygQ+NFkrk1zVRpTgPjIRLlTbA9ded4F2P +q5HuAAi5nVIf7PiZu3lWsUna0uXYYYtbr/CrN8V7Go6Gvn7FexUeYWjoC4eLc0mh +F3N+KXiOyuBBL3VzdKKXOn/3LnQJuExgi0Y2GRAtnQIDAQABo4GRMIGOMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMCsGA1UdEAQkMCKADzIwMjMwMjE0 +MDkxOTM4WoEPMjA1MzAyMTQwOTQ5MzhaMB8GA1UdIwQYMBaAFNfWVmJcPxeB5nNE +KfVRBe8LYDesMB0GA1UdDgQWBBTX1lZiXD8XgeZzRCn1UQXvC2A3rDANBgkqhkiG +9w0BAQsFAAOCAgEASZwp/j3snkV/qz48/iNvNz53p1P/eJ/8SUSAV2acbtp5/81F +rUyTv7VZxukQt+X4jPuHxR6L2LM/ApYKu4qO79e0wIMgOJdZRWT89ncT8gnXocg4 +dAjq+UhM+h8EnLT/7G5WNnKTbJU+LF/eDwurycwVPhaPZvyyELih0bTewGMZzO9T +qnU2IoslH7+byNfBX+ymNwmqe2K89iIt8dZY3Yy7UvQLp3apensajdytmoFiLoYF +kHJHL6HJZ4SwDWywuJsWt9CZFC+cEpsjqI2mQx7p5S3leKcfZJRktneyqFz7Casp +6x5tddH20MWlwx2fHvMaLbLIH+UoCm7zX/3a5iOhdpBcS5gBgizuRy0CGl9/NMVp +tXKtPvPPnm34KegRJyvgWQsbYetKymmlpNXNURuUjnnN3/audF2xLBuGU/7RMAZB +NAdigkz0fseHdA6wIR4JIIDBsxU9Rm3T8QaSP++glYocbncxtut4KQx77oKlT36k +KV6eqi34jsDz/A0GhZtO3PfiCXzQFFEeerMjr/rRYSpltQHZuOMHyiR20vBKvu+G +BIBCFXARaH7Xx7v+506bnJWlHEqkydAJjKrOSNIekpfXEentZsw33PXXG3SbpupC +rF0y4Fj0gUf/0hLifhzcSXaWwx2fS8pcKjdbPYrROJsh2uO/RUPT4Fh3Hyg= +-----END CERTIFICATE-----'`)) + + // e-Szigno Root CA 2017 + mozillaIncluded.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-----'`)) + + // Microsec e-Szigno Root CA 2009 + mozillaIncluded.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-----'`)) + + // Microsoft ECC Root Certificate Authority 2017 + mozillaIncluded.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-----'`)) + + // Microsoft RSA Root Certificate Authority 2017 + mozillaIncluded.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-----'`)) + + // NAVER Global Root Certification Authority + mozillaIncluded.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-----'`)) + + // NetLock Arany (Class Gold) Főtanúsítvány + mozillaIncluded.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-----'`)) + + // OISTE WISeKey Global Root GA CA + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----END CERTIFICATE-----'`)) + + // OISTE WISeKey Global Root GB CA + mozillaIncluded.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-----'`)) + + // OISTE WISeKey Global Root GC CA + mozillaIncluded.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-----'`)) + + // QuoVadis Root CA 1 G3 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE-----'`)) + + // QuoVadis Root CA 2 + mozillaIncluded.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-----'`)) + + // QuoVadis Root CA 2 G3 + mozillaIncluded.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-----'`)) + + // QuoVadis Root CA 3 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE-----'`)) + + // QuoVadis Root CA 3 G3 + mozillaIncluded.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-----'`)) + + // Security Communication ECC RootCA1 + mozillaIncluded.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-----'`)) + + // Security Communication RootCA2 + mozillaIncluded.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-----'`)) + + // AAA Certificate Services + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE-----'`)) + + // COMODO Certification Authority + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE-----'`)) + + // COMODO ECC Certification Authority + mozillaIncluded.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-----'`)) + + // COMODO RSA Certification Authority + mozillaIncluded.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-----'`)) + + // Sectigo Public Email Protection Root E46 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIQbvXTp0GOoFlApzBr0kBlVjAKBggqhkjOPQQDAzBaMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQDEyhT +ZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgRTQ2MB4XDTIxMDMy +MjAwMDAwMFoXDTQ2MDMyMTIzNTk1OVowWjELMAkGA1UEBhMCR0IxGDAWBgNVBAoT +D1NlY3RpZ28gTGltaXRlZDExMC8GA1UEAxMoU2VjdGlnbyBQdWJsaWMgRW1haWwg +UHJvdGVjdGlvbiBSb290IEU0NjB2MBAGByqGSM49AgEGBSuBBAAiA2IABLinUpT1 +PgWwG/YfsdN+ueQFZlSAzmylaH3kU1LbgvrEht9DePfIrRa8P3gyy2vTSdZE5bN+ +n3umxizy4rbTibCaPEvOiUvGxss6SWAPRrxtTnqcyZuFewq2sEfCiOPU0aNCMEAw +HQYDVR0OBBYEFC1OjKfCI7JXqQZrPmsrifPDXkfOMA4GA1UdDwEB/wQEAwIBhjAP +BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCSnRpZY0VYjhsW5H16 +bDZIMB8rcueQMzT9JKLGBoxvOzJXWvj+xkkSU5rZELKZUXICMAUlKjMh/JPmIqLM +cFUoNVaiB8QhhCMaTEyZUJmSFMtK3Fb79dOPaiz1cTr4izsDng== +-----END CERTIFICATE-----'`)) + + // Sectigo Public Email Protection Root R46 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIQHUSeuQ2DkXSu3fLriLemozANBgkqhkiG9w0BAQwFADBa +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQD +EyhTZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgUjQ2MB4XDTIx +MDMyMjAwMDAwMFoXDTQ2MDMyMTIzNTk1OVowWjELMAkGA1UEBhMCR0IxGDAWBgNV +BAoTD1NlY3RpZ28gTGltaXRlZDExMC8GA1UEAxMoU2VjdGlnbyBQdWJsaWMgRW1h +aWwgUHJvdGVjdGlvbiBSb290IFI0NjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAJHlG/qqbTcrdccuXxSl2yyXtixGj2nZ7JYt8x1avtMdI+ZoCf9KEXMa +rmefdprS5+y42V8r+SZWUa92nan8F+8yCtAjPLosT0eD7J0FaEJeBuDV6CtoSJey ++vOkcTV9NJsXi39NDdvcTwVMlGK/NfovyKccZtlxX+XmWlXKq/S4dxlFUEVOSqvb +nmbBGbc3QshWpUAS+TPoOEU6xoSjAo4vJLDDQYUHSZzP3NHyJm/tMxwzZypFN9mF +ZSIasbUQUglrA8YfcD2RxH2QPe1m+JD/JeDtkqKLMSmtnBJmeGOdV+z7C96O3IvL +Oql39Lrl7DiMi+YTZqdpWMOCGhrN8Z/YU5JOSX2pRefxQyFatz5AzWOJz9m/x1AL +4bzniJatntQX2l3P4JH9phDUuQOBm2ms+4SogTXrG+tobHxgPsPfybSudB1Ird1u +EYbhKmo2Fq7IzrzbWPxAk0DYjlOXwqwiOOWIMbMuoe/s4EIN6v+TVkoGpJtMAmhk +j1ZQwYEF/cvbxdcV8mu1dsOj+TLOyrVKqRt9Gdx/x2p+ley2uI39lUqcoytti/Fw +5UcrAFzkuZ7U+NlYKdDL4ChibK6cYuLMvDaTQfXv/kZilbBXSnQsR1Ipnd2ioU9C +wpLOLVBSXowKoffYncX4/TaHTlf9aKFfmYMc8LXd6JLTZUBVypaFAgMBAAGjQjBA +MB0GA1UdDgQWBBSn15V360rDJ82TvjdMJoQhFH1dmDAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQwFAAOCAgEANNLxFfOTAdRyi/Cr +CB8TPHO0sKvoeNlsupqvJuwQgOUNUzHd4/qMUSIkMze4GH46+ljoNOWM4KEfCUHS +Nz/Mywk1Qojp/BHXz0KqpHC2ccFTvcV0r8QiJGPPYoJ9yctRwYiQbVtcvvuZqLq2 +hrDpZgvlG2uv6iuGp9+oI0yWP09XQhgVg0Pxhia3KgPOC53opWgejG+9heMbUY/n +Fy8r0NZ4wi3dcojUZZ76mdR+55cKkgGapamEOgwqdD0zGMiH9+ik9YZCOf1rdSn8 +AAasoqUaVI7pUEkXZq9LBC2blIClVKuMVxdEnw/WaGRytEseAcfZm5TZg5mvEgUR +o5gi0vJXyiT5ujgVEki6Yzv8i5V41nIHVszN/J0c0MVkO2M0zwSZircweXq28sbV +2VR6hwt+TveE7BTziBYS8dWuChoJ7oat5av9rsMpeXTDAV8Rm991mcZK95uPbEns +IS+0AlmzLdBykLoLFHR4S8/BX1VyjlQrE876WAzTuyzZqZFh+PjxtnvevKnMkgTM +S2tfc4C2Ie1QT9d2h27O39K3vWKhfVhiaEVStj/eEtvtBGmedoiqAW3ahsdgG8NS +rDfsUHGAciohRQpTRzwZ643SWQTeJbDrHzVvYH3Xtca7CyeN4E1U5c8dJgFuOzXI +IBKJg/DS7Vg7NJ27MfUy/THzVho= +-----END CERTIFICATE-----'`)) + + // Sectigo Public Server Authentication Root E46 + mozillaIncluded.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-----'`)) + + // Sectigo Public Server Authentication Root R46 + mozillaIncluded.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-----'`)) + + // USERTrust ECC Certification Authority + mozillaIncluded.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-----'`)) + + // USERTrust RSA Certification Authority + mozillaIncluded.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-----'`)) + + // SSL.com Client ECC Root CA 2022 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICQDCCAcagAwIBAgIQdvhIHq7wPHAf4D8lVAGD1TAKBggqhkjOPQQDAzBRMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQDDB9T +U0wuY29tIENsaWVudCBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzAzMloX +DTQ2MDgxOTE2MzAzMVowUTELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjEoMCYGA1UEAwwfU1NMLmNvbSBDbGllbnQgRUNDIFJvb3QgQ0EgMjAy +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABC1Tfp+LPrM2ulDizOvcuiaK04wGP2cP +7/UX5dSumkYqQQEHaedncfHCAzbG8CtSjs8UkmikPnBREmmNeKKCyikUwOSUIrJE +kmBvyASkZ9Wi0PPQ1+qOPA+60kBHkDTufaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf +BgNVHSMEGDAWgBS3/i1ixYFTzVIaL11goMNd+7IcHDAdBgNVHQ4EFgQUt/4tYsWB +U81SGi9dYKDDXfuyHBwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUC +ME0HES0R+7kmwyHdcuEX/MHPFOpJznGHjtZT3BHNXVSKr9kt9IxR6rxmR+J/lYNg +ZQIxAIwhTE+75bBQ35BiSebMkdv4P11xkQiOT5LJf6Zc6hN+7W3E6MMqb1wR4aXz +alqaTQ== +-----END CERTIFICATE-----'`)) + + // SSL.com Client RSA Root CA 2022 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFjzCCA3egAwIBAgIQdq/uiJMVRbZQU5uAnKTfmjANBgkqhkiG9w0BAQsFADBR +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQD +DB9TU0wuY29tIENsaWVudCBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzEw +N1oXDTQ2MDgxOTE2MzEwNlowUTELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBD +b3Jwb3JhdGlvbjEoMCYGA1UEAwwfU1NMLmNvbSBDbGllbnQgUlNBIFJvb3QgQ0Eg +MjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALhY20Yw+8k/48jw +ATM04tpIqBjpIG6a1wHh1SmPMLQjauTLYrC+4p8gvT5UoDlox4Y3ZnQGBu90K9rc +n4SpUi+Q0u5+fPulIq1vcEZnlj0p1KO7VnsUBFnBIWNEHrIfElyQh2UNiPYeiCLi +Y1S78zb41n/c2v8pNanGbg5pWz/YvoKHFXBdsMdcEg9jpjjNz3O5ww6JJjcbP2Ic +MmnRm9n/VZAx3rFj3c/FdHf874ghU78AMRomLAAwpV9s4+T2AIrKmIecdAN6i2bs +fv2jjzUlXHils6T7PW2pivBsiIKL/UrQb+TXo7SONEk4vs5F5dIcyl7CNxSLzWZW +Mzed5WvsQ5JkoELadW/AFez5ab00uYp7+hb7Vf5SIOgEBFZWZfU3RJjIikbpt6y4 +6L5ijlQ2W/c7cL9d7i26X95CGYbwf4vrCMvYvuoOQkKgNnNXF+0y6tCN6Acbm5no +xJpiBA5I9zwSuvdYwZqM6cewIzZWNB3LbNq6B4Qd/dGsn+bCie/DuWwYs2mHV1+1 +DDhbpyEkKjunNJGetFTqKE/TwaOL5OYr1fKdv5thACLd1ktEHz9dVv7enHjMmVuq +5L2620NLrUwmTKNNNIpsdDYT22L8m7IFgf+uPwzN9hui9DnnyvVMXPtUdzWAWsAS +oRMBM2c9nYGhqfWFJFiIeOf042hVAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8w +HwYDVR0jBBgwFoAU8DhClDSpPAB/Uu45pfdLDbxqfSMwHQYDVR0OBBYEFPA4QpQ0 +qTwAf1LuOaX3Sw28an0jMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AgEAmU/b8OrWEfoq/cirbeQOc2LSQp8V/nxwUj9kh4IxP0VALuEinwZmKfyW0y2N +tjjH2fMnwVkpoIz2cyQPKCLXTmHdE93bnzJSk/tPzOo4PJhqA6sWryHRQq59RSvq +xM+KWZ+CcHY6+GImyRCXWEAkpC25LymAJ+GJa3LKSQhxN1MF8YDO00IC0vzC0ZQG +7gfi9oPif5/nu1bDW7/dlZMJHiTBzybNraSuwrRp56q17TeU6d3RY4VrmnpKVnbc +GYUo1OTGpNi4lkF30LRZ8UYFh4cCH2m5ghjQQ9km2hpnqNZ1durybQ5C/4gmom6E +/n5iG/DGPe3AHGrHkda4ADdJm7mEBaHNbjHWROpTi7pTmB2hkIrphfgb8pNYw8jc +miZPPiDPT0PzEIx/EGF6NsqqC33Mn0dEWa6llcaZU+MHaz1JELAY/10OhUMUS+dr +00q1smBh3GlJAiNd6JJxw5yfRWd5HtwyhrqqVTxkbzK1EEAV3nJAeOBucLtu6wno +OdmsupJ13UPKugGVrRqBKzrw48UvDBhNEMauwO3+BVJ/GQXLqa81CAw4IuT+VuVT +Pr/k1rPZCMM91TMygSTFqeFlEbgyMzBxGEkdGkXGmhSKWDkobvPLUblJJmR4A8eR +EYOpuZA0tm+qBZ6FKFeZvn8nBkliTaH8CeErRglMFJtWj0U= +-----END CERTIFICATE-----'`)) + + // SSL.com EV Root Certification Authority ECC + mozillaIncluded.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-----'`)) + + // SSL.com EV Root Certification Authority RSA R2 + mozillaIncluded.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-----'`)) + + // SSL.com Root Certification Authority ECC + mozillaIncluded.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-----'`)) + + // SSL.com Root Certification Authority RSA + mozillaIncluded.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-----'`)) + + // SSL.com TLS ECC Root CA 2022 + mozillaIncluded.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-----'`)) + + // SSL.com TLS RSA Root CA 2022 + mozillaIncluded.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-----'`)) + + // SwissSign Gold CA - G2 + mozillaIncluded.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-----'`)) + + // SwissSign Silver CA - G2 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE +BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu +IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow +RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY +U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv +Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br +YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF +nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH +6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt +eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ +c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ +MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH +HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf +jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 +5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB +rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c +wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB +AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp +WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 +xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ +2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ +IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 +aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X +em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR +dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ +OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ +hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy +tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE-----'`)) + + // TWCA CYBER Root CA + mozillaIncluded.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-----'`)) + + // TWCA Global Root CA + mozillaIncluded.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-----'`)) + + // TWCA Global Root CA G2 + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIFlTCCA32gAwIBAgIQQAE0jMIAAAAAAAAAAZdY9DANBgkqhkiG9w0BAQwFADBU +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMR8wHQYDVQQDExZUV0NBIEdsb2JhbCBSb290IENBIEcyMB4XDTIyMTEyMjA2 +NDIyMVoXDTQ3MTEyMjE1NTk1OVowVDELMAkGA1UEBhMCVFcxEjAQBgNVBAoTCVRB +SVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEfMB0GA1UEAxMWVFdDQSBHbG9iYWwg +Um9vdCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKoO1SCS +Aa2C+QwIkTRrihbQRhb/A7jYjeqTNPv/K739bqrcm/KGgVX1iRzEjXVqWHiREx4C +E3A9774K5wCPuDHldMUwvv991pnlwkKjzyHWswh/kdVh5qKVEA3vXpcLSTjVIrDX +i1lvnzWbf9KRzHp/u6Cf3lUz9kuNCup9CcB53L1E4v4c52QhKM8ESuK0v4Z5KrsO +k8mPXqwwOVKQB7nqnCZCFMRnRv7RGmihPlAZoyYKJymQwva063OaeB7hmPRlDDUh +BvgL3mLlTcGzXdm5+mGXKuPqx0RVJJL+Eqc/xHfgLQKBB9X7feYQnjq0qO/s+1Dq +Nc/MfrtCuURsUum/KnIfP96bcOncWsU7u7/wWYWvL8GwFHkFrHWfJfURJwZgIcdt +Zb6oiZzlrEbf+F1EA41gvfexDcwv70FUL+5rlblOfDTfO/l3nX3NBz0cBjMSgOxy +nPItgtrVO8TH+QTDZAJ89TVgp7RGKS4b76VYgC56iVE4Njz9oXe4gDDQit6NpzQm +7CO7GFUYNkXu7QEGqk2/ZAzKmJcaMQJm+HhoW4jfCajnm/o0bXAcIa0Ii/Khtqx2 +ar/xgCUAvjweTa65PLaVY71rfkcSkFVFEY3sFx/BvieBk1djaQAmd4vDWeV70Q1E +8qjw94WaBffCLnCak4XYlZAxkFSm7AufN0UPAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFJKM1DbRW0dTxHENhN1k +KvU2ZEDnMB0GA1UdDgQWBBSSjNQ20VtHU8RxDYTdZCr1NmRA5zANBgkqhkiG9w0B +AQwFAAOCAgEAJfxL2pC02nXnQTqB0ab+oGrzGHFiaiQIi6l6TclVzs8QKC4EGZYF +z10CICo7s1U/Ac1CzbJ37f9183x325alz4xnBvSkm3L2IUkJmKMyXndaYwnvYkOX +Aji16jwYUGj8WVvZedTx5FZIE1bY03ELXniUOBFF+gUX9Q51HmJSYUa6LhmthrSI +D7FQ5kAANBqVnZPgUfnUVUbplTwlhi6X1wExGETsHGDpfWmvMviXQCUkto0aVTzF +t/e8BlI7cTBwPnEXfvFmBF5dvIoxQ6aSHXtU0qU2i2+N1l7a1MMuHd85VWCCMJ4n +/46A3WNMplU12NAzqYBtPl6dzKhngGb6mVcMUsoZdbA4NVUqgcWMHlbXX5DyINja +4GZx6bJ4q2e5JG5rNnL8b439f3I5KGdSkQUfV2XSo6cNYfqh59U1RpXJBof2MOwy +UamsVsAhTqMUdAU6vOO/bT1OP16lpG0pv4RRdVOOhhr1UXAqDRxOQOH9o+OlK2eQ +ksdsroW/OpsXFcqcKpPUTTkNvCAIo42IbAkNjK5EIU3JcezYJtcXni0RGDyjIn24 +J1S/aMg7QsyPXk7n3MLF+mpED41WiHrfiYRsoLM+PfFlAAmI6irrQM6zXawyF67B +m+nQwfVJlN2nznxaB+uuIJwXMJJpk3Lzmltxm/5q33owaY6zLtsPLN0= +-----END CERTIFICATE-----'`)) + + // TWCA Root Certification Authority + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE-----'`)) + + // Telia Root CA v2 + mozillaIncluded.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-----'`)) + + // TeliaSonera Root CA v1 + mozillaIncluded.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-----'`)) + + // Secure Global CA + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE-----'`)) + + // SecureTrust CA + mozillaIncluded.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-----'`)) + + // Trustwave Global Certification Authority + mozillaIncluded.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-----'`)) + + // Trustwave Global ECC P256 Certification Authority + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE-----'`)) + + // Trustwave Global ECC P384 Certification Authority + mozillaIncluded.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-----'`)) + + // XRamp Global Certification Authority + mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE-----'`)) +} diff --git a/common/certificate/store.go b/common/certificate/store.go new file mode 100644 index 0000000000..34f20019d4 --- /dev/null +++ b/common/certificate/store.go @@ -0,0 +1,185 @@ +package certificate + +import ( + "context" + "crypto/x509" + "io/fs" + "os" + "path/filepath" + "strings" + + "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" + "github.com/sagernet/sing/service" +) + +var _ adapter.CertificateStore = (*Store)(nil) + +type Store struct { + systemPool *x509.CertPool + currentPool *x509.CertPool + certificate string + certificatePaths []string + certificateDirectoryPaths []string + watcher *fswatch.Watcher +} + +func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) { + var systemPool *x509.CertPool + switch options.Store { + case C.CertificateStoreSystem, "": + systemPool = x509.NewCertPool() + platformInterface := service.FromContext[platform.Interface](ctx) + var systemValid bool + if platformInterface != nil { + for _, cert := range platformInterface.SystemCertificates() { + if systemPool.AppendCertsFromPEM([]byte(cert)) { + systemValid = true + } + } + } + if !systemValid { + certPool, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + systemPool = certPool + } + case C.CertificateStoreMozilla: + systemPool = mozillaIncluded + case C.CertificateStoreNone: + systemPool = nil + default: + return nil, E.New("unknown certificate store: ", options.Store) + } + store := &Store{ + systemPool: systemPool, + certificate: strings.Join(options.Certificate, "\n"), + certificatePaths: options.CertificatePath, + certificateDirectoryPaths: options.CertificateDirectoryPath, + } + var watchPaths []string + for _, target := range options.CertificatePath { + watchPaths = append(watchPaths, target) + } + for _, target := range options.CertificateDirectoryPath { + watchPaths = append(watchPaths, target) + } + if len(watchPaths) > 0 { + watcher, err := fswatch.NewWatcher(fswatch.Options{ + Path: watchPaths, + Logger: logger, + Callback: func(_ string) { + err := store.update() + if err != nil { + logger.Error(E.Cause(err, "reload certificates")) + } + }, + }) + if err != nil { + return nil, E.Cause(err, "fswatch: create fsnotify watcher") + } + store.watcher = watcher + } + err := store.update() + if err != nil { + return nil, E.Cause(err, "initializing certificate store") + } + return store, nil +} + +func (s *Store) Name() string { + return "certificate" +} + +func (s *Store) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if s.watcher != nil { + return s.watcher.Start() + } + return nil +} + +func (s *Store) Close() error { + if s.watcher != nil { + return s.watcher.Close() + } + return nil +} + +func (s *Store) Pool() *x509.CertPool { + return s.currentPool +} + +func (s *Store) update() error { + var currentPool *x509.CertPool + if s.systemPool == nil { + currentPool = x509.NewCertPool() + } else { + currentPool = s.systemPool.Clone() + } + if s.certificate != "" { + if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) { + return E.New("invalid certificate PEM strings") + } + } + for _, path := range s.certificatePaths { + pemContent, err := os.ReadFile(path) + if err != nil { + return err + } + if !currentPool.AppendCertsFromPEM(pemContent) { + return E.New("invalid certificate PEM file: ", path) + } + } + var firstErr error + for _, directoryPath := range s.certificateDirectoryPaths { + directoryEntries, err := readUniqueDirectoryEntries(directoryPath) + if err != nil { + if firstErr == nil && !os.IsNotExist(err) { + firstErr = E.Cause(err, "invalid certificate directory: ", directoryPath) + } + continue + } + for _, directoryEntry := range directoryEntries { + pemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name())) + if err == nil { + currentPool.AppendCertsFromPEM(pemContent) + } + } + } + if firstErr != nil { + return firstErr + } + s.currentPool = currentPool + return nil +} + +func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) { + files, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + uniq := files[:0] + for _, f := range files { + if !isSameDirSymlink(f, dir) { + uniq = append(uniq, f) + } + } + return uniq, nil +} + +func isSameDirSymlink(f fs.DirEntry, dir string) bool { + if f.Type()&fs.ModeSymlink == 0 { + return false + } + target, err := os.Readlink(filepath.Join(dir, f.Name())) + return err == nil && !strings.Contains(target, "/") +} diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go index fff1873d29..8021935049 100644 --- a/common/tls/ech_client.go +++ b/common/tls/ech_client.go @@ -100,6 +100,7 @@ func NewECHClient(ctx context.Context, serverAddress string, options option.Outb var tlsConfig cftls.Config tlsConfig.Time = ntp.TimeFuncFromContext(ctx) + tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx) if options.DisableSNI { tlsConfig.ServerName = "127.0.0.1" } else { diff --git a/common/tls/ech_server.go b/common/tls/ech_server.go index d41783fe31..03f5d87695 100644 --- a/common/tls/ech_server.go +++ b/common/tls/ech_server.go @@ -90,7 +90,7 @@ func (c *echServerConfig) startWatcher() error { Callback: func(path string) { err := c.credentialsUpdated(path) if err != nil { - c.logger.Error(E.Cause(err, "reload credentials from ", path)) + c.logger.Error(E.Cause(err, "reload credentials")) } }, }) diff --git a/common/tls/reality_client.go b/common/tls/reality_client.go index f9a11a0b6c..748567b5d3 100644 --- a/common/tls/reality_client.go +++ b/common/tls/reality_client.go @@ -27,9 +27,11 @@ import ( "time" "unsafe" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/debug" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/ntp" aTLS "github.com/sagernet/sing/common/tls" utls "github.com/sagernet/utls" @@ -40,6 +42,7 @@ import ( var _ ConfigCompat = (*RealityClientConfig)(nil) type RealityClientConfig struct { + ctx context.Context uClient *UTLSClientConfig publicKey []byte shortID [8]byte @@ -70,7 +73,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option. if decodedLen > 8 { return nil, E.New("invalid short_id") } - return &RealityClientConfig{uClient, publicKey, shortID}, nil + return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil } func (e *RealityClientConfig) ServerName() string { @@ -180,20 +183,24 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn } if !verifier.verified { - go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id) + go realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id) return nil, E.New("reality verification failed") } return &realityClientConnWrapper{uConn}, nil } -func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { +func realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { defer uConn.Close() client := &http.Client{ Transport: &http2.Transport{ DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) { return uConn, nil }, + TLSClientConfig: &tls.Config{ + Time: ntp.TimeFuncFromContext(ctx), + RootCAs: adapter.RootPoolFromContext(ctx), + }, }, } request, _ := http.NewRequest("GET", "https://"+serverName, nil) @@ -213,6 +220,7 @@ func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello [ func (e *RealityClientConfig) Clone() Config { return &RealityClientConfig{ + e.ctx, e.uClient.Clone().(*UTLSClientConfig), e.publicKey, e.shortID, diff --git a/common/tls/std_client.go b/common/tls/std_client.go index 7cd130a6e6..78f4e5cf9d 100644 --- a/common/tls/std_client.go +++ b/common/tls/std_client.go @@ -8,6 +8,7 @@ import ( "os" "strings" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" @@ -58,6 +59,7 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb var tlsConfig tls.Config tlsConfig.Time = ntp.TimeFuncFromContext(ctx) + tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx) if options.DisableSNI { tlsConfig.ServerName = "127.0.0.1" } else { diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 27986003bb..9ba405ba98 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -100,7 +100,7 @@ func (c *STDServerConfig) startWatcher() error { Callback: func(path string) { err := c.certificateUpdated(path) if err != nil { - c.logger.Error(err) + c.logger.Error(E.Cause(err, "reload certificate")) } }, }) diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index 15103f0522..fe8e4296d9 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -12,6 +12,7 @@ import ( "os" "strings" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" @@ -130,6 +131,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out var tlsConfig utls.Config tlsConfig.Time = ntp.TimeFuncFromContext(ctx) + tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx) if options.DisableSNI { tlsConfig.ServerName = "127.0.0.1" } else { diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index 9efd040420..cfe1e53279 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -2,32 +2,32 @@ package urltest import ( "context" + "crypto/tls" "net" "net/http" "net/url" "sync" "time" + "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" ) -type History struct { - Time time.Time `json:"time"` - Delay uint16 `json:"delay"` -} +var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil) type HistoryStorage struct { access sync.RWMutex - delayHistory map[string]*History + delayHistory map[string]*adapter.URLTestHistory updateHook chan<- struct{} } func NewHistoryStorage() *HistoryStorage { return &HistoryStorage{ - delayHistory: make(map[string]*History), + delayHistory: make(map[string]*adapter.URLTestHistory), } } @@ -35,7 +35,7 @@ func (s *HistoryStorage) SetHook(hook chan<- struct{}) { s.updateHook = hook } -func (s *HistoryStorage) LoadURLTestHistory(tag string) *History { +func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory { if s == nil { return nil } @@ -51,7 +51,7 @@ func (s *HistoryStorage) DeleteURLTestHistory(tag string) { s.notifyUpdated() } -func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) { +func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) { s.access.Lock() s.delayHistory[tag] = history s.access.Unlock() @@ -110,6 +110,10 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return instance, nil }, + TLSClientConfig: &tls.Config{ + Time: ntp.TimeFuncFromContext(ctx), + RootCAs: adapter.RootPoolFromContext(ctx), + }, }, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse diff --git a/constant/certificate.go b/constant/certificate.go new file mode 100644 index 0000000000..7138242ce9 --- /dev/null +++ b/constant/certificate.go @@ -0,0 +1,7 @@ +package constant + +const ( + CertificateStoreSystem = "system" + CertificateStoreMozilla = "mozilla" + CertificateStoreNone = "none" +) diff --git a/experimental/clashapi/api_meta_group.go b/experimental/clashapi/api_meta_group.go index 8f09ced9e3..31dbdaf692 100644 --- a/experimental/clashapi/api_meta_group.go +++ b/experimental/clashapi/api_meta_group.go @@ -111,7 +111,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) server.urlTestHistory.DeleteURLTestHistory(realTag) } else { server.logger.Debug("outbound ", tag, " available: ", t, "ms") - server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{ + server.urlTestHistory.StoreURLTestHistory(realTag, &adapter.URLTestHistory{ Time: time.Now(), Delay: t, }) diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index 6246b9da0d..ef88ff37c5 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -72,9 +72,9 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject { info.Put("udp", common.Contains(detour.Network(), N.NetworkUDP)) delayHistory := server.urlTestHistory.LoadURLTestHistory(adapter.OutboundTag(detour)) if delayHistory != nil { - info.Put("history", []*urltest.History{delayHistory}) + info.Put("history", []*adapter.URLTestHistory{delayHistory}) } else { - info.Put("history", []*urltest.History{}) + info.Put("history", []*adapter.URLTestHistory{}) } if group, isGroup := detour.(adapter.OutboundGroup); isGroup { info.Put("now", group.Now()) @@ -116,7 +116,7 @@ func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) { "type": "Fallback", "name": "GLOBAL", "udp": true, - "history": []*urltest.History{}, + "history": []*adapter.URLTestHistory{}, "all": allProxies, "now": defaultTag, }) @@ -208,7 +208,7 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) if err != nil { server.urlTestHistory.DeleteURLTestHistory(realTag) } else { - server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{ + server.urlTestHistory.StoreURLTestHistory(realTag, &adapter.URLTestHistory{ Time: time.Now(), Delay: delay, }) diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index d01e180a9f..e6d8c4cfad 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -48,7 +48,7 @@ type Server struct { logger log.Logger httpServer *http.Server trafficManager *trafficontrol.Manager - urlTestHistory *urltest.HistoryStorage + urlTestHistory adapter.URLTestHistoryStorage mode string modeList []string modeUpdateHook chan<- struct{} @@ -79,7 +79,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op externalUIDownloadURL: options.ExternalUIDownloadURL, externalUIDownloadDetour: options.ExternalUIDownloadDetour, } - s.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx) + s.urlTestHistory = service.FromContext[adapter.URLTestHistoryStorage](ctx) if s.urlTestHistory == nil { s.urlTestHistory = urltest.NewHistoryStorage() } @@ -234,7 +234,7 @@ func (s *Server) SetMode(newMode string) { s.logger.Info("updated mode: ", newMode) } -func (s *Server) HistoryStorage() *urltest.HistoryStorage { +func (s *Server) HistoryStorage() adapter.URLTestHistoryStorage { return s.urlTestHistory } diff --git a/experimental/clashapi/server_resources.go b/experimental/clashapi/server_resources.go index 7cf0000b89..ad9fff5369 100644 --- a/experimental/clashapi/server_resources.go +++ b/experimental/clashapi/server_resources.go @@ -3,6 +3,7 @@ package clashapi import ( "archive/zip" "context" + "crypto/tls" "io" "net" "net/http" @@ -15,6 +16,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/service/filemanager" ) @@ -60,6 +62,10 @@ func (s *Server) downloadExternalUI() error { DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) }, + TLSClientConfig: &tls.Config{ + Time: ntp.TimeFuncFromContext(s.ctx), + RootCAs: adapter.RootPoolFromContext(s.ctx), + }, }, } defer httpClient.CloseIdleConnections() diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go index c30d996e6b..5dcb3d6797 100644 --- a/experimental/libbox/command_urltest.go +++ b/experimental/libbox/command_urltest.go @@ -76,7 +76,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error { if err != nil { historyStorage.DeleteURLTestHistory(outboundTag) } else { - historyStorage.StoreURLTestHistory(outboundTag, &urltest.History{ + historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{ Time: time.Now(), Delay: t, }) diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 603d8b6d27..d3149dc27f 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -108,6 +108,10 @@ func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState { return adapter.WIFIState{} } +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) { return nil, os.ErrInvalid } diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index f05903671f..affcad387f 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -21,6 +21,7 @@ type PlatformInterface interface { UnderNetworkExtension() bool IncludeAllNetworks() bool ReadWIFIState() *WIFIState + SystemCertificates() StringIterator ClearDNSCache() SendNotification(notification *Notification) error } diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index ef37daea22..35b0830b4e 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -19,6 +19,7 @@ type Interface interface { IncludeAllNetworks() bool ClearDNSCache() ReadWIFIState() adapter.WIFIState + SystemCertificates() []string process.Searcher SendNotification(notification *Notification) error } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index bd891f6076..48d614ff0d 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -34,7 +34,7 @@ import ( type BoxService struct { ctx context.Context cancel context.CancelFunc - urlTestHistoryStorage *urltest.HistoryStorage + urlTestHistoryStorage adapter.URLTestHistoryStorage instance *box.Box clashServer adapter.ClashServer pauseManager pause.Manager @@ -233,6 +233,10 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { return (adapter.WIFIState)(*wifiState) } +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) { var uid int32 if w.useProcFS { diff --git a/option/certificate.go b/option/certificate.go new file mode 100644 index 0000000000..ab524b9921 --- /dev/null +++ b/option/certificate.go @@ -0,0 +1,36 @@ +package option + +import ( + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" +) + +type _CertificateOptions struct { + Store string `json:"store,omitempty"` + Certificate badoption.Listable[string] `json:"certificate,omitempty"` + CertificatePath badoption.Listable[string] `json:"certificate_path,omitempty"` + CertificateDirectoryPath badoption.Listable[string] `json:"certificate_directory_path,omitempty"` +} + +type CertificateOptions _CertificateOptions + +func (o CertificateOptions) MarshalJSON() ([]byte, error) { + switch o.Store { + case C.CertificateStoreSystem: + o.Store = "" + } + return json.Marshal((*_CertificateOptions)(&o)) +} + +func (o *CertificateOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_CertificateOptions)(o)) + if err != nil { + return err + } + switch o.Store { + case C.CertificateStoreSystem, "": + o.Store = C.CertificateStoreSystem + } + return nil +} diff --git a/option/options.go b/option/options.go index 85ff81eb97..c0e579ccb5 100644 --- a/option/options.go +++ b/option/options.go @@ -14,6 +14,7 @@ type _Options struct { Log *LogOptions `json:"log,omitempty"` DNS *DNSOptions `json:"dns,omitempty"` NTP *NTPOptions `json:"ntp,omitempty"` + Certificate *CertificateOptions `json:"certificate,omitempty"` Endpoints []Endpoint `json:"endpoints,omitempty"` Inbounds []Inbound `json:"inbounds,omitempty"` Outbounds []Outbound `json:"outbounds,omitempty"` diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index c1a5c597e7..f44698e140 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -182,7 +182,7 @@ type URLTestGroup struct { interval time.Duration tolerance uint16 idleTimeout time.Duration - history *urltest.HistoryStorage + history adapter.URLTestHistoryStorage checking atomic.Bool selectedOutboundTCP adapter.Outbound selectedOutboundUDP adapter.Outbound @@ -208,8 +208,9 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage if interval > idleTimeout { return nil, E.New("interval must be less or equal than idle_timeout") } - var history *urltest.HistoryStorage - if history = service.PtrFromContext[urltest.HistoryStorage](ctx); history != nil { + var history adapter.URLTestHistoryStorage + if historyFromCtx := service.PtrFromContext[urltest.HistoryStorage](ctx); historyFromCtx != nil { + history = historyFromCtx } else if clashServer := service.FromContext[adapter.ClashServer](ctx); clashServer != nil { history = clashServer.HistoryStorage() } else { @@ -376,7 +377,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint g.history.DeleteURLTestHistory(realTag) } else { g.logger.Debug("outbound ", tag, " available: ", t, "ms") - g.history.StoreURLTestHistory(realTag, &urltest.History{ + g.history.StoreURLTestHistory(realTag, &adapter.URLTestHistory{ Time: time.Now(), Delay: t, }) diff --git a/route/router.go b/route/router.go index 0872421ff9..ae2ecb5502 100644 --- a/route/router.go +++ b/route/router.go @@ -90,7 +90,7 @@ func (r *Router) Start(stage adapter.StartStage) error { var cacheContext *adapter.HTTPStartContext if len(r.ruleSets) > 0 { monitor.Start("initialize rule-set") - cacheContext = adapter.NewHTTPStartContext() + cacheContext = adapter.NewHTTPStartContext(r.ctx) var ruleSetStartGroup task.Group for i, ruleSet := range r.ruleSets { ruleSetInPlace := ruleSet diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index c29d6616ee..16a95bb645 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -3,6 +3,7 @@ package rule import ( "bytes" "context" + "crypto/tls" "io" "net" "net/http" @@ -23,6 +24,7 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" @@ -238,6 +240,10 @@ func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPSta DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return s.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) }, + TLSClientConfig: &tls.Config{ + Time: ntp.TimeFuncFromContext(s.ctx), + RootCAs: adapter.RootPoolFromContext(s.ctx), + }, }, } } From 23666a92306556aca3dfd8d39833f1fba91135e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 25 Jan 2025 20:35:10 +0800 Subject: [PATCH 0361/1272] documentation: Refactor DNS --- docs/configuration/dns/fakeip.md | 8 + docs/configuration/dns/fakeip.zh.md | 8 + docs/configuration/dns/index.md | 2 - docs/configuration/dns/index.zh.md | 2 - docs/configuration/dns/rule_action.md | 11 + docs/configuration/dns/rule_action.zh.md | 14 +- docs/configuration/dns/server/dhcp.md | 38 ++ docs/configuration/dns/server/fakeip.md | 35 ++ docs/configuration/dns/server/http3.md | 71 +++ docs/configuration/dns/server/https.md | 71 +++ docs/configuration/dns/server/index.md | 46 ++ .../dns/{server.md => server/legacy.md} | 8 + .../dns/{server.zh.md => server/legacy.zh.md} | 8 + docs/configuration/dns/server/local.md | 33 ++ docs/configuration/dns/server/predefined.md | 93 ++++ docs/configuration/dns/server/quic.md | 58 ++ docs/configuration/dns/server/tcp.md | 52 ++ docs/configuration/dns/server/tls.md | 58 ++ docs/configuration/dns/server/udp.md | 52 ++ docs/deprecated.md | 9 + docs/deprecated.zh.md | 7 + docs/migration.md | 509 +++++++++++++++++ docs/migration.zh.md | 511 +++++++++++++++++- mkdocs.yml | 14 +- 24 files changed, 1710 insertions(+), 8 deletions(-) create mode 100644 docs/configuration/dns/server/dhcp.md create mode 100644 docs/configuration/dns/server/fakeip.md create mode 100644 docs/configuration/dns/server/http3.md create mode 100644 docs/configuration/dns/server/https.md create mode 100644 docs/configuration/dns/server/index.md rename docs/configuration/dns/{server.md => server/legacy.md} (93%) rename docs/configuration/dns/{server.zh.md => server/legacy.zh.md} (92%) create mode 100644 docs/configuration/dns/server/local.md create mode 100644 docs/configuration/dns/server/predefined.md create mode 100644 docs/configuration/dns/server/quic.md create mode 100644 docs/configuration/dns/server/tcp.md create mode 100644 docs/configuration/dns/server/tls.md create mode 100644 docs/configuration/dns/server/udp.md diff --git a/docs/configuration/dns/fakeip.md b/docs/configuration/dns/fakeip.md index 63490ac197..f9204d3452 100644 --- a/docs/configuration/dns/fakeip.md +++ b/docs/configuration/dns/fakeip.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.12.0" + + Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers). + ### Structure ```json diff --git a/docs/configuration/dns/fakeip.zh.md b/docs/configuration/dns/fakeip.zh.md index 10c6dc68e7..e4d77b359d 100644 --- a/docs/configuration/dns/fakeip.zh.md +++ b/docs/configuration/dns/fakeip.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "已在 sing-box 1.12.0 废弃" + + 旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。 + ### 结构 ```json diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index 0756281dbf..43c7d573a1 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -49,8 +49,6 @@ Default domain strategy for resolving the domain names. One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. -Take no effect if `server.strategy` is set. - #### disable_cache Disable dns cache. diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index 76c07b6a2c..8ed6a854da 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -48,8 +48,6 @@ icon: material/new-box 可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 -如果设置了 `server.strategy`,则不生效。 - #### disable_cache 禁用 DNS 缓存。 diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index af19131f62..c3f8c2cb57 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [strategy](#strategy) + !!! question "Since sing-box 1.11.0" ### route @@ -10,6 +14,7 @@ icon: material/new-box { "action": "route", // default "server": "", + "strategy": "", "disable_cache": false, "rewrite_ttl": 0, "client_subnet": null @@ -24,6 +29,12 @@ icon: material/new-box Tag of target server. +#### strategy + +Set domain strategy for this query. + +One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. + #### disable_cache Disable cache and save cache in this query. diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 219a5fd757..427f8a8d77 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [strategy](#strategy) + !!! question "自 sing-box 1.11.0 起" ### route @@ -10,8 +14,8 @@ icon: material/new-box { "action": "route", // 默认 "server": "", - - // 兼容性 + + "strategy": "", "disable_cache": false, "rewrite_ttl": 0, "client_subnet": null @@ -26,6 +30,12 @@ icon: material/new-box 目标 DNS 服务器的标签。 +#### strategy + +为此查询设置域名策略。 + +可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 + #### disable_cache 在此查询中禁用缓存。 diff --git a/docs/configuration/dns/server/dhcp.md b/docs/configuration/dns/server/dhcp.md new file mode 100644 index 0000000000..b26da2a547 --- /dev/null +++ b/docs/configuration/dns/server/dhcp.md @@ -0,0 +1,38 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DHCP + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "dhcp", + "tag": "", + + "interface": "", + + // Dial Fields + } + ] + } +} +``` + +### Fields + +#### interface + +Interface name to listen on. + +Tge default interface will be used by default. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/fakeip.md b/docs/configuration/dns/server/fakeip.md new file mode 100644 index 0000000000..6d2bba4395 --- /dev/null +++ b/docs/configuration/dns/server/fakeip.md @@ -0,0 +1,35 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Fake IP + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "fakeip", + "tag": "", + + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + ] + } +} +``` + +### Fields + +#### inet4_range + +IPv4 address range for FakeIP. + +#### inet6_address + +IPv6 address range for FakeIP. diff --git a/docs/configuration/dns/server/http3.md b/docs/configuration/dns/server/http3.md new file mode 100644 index 0000000000..89f000e847 --- /dev/null +++ b/docs/configuration/dns/server/http3.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DNS over HTTP3 (DoH3) + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "h3", + "tag": "", + + "server": "", + "server_port": 443, + + "path": "", + "headers": {}, + + "tls": {}, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy H3 server" + + * The old 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. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`853` will be used by default. + +#### path + +The path of the DNS server. + +`/dns-query` will be used by default. + +#### headers + +Additional headers to be sent to the DNS server. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/https.md b/docs/configuration/dns/server/https.md new file mode 100644 index 0000000000..93dc71a4cd --- /dev/null +++ b/docs/configuration/dns/server/https.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DNS over HTTPS (DoH) + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "https", + "tag": "", + + "server": "", + "server_port": 443, + + "path": "", + "headers": {}, + + "tls": {}, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy HTTPS server" + + * The old 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. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`853` will be used by default. + +#### path + +The path of the DNS server. + +`/dns-query` will be used by default. + +#### headers + +Additional headers to be sent to the DNS server. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/index.md b/docs/configuration/dns/server/index.md new file mode 100644 index 0000000000..5393b0bdfe --- /dev/null +++ b/docs/configuration/dns/server/index.md @@ -0,0 +1,46 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [type](#type) + +# DNS Server + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "", + "tag": "" + } + ] + } +} +``` + +#### type + +The type of the DNS server. + +| Type | Format | +|-----------------|-----------------------------------------------------| +| empty (default) | [Legacy](/configuration/dns/server/legacy/) | +| `tcp` | [TCP](/configuration/dns/server/tcp/) | +| `udp` | [UDP](/configuration/dns/server/udp/) | +| `tls` | [TLS](/configuration/dns/server/tls/) | +| `https` | [HTTPS](/configuration/dns/server/https/) | +| `quic` | [QUIC](/configuration/dns/server/quic/) | +| `h3` | [HTTP/3](/configuration/dns/server/http3/) | +| `predefined` | [Predefined](/configuration/dns/server/predefined/) | +| `dhcp` | [DHCP](/configuration/dns/server/dhcp/) | +| `fakeip` | [Fake IP](/configuration/dns/server/fakeip/) | + + +#### tag + +The tag of the DNS server. diff --git a/docs/configuration/dns/server.md b/docs/configuration/dns/server/legacy.md similarity index 93% rename from docs/configuration/dns/server.md rename to docs/configuration/dns/server/legacy.md index 5ec75faa51..387d76ec26 100644 --- a/docs/configuration/dns/server.md +++ b/docs/configuration/dns/server/legacy.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.12.0" + + Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers). + !!! quote "Changes in sing-box 1.9.0" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/dns/server.zh.md b/docs/configuration/dns/server/legacy.zh.md similarity index 92% rename from docs/configuration/dns/server.zh.md rename to docs/configuration/dns/server/legacy.zh.md index 9f47054164..4bf2bcd3c6 100644 --- a/docs/configuration/dns/server.zh.md +++ b/docs/configuration/dns/server/legacy.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.12.0" + + 旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。 + !!! quote "sing-box 1.9.0 中的更改" :material-plus: [client_subnet](#client_subnet) diff --git a/docs/configuration/dns/server/local.md b/docs/configuration/dns/server/local.md new file mode 100644 index 0000000000..debcba9847 --- /dev/null +++ b/docs/configuration/dns/server/local.md @@ -0,0 +1,33 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Local + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "local", + "tag": "", + + // Dial Fields + } + ] + } +} +``` + +!!! 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. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/predefined.md b/docs/configuration/dns/server/predefined.md new file mode 100644 index 0000000000..ac75d6bbe0 --- /dev/null +++ b/docs/configuration/dns/server/predefined.md @@ -0,0 +1,93 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Predefined + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "predefined", + "tag": "", + "responses": [] + } + ] + } +} +``` + +### Fields + +#### responses + +==Required== + +List of [Response](#response-structure). + +### Response Structure + +```json +{ + "query": [], + "query_type": [], + "rcode": "", + "answer": [], + "ns": [], + "extra": [] +} +``` + +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + +### Response Fields + +#### query + +List of domain name to match. + +#### query_type + +List of query type to match. + +#### rcode + +The response code. + +| Value | Value in the legacy rcode server | Description | +|------------|----------------------------------|-----------------| +| `NOERROR` | `success` | Ok | +| `FORMERR` | `format_error` | Bad request | +| `SERVFAIL` | `server_failure` | Server failure | +| `NXDOMAIN` | `name_error` | Not found | +| `NOTIMP` | `not_implemented` | Not implemented | +| `REFUSED` | `refused` | Refused | + +`NOERROR` will be used by default. + +#### answer + +List of text DNS record to respond as answers. + +Examples: + +| Record Type | Example | +|-------------|-------------------------------| +| `A` | `localhost. IN A 127.0.0.1` | +| `AAAA` | `localhost. IN AAAA ::1` | +| `TXT` | `localhost. IN TXT \"Hello\"` | + +#### ns + +List of text DNS record to respond as name servers. + +#### extra + +List of text DNS record to respond as extra records. diff --git a/docs/configuration/dns/server/quic.md b/docs/configuration/dns/server/quic.md new file mode 100644 index 0000000000..096264fe68 --- /dev/null +++ b/docs/configuration/dns/server/quic.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DNS over QUIC (DoQ) + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "quic", + "tag": "", + + "server": "", + "server_port": 853, + + "tls": {}, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy QUIC server" + + * The old 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. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`853` will be used by default. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/tcp.md b/docs/configuration/dns/server/tcp.md new file mode 100644 index 0000000000..c0a4ceaf47 --- /dev/null +++ b/docs/configuration/dns/server/tcp.md @@ -0,0 +1,52 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# TCP + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "tcp", + "tag": "", + + "server": "", + "server_port": 53, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy TCP server" + + * The old 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. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`53` will be used by default. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/tls.md b/docs/configuration/dns/server/tls.md new file mode 100644 index 0000000000..0e3be2260c --- /dev/null +++ b/docs/configuration/dns/server/tls.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DNS over TLS (DoT) + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "tls", + "tag": "", + + "server": "", + "server_port": 853, + + "tls": {}, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy TLS server" + + * The old 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. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`853` will be used by default. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/udp.md b/docs/configuration/dns/server/udp.md new file mode 100644 index 0000000000..4ffbf4d862 --- /dev/null +++ b/docs/configuration/dns/server/udp.md @@ -0,0 +1,52 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# TCP + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "udp", + "tag": "", + + "server": "", + "server_port": 53, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy UDP server" + + * The old 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. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`53` will be used by default. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/deprecated.md b/docs/deprecated.md index 1868d95205..40bbc3a472 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -4,6 +4,15 @@ icon: material/delete-alert # Deprecated Feature List +## 1.12.0 + +#### Legacy DNS server formats + +DNS servers are refactored, +check [Migration](../migration/#migrate-to-new-dns-servers). + +Compatibility for old formats will be removed in sing-box 1.14.0. + ## 1.11.0 #### Legacy special outbounds diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 2cda6386c6..29f38742f8 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -4,6 +4,13 @@ icon: material/delete-alert # 废弃功能列表 +#### 旧的 DNS 服务器格式 + +DNS 服务器已重构, +参阅 [迁移指南](/migration/#migrate-to-new-dns-servers). + +对旧格式的兼容性将在 sing-box 1.14.0 中被移除。 + ## 1.11.0 #### 旧的特殊出站 diff --git a/docs/migration.md b/docs/migration.md index 4943bb86fe..a2818e8fae 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -2,6 +2,515 @@ icon: material/arrange-bring-forward --- +## 1.12.0 + +### Migrate to new DNS server formats + +DNS servers are refactored. + +!!! info "References" + + [DNS Server](/configuration/dns/server/) / + [Legacy DNS Server](/configuration/dns/server/legacy/) + +=== "Local" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "local" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "local" + } + ] + } + } + ``` + +=== "TCP" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "tcp://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "tcp", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "UDP" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "TLS" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "tls://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "tls", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "HTTPS" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "https://1.1.1.1/dns-query" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "https", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "QUIC" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "quic://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "quic", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "HTTP3" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "h3://1.1.1.1/dns-query" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "h3", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "DHCP" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "dhcp://auto" + }, + { + "address": "dhcp://en0" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "dhcp", + }, + { + "type": "dhcp", + "interface": "en0" + } + ] + } + } + ``` + +=== "FakeIP" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + }, + { + "address": "fakeip", + "tag": "fakeip" + } + ], + "rules": [ + { + "query_type": [ + "A", + "AAAA" + ], + "server": "fakeip" + } + ], + "fakeip": { + "enable": true, + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "fakeip", + "tag": "fakeip", + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + ], + "rules": [ + { + "query_type": [ + "A", + "AAAA" + ], + "server": "fakeip" + } + ] + } + } + ``` + +=== "RCode" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "rcode://refused" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "predefined", + "responses": [ + { + "rcode": "REFUSED" + } + ] + } + ] + } + } + ``` + +=== "Servers with domain address" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "https://dns.google/dns-query", + "address_resolver": "google" + }, + { + "tag": "google", + "address": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "https", + "server": "dns.google", + "domain_resolver": "google" + }, + { + "type": "udp", + "tag": "google", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "Servers with strategy" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1", + "strategy": "ipv4_only" + }, + { + "tag": "google", + "address": "8.8.8.8", + "strategy": "prefer_ipv6" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "udp", + "tag": "google", + "server": "8.8.8.8" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google", + "strategy": "prefer_ipv6" + } + ], + "strategy": "ipv4_only" + } + } + ``` + +=== "Servers with client subnet" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + }, + { + "tag": "google", + "address": "8.8.8.8", + "client_subnet": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "udp", + "tag": "google", + "server": "8.8.8.8" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google", + "client_subnet": "1.1.1.1" + } + ] + } + } + ``` + ## 1.11.0 ### Migrate legacy special outbounds to rule actions diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 32be560427..3cc45c2fb3 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -2,6 +2,515 @@ icon: material/arrange-bring-forward --- +## 1.12.0 + +### 迁移到新的 DNS 服务器格式 + +DNS 服务器已经重构。 + +!!! info "饮用" + + [DNS 服务器](/configuration/dns/server/) / + [旧 DNS 服务器](/configuration/dns/server/legacy/) + +=== "Local" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "local" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "local" + } + ] + } + } + ``` + +=== "TCP" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "tcp://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "tcp", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "UDP" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "TLS" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "tls://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "tls", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "HTTPS" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "https://1.1.1.1/dns-query" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "https", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "QUIC" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "quic://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "quic", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "HTTP3" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "h3://1.1.1.1/dns-query" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "h3", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "DHCP" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "dhcp://auto" + }, + { + "address": "dhcp://en0" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "dhcp", + }, + { + "type": "dhcp", + "interface": "en0" + } + ] + } + } + ``` + +=== "FakeIP" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + }, + { + "address": "fakeip", + "tag": "fakeip" + } + ], + "rules": [ + { + "query_type": [ + "A", + "AAAA" + ], + "server": "fakeip" + } + ], + "fakeip": { + "enable": true, + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "fakeip", + "tag": "fakeip", + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + ], + "rules": [ + { + "query_type": [ + "A", + "AAAA" + ], + "server": "fakeip" + } + ] + } + } + ``` + +=== "RCode" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "rcode://refused" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "predefined", + "responses": [ + { + "rcode": "REFUSED" + } + ] + } + ] + } + } + ``` + +=== "带有域名地址的服务器" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "https://dns.google/dns-query", + "address_resolver": "google" + }, + { + "tag": "google", + "address": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "https", + "server": "dns.google", + "domain_resolver": "google" + }, + { + "type": "udp", + "tag": "google", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "带有域策略的服务器" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1", + "strategy": "ipv4_only" + }, + { + "tag": "google", + "address": "8.8.8.8", + "strategy": "prefer_ipv6" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "udp", + "tag": "google", + "server": "8.8.8.8" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google", + "strategy": "prefer_ipv6" + } + ], + "strategy": "ipv4_only" + } + } + ``` + +=== "带有客户端子网的服务器" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + }, + { + "tag": "google", + "address": "8.8.8.8", + "client_subnet": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "udp", + "tag": "google", + "server": "8.8.8.8" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google", + "client_subnet": "1.1.1.1" + } + ] + } + } + ``` + ## 1.11.0 ### 迁移旧的特殊出站到规则动作 @@ -129,7 +638,7 @@ icon: material/arrange-bring-forward } ``` -=== ":material-card-multiple: New" +=== ":material-card-multiple: 新的" ```json { diff --git a/mkdocs.yml b/mkdocs.yml index 4854fa4a60..acab5853ac 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,7 +80,19 @@ nav: - configuration/log/index.md - DNS: - configuration/dns/index.md - - DNS Server: configuration/dns/server.md + - DNS Server: + - configuration/dns/server/index.md + - Legacy: configuration/dns/server/legacy.md + - Local: configuration/dns/server/local.md + - TCP: configuration/dns/server/tcp.md + - UDP: configuration/dns/server/udp.md + - TLS: configuration/dns/server/tls.md + - QUIC: configuration/dns/server/quic.md + - HTTPS: configuration/dns/server/https.md + - HTTP3: configuration/dns/server/http3.md + - Predefined: configuration/dns/server/predefined.md + - DHCP: configuration/dns/server/dhcp.md + - FakeIP: configuration/dns/server/fakeip.md - DNS Rule: configuration/dns/rule.md - DNS Rule Action: configuration/dns/rule_action.md - FakeIP: configuration/dns/fakeip.md From fa6f7d396efed54b636d74ef28b49ed61e5fdfbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 29 Jan 2025 13:13:33 +0800 Subject: [PATCH 0362/1272] documentation: Outbound domain resolver --- docs/configuration/dns/rule.md | 10 +++- docs/configuration/dns/rule.zh.md | 10 +++- docs/configuration/route/geoip.md | 6 +- docs/configuration/route/geoip.zh.md | 6 +- docs/configuration/route/geosite.md | 6 +- docs/configuration/route/geosite.zh.md | 6 +- docs/configuration/route/index.md | 26 +++++++-- docs/configuration/route/index.zh.md | 16 +++++- docs/configuration/shared/dial.md | 48 +++++++++++++--- docs/configuration/shared/dial.zh.md | 44 ++++++++++++--- docs/deprecated.md | 6 ++ docs/deprecated.zh.md | 8 ++- docs/migration.md | 77 +++++++++++++++++++++++++- docs/migration.zh.md | 67 ++++++++++++++++++++++ 14 files changed, 297 insertions(+), 39 deletions(-) diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 1f04b2999d..3039bffce3 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -1,7 +1,11 @@ --- -icon: material/new-box +icon: material/alert-decagram --- +!!! quote "Changes in sing-box 1.12.0" + + :material-delete-clock: [outbound](#outbound) + !!! quote "Changes in sing-box 1.11.0" :material-plus: [action](#action) @@ -395,6 +399,10 @@ Invert match result. #### outbound +!!! failure "Deprecated in sing-box 1.12.0" + + `outbound` rule items are deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver). + Match outbound. `any` can be used as a value to match any outbound. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index bf0a03e23a..94ca453509 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -1,7 +1,11 @@ --- -icon: material/new-box +icon: material/alert-decagram --- +!!! quote "sing-box 1.12.0 中的更改" + + :material-delete-clock: [outbound](#outbound) + !!! quote "sing-box 1.11.0 中的更改" :material-plus: [action](#action) @@ -395,6 +399,10 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. #### outbound +!!! failure "已在 sing-box 1.12.0 废弃" + + `outbound` 规则项已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver)。 + 匹配出站。 `any` 可作为值用于匹配任意出站。 diff --git a/docs/configuration/route/geoip.md b/docs/configuration/route/geoip.md index a045574aa0..62162cdf8d 100644 --- a/docs/configuration/route/geoip.md +++ b/docs/configuration/route/geoip.md @@ -1,10 +1,10 @@ --- -icon: material/delete-clock +icon: material/note-remove --- -!!! failure "Deprecated in sing-box 1.8.0" +!!! failure "Removed in sing-box 1.12.0" - GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). ### Structure diff --git a/docs/configuration/route/geoip.zh.md b/docs/configuration/route/geoip.zh.md index eb7bbe2d10..3d63a3b70c 100644 --- a/docs/configuration/route/geoip.zh.md +++ b/docs/configuration/route/geoip.zh.md @@ -1,10 +1,10 @@ --- -icon: material/delete-clock +icon: material/note-remove --- -!!! failure "已在 sing-box 1.8.0 废弃" +!!! failure "已在 sing-box 1.12.0 中被移除" - GeoIP 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。 + GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。 ### 结构 diff --git a/docs/configuration/route/geosite.md b/docs/configuration/route/geosite.md index 9a1b9dce03..830d115803 100644 --- a/docs/configuration/route/geosite.md +++ b/docs/configuration/route/geosite.md @@ -1,10 +1,10 @@ --- -icon: material/delete-clock +icon: material/note-remove --- -!!! failure "Deprecated in sing-box 1.8.0" +!!! failure "Removed in sing-box 1.12.0" - Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). ### Structure diff --git a/docs/configuration/route/geosite.zh.md b/docs/configuration/route/geosite.zh.md index 7cec5b2086..9afea3d7e1 100644 --- a/docs/configuration/route/geosite.zh.md +++ b/docs/configuration/route/geosite.zh.md @@ -1,10 +1,10 @@ --- -icon: material/delete-clock +icon: material/note-remove --- -!!! failure "已在 sing-box 1.8.0 废弃" +!!! failure "已在 sing-box 1.12.0 中被移除" - Geosite 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。 + Geosite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。 ### 结构 diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 1a1919e94b..1fc9bfd231 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -1,9 +1,15 @@ --- -icon: material/new-box +icon: material/alert-decagram --- # Route +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [default_domain_resolver](#default_domain_resolver) + :material-note-remove: [geoip](#geoip) + :material-note-remove: [geosite](#geosite) + !!! quote "Changes in sing-box 1.11.0" :material-plus: [default_network_strategy](#default_network_strategy) @@ -22,8 +28,6 @@ icon: material/new-box ```json { "route": { - "geoip": {}, - "geosite": {}, "rules": [], "rule_set": [], "final": "", @@ -31,10 +35,16 @@ icon: material/new-box "override_android_vpn": false, "default_interface": "", "default_mark": 0, + "default_domain_resolver": "", // or {} "default_network_strategy": "", "default_network_type": [], "default_fallback_network_type": [], - "default_fallback_delay": "" + "default_fallback_delay": "", + + // Removed + + "geoip": {}, + "geosite": {} } } ``` @@ -97,6 +107,14 @@ Set routing mark by default. Takes no effect if `outbound.routing_mark` is set. +#### default_domain_resolver + +!!! question "Since sing-box 1.12.0" + +See [Dial Fields](/configuration/shared/dial/#domain_resolver) for details. + +Can be overrides by `outbound.domain_resolver`. + #### default_network_strategy !!! question "Since sing-box 1.11.0" diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md index a224ddc4c5..3748a522ca 100644 --- a/docs/configuration/route/index.zh.md +++ b/docs/configuration/route/index.zh.md @@ -1,9 +1,15 @@ --- -icon: material/new-box +icon: material/alert-decagram --- # 路由 +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [default_domain_resolver](#default_domain_resolver) + :material-note-remove: [geoip](#geoip) + :material-note-remove: [geosite](#geosite) + !!! quote "sing-box 1.11.0 中的更改" :material-plus: [network_strategy](#network_strategy) @@ -100,6 +106,14 @@ icon: material/new-box 如果设置了 `outbound.routing_mark` 设置,则不生效。 +#### default_domain_resolver + +!!! question "自 sing-box 1.12.0 起" + +详情参阅 [拨号字段](/configuration/shared/dial/#domain_resolver)。 + +可以被 `outbound.domain_resolver` 覆盖。 + #### network_strategy !!! question "自 sing-box 1.11.0 起" diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 5f654ae247..e852d57ba7 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [domain_resolver](#domain_resolver) + :material-delete-clock: [domain_strategy](#domain_strategy) + !!! quote "Changes in sing-box 1.11.0" :material-plus: [network_strategy](#network_strategy) @@ -23,11 +28,14 @@ icon: material/new-box "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, - "domain_strategy": "prefer_ipv6", + "domain_resolver": "", // or {} "network_strategy": "default", "network_type": [], "fallback_network_type": [], - "fallback_delay": "300ms" + "fallback_delay": "300ms", + + // Deprecated + "domain_strategy": "prefer_ipv6" } ``` @@ -92,16 +100,22 @@ decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". -#### domain_strategy +#### domain_resolver -Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. +!!! warning "" -If set, the requested domain name will be resolved to IP before connect. + `outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0. -| Outbound | Effected domains | Fallback Value | -|----------|--------------------------|-------------------------------------------| -| `direct` | Domain in request | Take `inbound.domain_strategy` if not set | -| others | Domain in server address | / | +Set domain resolver to use for resolving domain names. + +This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field. + +Setting this option directly to a string is equivalent to setting `server` of this options. + +| Outbound/Endpoints | Effected domains | +|--------------------|--------------------------| +| `direct` | Domain in request | +| others | Domain in server address | #### network_strategy @@ -171,3 +185,19 @@ back to other interfaces. Only take effect when `domain_strategy` or `network_strategy` is set. `300ms` is used by default. + +#### domain_strategy + +!!! failure "Deprecated in sing-box 1.12.0" + + `domain_strategy` is merged to [domain_resolver](#domain_resolver) in sing-box 1.12.0. + +Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. + +If set, the requested domain name will be resolved to IP before connect. + +| Outbound | Effected domains | Fallback Value | +|----------|--------------------------|-------------------------------------------| +| `direct` | Domain in request | Take `inbound.domain_strategy` if not set | +| others | Domain in server address | / | + diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index ab83c44c69..66e1436ee8 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [domain_resolver](#domain_resolver) + :material-delete-clock: [domain_strategy](#domain_strategy) + !!! quote "sing-box 1.11.0 中的更改" :material-plus: [network_strategy](#network_strategy) @@ -23,11 +28,15 @@ icon: material/new-box "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, - "domain_strategy": "prefer_ipv6", + "domain_resolver": "", // 或 {} "network_strategy": "", "network_type": [], "fallback_network_type": [], - "fallback_delay": "300ms" + "fallback_delay": "300ms", + + // 废弃的 + + "domain_strategy": "prefer_ipv6" } ``` @@ -90,16 +99,22 @@ icon: material/new-box 持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。 有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。 -#### domain_strategy +#### domain_resolver -可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 +!!! warning "" -如果设置,域名将在请求发出之前解析为 IP。 + `outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项。 -| 出站 | 受影响的域名 | 默认回退值 | -|----------|-----------|---------------------------| -| `direct` | 请求中的域名 | `inbound.domain_strategy` | -| others | 服务器地址中的域名 | / | +用于设置解析域名的域名解析器。 + +此选项的格式与 [路由 DNS 规则动作](/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。 + +若直接将此选项设置为字符串,则等同于设置该选项的 `server` 字段。 + +| 出站/端点 | 受影响的域名 | +|----------------|---------------------------| +| `direct` | 请求中的域名 | +| 其他类型 | 服务器地址中的域名 | #### network_strategy @@ -160,3 +175,14 @@ icon: material/new-box 仅当 `domain_strategy` 或 `network_strategy` 已设置时生效。 默认使用 `300ms`。 + +#### domain_strategy + +可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 + +如果设置,域名将在请求发出之前解析为 IP。 + +| 出站 | 受影响的域名 | 默认回退值 | +|----------|-----------|---------------------------| +| `direct` | 请求中的域名 | `inbound.domain_strategy` | +| others | 服务器地址中的域名 | / | \ No newline at end of file diff --git a/docs/deprecated.md b/docs/deprecated.md index 40bbc3a472..128c0709d6 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -13,6 +13,12 @@ check [Migration](../migration/#migrate-to-new-dns-servers). Compatibility for old formats will be removed in sing-box 1.14.0. +#### `outbound` DNS rule item + +Legacy `outbound` DNS rules are deprecated +and can be replaced by dial fields, +check [Migration](../migration/#migrate-outbound-dns-rule-items-to-domain-resolver). + ## 1.11.0 #### Legacy special outbounds diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 29f38742f8..2c0f557850 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -11,6 +11,12 @@ DNS 服务器已重构, 对旧格式的兼容性将在 sing-box 1.14.0 中被移除。 +#### `outbound` DNS 规则项 + +旧的 `outbound` DNS 规则已废弃, +且可被拨号字段代替, +参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver). + ## 1.11.0 #### 旧的特殊出站 @@ -27,7 +33,7 @@ DNS 服务器已重构, 旧字段将在 sing-box 1.13.0 中被移除。 -#### direct 出站中的目标地址覆盖字段 +#### direct 出站中的目标地址覆盖字段 direct 出站中的目标地址覆盖字段(`override_address` / `override_port`)已废弃且可以通过规则动作替代, 参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。 diff --git a/docs/migration.md b/docs/migration.md index a2818e8fae..62e09e71a6 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -6,7 +6,7 @@ icon: material/arrange-bring-forward ### Migrate to new DNS server formats -DNS servers are refactored. +DNS servers are refactored for better performance and scalability. !!! info "References" @@ -511,6 +511,81 @@ DNS servers are refactored. } ``` +### Migrate outbound DNS rule items to domain resolver + +The legacy outbound DNS rules are deprecated and can be replaced by new domain resolver options. + +!!! info "References" + + [DNS rule](/configuration/dns/rule/#outbound) / + [Dial Fields](/configuration/shared/dial/#domain_resolver) / + [Route](/configuration/route/#domain_resolver) + +=== ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "local", + "tag": "local" + } + ], + "rules": [ + { + "outbound": "any", + "server": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080 + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_resolver": { + "server": "local", + "rewrite_tll": 60, + "client_subnet": "1.1.1.1" + }, + // or "domain_resolver": "local", + } + ], + + // or + + "route": { + "default_domain_resolver": { + "server": "local", + "rewrite_tll": 60, + "client_subnet": "1.1.1.1" + } + } + } + ``` + ## 1.11.0 ### Migrate legacy special outbounds to rule actions diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 3cc45c2fb3..1204466938 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -511,6 +511,73 @@ DNS 服务器已经重构。 } ``` +### 迁移 outbound DNS 规则项到域解析选项 + +旧的 `outbound` DNS 规则已废弃,且可新的域解析选项代替。 + +!!! info "参考" + + [DNS 规则](/configuration/dns/rule/#outbound) / + [拨号字段](/configuration/shared/dial/#domain_resolver) / + [路由](/configuration/route/#default_domain_resolver) + +=== ":material-card-remove: 废弃的" + + ```json + { + "dns": { + "servers": [ + { + "address": "local", + "tag": "local" + } + ], + "rules": [ + { + "outbound": "any", + "server": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080 + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_resolver": "local", + } + ], + "route": { + "default_domain_resolver": { + "server": "local", + "rewrite_tll": 60, + "client_subnet": "1.1.1.1" + } + } + } + ``` + ## 1.11.0 ### 迁移旧的特殊出站到规则动作 From 061276902b2c750be0ed8f7b55a83b5683e4c489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 30 Jan 2025 13:29:16 +0800 Subject: [PATCH 0363/1272] documentation: TLS fragment --- docs/configuration/route/rule_action.md | 31 +++++++++++++++++++++- docs/configuration/route/rule_action.zh.md | 27 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 31256e251e..567b9eb69c 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [tls_fragment](#tls_fragment) + :material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay) + ## Final actions ### route @@ -81,7 +86,9 @@ Not available when `method` is set to drop. "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false, - "udp_timeout": "" + "udp_timeout": "", + "tls_fragment": false, + "tls_fragment_fallback_delay": "" } ``` @@ -148,6 +155,28 @@ If no protocol is sniffed, the following ports will be recognized as protocols b | 443 | `quic` | | 3478 | `stun` | +#### tls_fragment + +!!! question "Since sing-box 1.12.0" + +Fragment TLS handshakes to bypass firewalls. + +This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship. + +Since it is not designed for performance, it should not be applied to all connections, but only to server names that are known to be blocked. + +On Linux, Apple platforms, (administrator privileges required) Windows, the wait time can be automatically detected, otherwise it will fall back to waiting for a fixed time specified by `tls_fragment_fallback_delay`. + +In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, because the target is considered to be local or behind a transparent proxy. + +#### tls_fragment_fallback_delay + +!!! question "Since sing-box 1.12.0" + +The fallback value used when TLS segmentation cannot automatically determine the wait time. + +`500ms` is used by default. + ### sniff ```json diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 544918d4fb..a8eca8a24d 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [tls_fragment](#tls_fragment) + :material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay) + ## 最终动作 ### route @@ -146,6 +151,28 @@ UDP 连接超时时间。 | 443 | `quic` | | 3478 | `stun` | +#### tls_fragment + +!!! question "自 sing-box 1.12.0 起" + +通过分段 TLS 握手数据包来绕过防火墙检测。 + +此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。 + +由于它不是为性能设计的,不应被应用于所有连接,而仅应用于已知被阻止的服务器名称。 + +在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。 + +此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。 + +#### tls_fragment_fallback_delay + +!!! question "自 sing-box 1.12.0 起" + +当 TLS 分片功能无法自动判定等待时间时使用的回退值。 + +默认使用 `500ms`。 + ### sniff ```json From e809623ec9ddafb071919ded955dbe95e4c5545e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 30 Jan 2025 14:27:25 +0800 Subject: [PATCH 0364/1272] documentation: Certificate store --- docs/configuration/certificate/index.md | 54 +++++++++++++++++++++++++ mkdocs.yml | 4 +- 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 docs/configuration/certificate/index.md diff --git a/docs/configuration/certificate/index.md b/docs/configuration/certificate/index.md new file mode 100644 index 0000000000..698fec70eb --- /dev/null +++ b/docs/configuration/certificate/index.md @@ -0,0 +1,54 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Certificate + +### Structure + +```json +{ + "store": "", + "certificate": [], + "certificate_path": [], + "certificate_directory_path": [] +} +``` + +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + +### Fields + +#### store + +The default X509 trusted CA certificate list. + +| 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 | + +#### certificate + +The certificate line array to trust, in PEM format. + +#### certificate_path + +!!! note "" + + Will be automatically reloaded if file modified. + +The paths to certificates to trust, in PEM format. + +#### certificate_directory_path + +!!! note "" + + Will be automatically reloaded if file modified. + +The directory path to search for certificates to trust,in PEM format. diff --git a/mkdocs.yml b/mkdocs.yml index acab5853ac..5758525a79 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -96,8 +96,8 @@ nav: - DNS Rule: configuration/dns/rule.md - DNS Rule Action: configuration/dns/rule_action.md - FakeIP: configuration/dns/fakeip.md - - NTP: - - configuration/ntp/index.md + - NTP: configuration/ntp/index.md + - Certificate: configuration/certificate/index.md - Route: - configuration/route/index.md - GeoIP: configuration/route/geoip.md From c830e9a634485c3c3884a68d11dfa09fb21eaa2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 30 Jan 2025 14:30:37 +0800 Subject: [PATCH 0365/1272] documentation: Remove outdated icons --- docs/configuration/experimental/clash-api.md | 4 ---- docs/configuration/experimental/clash-api.zh.md | 4 ---- docs/configuration/route/sniff.md | 4 ---- docs/configuration/route/sniff.zh.md | 4 ---- docs/configuration/rule-set/adguard.md | 4 ---- docs/configuration/rule-set/adguard.zh.md | 4 ---- docs/configuration/rule-set/index.md | 4 ---- docs/configuration/rule-set/index.zh.md | 4 ---- docs/configuration/shared/tls.md | 4 ---- docs/configuration/shared/tls.zh.md | 4 ---- 10 files changed, 40 deletions(-) diff --git a/docs/configuration/experimental/clash-api.md b/docs/configuration/experimental/clash-api.md index d471ea876d..a8908940e1 100644 --- a/docs/configuration/experimental/clash-api.md +++ b/docs/configuration/experimental/clash-api.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.10.0" :material-plus: [access_control_allow_origin](#access_control_allow_origin) diff --git a/docs/configuration/experimental/clash-api.zh.md b/docs/configuration/experimental/clash-api.zh.md index 8193cd8781..f86b2cac9c 100644 --- a/docs/configuration/experimental/clash-api.zh.md +++ b/docs/configuration/experimental/clash-api.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.10.0 中的更改" :material-plus: [access_control_allow_origin](#access_control_allow_origin) diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index 3880f79025..24bc8ef602 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.10.0" :material-plus: QUIC client type detect support for QUIC diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index 4efa453814..cd58251733 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.10.0 中的更改" :material-plus: QUIC 的 客户端类型探测支持 diff --git a/docs/configuration/rule-set/adguard.md b/docs/configuration/rule-set/adguard.md index bda7379451..c8bd32fa87 100644 --- a/docs/configuration/rule-set/adguard.md +++ b/docs/configuration/rule-set/adguard.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.10.0" sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box, diff --git a/docs/configuration/rule-set/adguard.zh.md b/docs/configuration/rule-set/adguard.zh.md index 026f2e0bea..82773280c6 100644 --- a/docs/configuration/rule-set/adguard.zh.md +++ b/docs/configuration/rule-set/adguard.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.10.0 起" sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box, diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index ea27860841..73ec7b859f 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.10.0" :material-plus: `type: inline` diff --git a/docs/configuration/rule-set/index.zh.md b/docs/configuration/rule-set/index.zh.md index 17fdfa5aa9..eac519539c 100644 --- a/docs/configuration/rule-set/index.zh.md +++ b/docs/configuration/rule-set/index.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.10.0 中的更改" :material-plus: `type: inline` diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index 7a6c9d5e08..f2a716a7e0 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - !!! quote "Changes in sing-box 1.10.0" :material-alert-decagram: [utls](#utls) diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index b254e08389..6cfc5f36e4 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - !!! quote "sing-box 1.10.0 中的更改" :material-alert-decagram: [utls](#utls) From 0f6c417c3c4bfb70f8ea6ad26937e179e6b74f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 12 Feb 2025 08:31:21 +0800 Subject: [PATCH 0366/1272] Build legacy binaries with latest Go --- .github/setup_legacy_go.sh | 25 +++++++++++++++++++++++++ .github/workflows/build.yml | 28 ++++++++++++++++++---------- .goreleaser.yaml | 5 ++--- 3 files changed, 45 insertions(+), 13 deletions(-) create mode 100755 .github/setup_legacy_go.sh diff --git a/.github/setup_legacy_go.sh b/.github/setup_legacy_go.sh new file mode 100755 index 0000000000..3f1a31ad65 --- /dev/null +++ b/.github/setup_legacy_go.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +VERSION="1.23.6" + +mkdir -p $HOME/go +cd $HOME/go +wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz" +tar -xzf "go${VERSION}.linux-amd64.tar.gz" +mv go go_legacy +cd go_legacy + +# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557 +# this patch file only works on golang1.23.x +# that means after golang1.24 release it must be changed +# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/ +# revert: +# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng" +# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7" +# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround" +# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries" + +curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1 +curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1 +curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1 +curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 921929fe23..322ed88d42 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -94,7 +94,6 @@ jobs: - { os: windows, arch: arm64 } - { os: darwin, arch: amd64 } - - { os: darwin, arch: amd64, legacy_go: true } - { os: darwin, arch: arm64 } - { os: android, arch: arm64, ndk: "aarch64-linux-android21" } @@ -106,16 +105,28 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 with: fetch-depth: 0 - - name: Setup Go - if: matrix.legacy_go - uses: actions/setup-go@v5 - with: - go-version: ~1.20 - name: Setup Go if: ${{ ! matrix.legacy_go }} uses: actions/setup-go@v5 with: go-version: ^1.24 + - name: Cache Legacy Go + if: matrix.require_legacy_go + id: cache-legacy-go + uses: actions/cache@v4 + with: + path: | + ~/go/go_legacy + key: go_legacy_1236 + - name: Setup Legacy Go + if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true' + run: |- + .github/setup_legacy_go.sh + - name: Setup Legacy Go 2 + if: matrix.legacy_go + run: |- + echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV + echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV - name: Setup Android NDK if: matrix.os == 'android' uses: nttld/setup-ndk@v1 @@ -129,10 +140,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api' - if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then - TAGS="${TAGS},with_ech" - fi + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api,with_tailscale' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build if: matrix.os != 'android' diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 87a2f45806..069e5eac77 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -51,12 +51,11 @@ builds: - with_clash_api env: - CGO_ENABLED=0 - - GOROOT={{ .Env.GOPATH }}/go1.20.14 - tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go" + - GOROOT={{ .Env.GOPATH }}/go_legacy + tool: "{{ .Env.GOPATH }}/go_legacy/bin/go" targets: - windows_amd64_v1 - windows_386 - - darwin_amd64_v1 - id: android <<: *template env: From 6f793a0273cc4252ff78af597ee2234ceb36c1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 6 Apr 2025 19:14:41 +0800 Subject: [PATCH 0367/1272] Add Tailscale endpoint --- .goreleaser.fury.yaml | 1 + .goreleaser.yaml | 4 + Makefile | 14 +- cmd/internal/build_libbox/main.go | 24 +- common/dialer/dialer.go | 3 + constant/dns.go | 1 + constant/proxy.go | 1 + dns/router.go | 3 - docs/configuration/dns/server/tailscale.md | 83 ++++ docs/configuration/endpoint/tailscale.md | 99 ++++ docs/installation/build-from-source.md | 1 + docs/installation/build-from-source.zh.md | 1 + go.mod | 86 ++-- go.sum | 206 ++++++--- include/registry.go | 2 + include/tailscale.go | 17 + include/tailscale_stub.go | 27 ++ mkdocs.yml | 2 + option/tailscale.go | 24 + protocol/tailscale/dns_transport.go | 320 +++++++++++++ protocol/tailscale/endpoint.go | 496 +++++++++++++++++++++ protocol/tailscale/protect_android.go | 16 + protocol/tailscale/protect_nonandroid.go | 8 + 23 files changed, 1339 insertions(+), 100 deletions(-) create mode 100644 docs/configuration/dns/server/tailscale.md create mode 100644 docs/configuration/endpoint/tailscale.md create mode 100644 include/tailscale.go create mode 100644 include/tailscale_stub.go create mode 100644 option/tailscale.go create mode 100644 protocol/tailscale/dns_transport.go create mode 100644 protocol/tailscale/endpoint.go create mode 100644 protocol/tailscale/protect_android.go create mode 100644 protocol/tailscale/protect_nonandroid.go diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml index 4761a76af5..8f3835f4ab 100644 --- a/.goreleaser.fury.yaml +++ b/.goreleaser.fury.yaml @@ -19,6 +19,7 @@ builds: - with_reality_server - with_acme - with_clash_api + - with_tailscale env: - CGO_ENABLED=0 targets: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 069e5eac77..86090bb779 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -21,8 +21,10 @@ builds: - with_reality_server - with_acme - with_clash_api + - with_tailscale env: - CGO_ENABLED=0 + - GOTOOLCHAIN=local targets: - linux_386 - linux_amd64_v1 @@ -49,6 +51,7 @@ builds: - with_reality_server - with_acme - with_clash_api + - with_tailscale env: - CGO_ENABLED=0 - GOROOT={{ .Env.GOPATH }}/go_legacy @@ -60,6 +63,7 @@ builds: <<: *template env: - CGO_ENABLED=1 + - GOTOOLCHAIN=local overrides: - goos: android goarch: arm diff --git a/Makefile b/Makefile index 868e47bf74..7d3eb324ee 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_acme TAGS_GO121 = with_ech -TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121) +TAGS_GO123 = with_tailscale +TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121),$(TAGS_GO123) TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server GOHOSTOS = $(shell go env GOHOSTOS) @@ -17,14 +18,17 @@ PREFIX ?= $(shell go env GOPATH) .PHONY: test release docs build build: + export GOTOOLCHAIN=local && \ go build $(MAIN_PARAMS) $(MAIN) ci_build_go120: - go build $(PARAMS) $(MAIN) + export GOTOOLCHAIN=local && \ + go build $(PARAMS) $(MAIN) && \ go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN) ci_build: - go build $(PARAMS) $(MAIN) + export GOTOOLCHAIN=local && \ + go build $(PARAMS) $(MAIN) && \ go build $(MAIN_PARAMS) $(MAIN) generate_completions: @@ -230,8 +234,8 @@ lib: go run ./cmd/internal/build_libbox -target ios lib_install: - go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.4 - go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.4 + go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6 + go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6 docs: venv/bin/mkdocs serve diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index e37a9ff4dc..8b251fee16 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -45,6 +45,7 @@ var ( debugFlags []string sharedTags []string iosTags []string + memcTags []string debugTags []string ) @@ -60,6 +61,7 @@ func init() { sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api") iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack") + memcTags = append(memcTags, "with_tailscale") debugTags = append(debugTags, "debug") } @@ -99,18 +101,19 @@ func buildAndroid() { "-javapkg=io.nekohasekai", "-libname=box", } + if !debugEnabled { args = append(args, sharedFlags...) } else { args = append(args, debugFlags...) } - args = append(args, "-tags") - if !debugEnabled { - args = append(args, strings.Join(sharedTags, ",")) - } else { - args = append(args, strings.Join(append(sharedTags, debugTags...), ",")) + tags := append(sharedTags, memcTags...) + if debugEnabled { + tags = append(tags, debugTags...) } + + args = append(args, "-tags", strings.Join(tags, ",")) args = append(args, "./experimental/libbox") command := exec.Command(build_shared.GoBinPath+"/gomobile", args...) @@ -148,7 +151,9 @@ func buildApple() { "-v", "-target", bindTarget, "-libname=box", + "-tags-macos=" + strings.Join(memcTags, ","), } + if !debugEnabled { args = append(args, sharedFlags...) } else { @@ -156,12 +161,11 @@ func buildApple() { } tags := append(sharedTags, iosTags...) - args = append(args, "-tags") - if !debugEnabled { - args = append(args, strings.Join(tags, ",")) - } else { - args = append(args, strings.Join(append(tags, debugTags...), ",")) + if debugEnabled { + tags = append(tags, debugTags...) } + + args = append(args, "-tags", strings.Join(tags, ",")) args = append(args, "./experimental/libbox") command := exec.Command(build_shared.GoBinPath+"/gomobile", args...) diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 812e557557..0efc7e9d43 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -22,6 +22,7 @@ type Options struct { RemoteIsDomain bool DirectResolver bool ResolverOnDetour bool + NewDialer bool } // TODO: merge with NewWithOptions @@ -100,6 +101,8 @@ func NewWithOptions(options Options) (N.Dialer, error) { } dnsQueryOptions.Transport = transport resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) + } else if options.NewDialer { + return nil, E.New("missing domain resolver for domain server address") } else { deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) } diff --git a/constant/dns.go b/constant/dns.go index 99461a270b..0a444ff0cf 100644 --- a/constant/dns.go +++ b/constant/dns.go @@ -27,6 +27,7 @@ const ( DNSTypePreDefined = "predefined" DNSTypeFakeIP = "fakeip" DNSTypeDHCP = "dhcp" + DNSTypeTailscale = "tailscale" ) const ( diff --git a/constant/proxy.go b/constant/proxy.go index 3197de6052..45e79f8455 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -23,6 +23,7 @@ const ( TypeVLESS = "vless" TypeTUIC = "tuic" TypeHysteria2 = "hysteria2" + TypeTailscale = "tailscale" ) const ( diff --git a/dns/router.go b/dns/router.go index f6f42a3152..f79aae8517 100644 --- a/dns/router.go +++ b/dns/router.go @@ -174,7 +174,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, options.ClientSubnet = legacyTransport.LegacyClientSubnet() } } - r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) return transport, currentRule, currentRuleIndex case *R.RuleActionDNSRouteOptions: if action.Strategy != C.DomainStrategyAsIS { @@ -189,9 +188,7 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, if action.ClientSubnet.IsValid() { options.ClientSubnet = action.ClientSubnet } - r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) case *R.RuleActionReject: - r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) return nil, currentRule, currentRuleIndex } } diff --git a/docs/configuration/dns/server/tailscale.md b/docs/configuration/dns/server/tailscale.md new file mode 100644 index 0000000000..71744858ee --- /dev/null +++ b/docs/configuration/dns/server/tailscale.md @@ -0,0 +1,83 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Tailscale + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "tailscale", + "tag": "", + + "endpoint": "ts-ep", + "accept_default_resolvers": false + } + ] + } +} +``` + +### Fields + +#### endpoint + +==Required== + +The tag of the Tailscale endpoint. + +#### accept_default_resolvers + +Indicates whether default DNS resolvers should be accepted for fallback queries in addition to MagicDNS。 + +if not enabled, NXDOMAIN will be returned for non-Tailscale domain queries. + +### Examples + +=== "MagicDNS only" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "tailscale", + "tag": "ts", + "endpoint": "ts-ep" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "ts" + } + ] + } + } + ``` + +=== "Use as global DNS" + + ```json + { + "dns": { + "servers": [ + { + "type": "tailscale", + "endpoint": "ts-ep", + "accept_default_resolvers": true + } + ] + } + } + ``` \ No newline at end of file diff --git a/docs/configuration/endpoint/tailscale.md b/docs/configuration/endpoint/tailscale.md new file mode 100644 index 0000000000..298b68cebf --- /dev/null +++ b/docs/configuration/endpoint/tailscale.md @@ -0,0 +1,99 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +### Structure + +```json +{ + "type": "tailscale", + "tag": "ts-ep", + "state_directory": "", + "auth_key": "", + "control_url": "", + "ephemeral": false, + "hostname": "", + "exit_node": "", + "exit_node_allow_lan_access": false, + "advertise_routes": [], + "advertise_exit_node": false, + "udp_timeout": "5m", + + ... // Dial Fields +} +``` + +### Fields + +#### state_directory + +The directory where the Tailscale state is stored. + +`tailscale` is used by default. + +Example: `$HOME/.tailscale` + +#### auth_key + +!!! note + + Auth key is not required. By default, sing-box will log the login URL (or popup a notification on graphical clients). + +The auth key to create the node. If the node is already created (from state previously stored), then this field is not +used. + +#### control_url + +The coordination server URL. + +`https://controlplane.tailscale.com` is used by default. + +#### ephemeral + +Indicates whether the instance should register as an Ephemeral node (https://tailscale.com/s/ephemeral-nodes). + +#### hostname + +The hostname of the node. + +System hostname is used by default. + +Example: `localhost` + +#### exit_node + +The exit node name or IP address to use. + +#### exit_node_allow_lan_access + +!!! note + + When the exit node does not have a corresponding advertised route, private traffics cannot be routed to the exit node even if `exit_node_allow_lan_access is` set. + +Indicates whether locally accessible subnets should be routed directly or via the exit node. + +#### advertise_routes + +CIDR prefixes to advertise into the Tailscale network as reachable through the current node. + +Example: `["192.168.1.1/24"]` + +#### advertise_exit_node + +Indicates whether the node should advertise itself as an exit node. + +#### udp_timeout + +UDP NAT expiration time. + +`5m` will be used by default. + +### Dial Fields + +!!! note + + Dial Fields in Tailscale endpoints only control how it connects to the control plane and have nothing to do with actual connections. + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/installation/build-from-source.md b/docs/installation/build-from-source.md index e7c92ef4b1..8c4d492379 100644 --- a/docs/installation/build-from-source.md +++ b/docs/installation/build-from-source.md @@ -60,5 +60,6 @@ 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) | It is not recommended to change the default build tag list unless you really know what you are adding. diff --git a/docs/installation/build-from-source.zh.md b/docs/installation/build-from-source.zh.md index f0bcc49baa..7de7fcf400 100644 --- a/docs/installation/build-from-source.zh.md +++ b/docs/installation/build-from-source.zh.md @@ -60,5 +60,6 @@ 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) | 除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。 diff --git a/go.mod b/go.mod index 2bf760a319..3db283837a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sagernet/sing-box -go 1.20 +go 1.23.1 require ( github.com/caddyserver/certmagic v0.20.0 @@ -22,7 +22,7 @@ require ( github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 - github.com/sagernet/gomobile v0.1.4 + github.com/sagernet/gomobile v0.1.6 github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 @@ -35,6 +35,7 @@ require ( github.com/sagernet/sing-tun v0.6.9 github.com/sagernet/sing-vmess v0.2.3 github.com/sagernet/smux v1.5.34-mod.2 + github.com/sagernet/tailscale v1.80.3-mod.5 github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 @@ -42,62 +43,97 @@ require ( github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.32.0 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/mod v0.20.0 - golang.org/x/net v0.34.0 + golang.org/x/crypto v0.33.0 + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 + golang.org/x/mod v0.22.0 + golang.org/x/net v0.35.0 golang.org/x/sys v0.30.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 - google.golang.org/protobuf v1.33.0 + google.golang.org/protobuf v1.35.1 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 - github.com/andybalholm/brotli v1.0.6 // indirect + 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/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/coder/websocket v1.8.12 // indirect + github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // 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/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/go-ole/go-ole v1.3.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // 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/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-querystring v1.1.0 // indirect - github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // 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/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/jsimonetti/rtnetlink v1.4.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/libdns/libdns v0.2.2 // indirect - github.com/mdlayher/netlink v1.7.2 // indirect - github.com/mdlayher/socket v0.4.1 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/onsi/ginkgo/v2 v2.9.7 // indirect - github.com/pierrec/lz4/v4 v4.1.14 // indirect - github.com/pmezard/go-difflib v1.0.0 // 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.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/onsi/ginkgo/v2 v2.17.2 // indirect + 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.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // 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 github.com/spf13/pflag v1.0.5 // indirect - github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // 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/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/vishvananda/netns v0.0.4 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.7.0 // indirect - golang.org/x/tools v0.24.0 // indirect + go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.29.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-20240227224415-6ceb2ff114de // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // 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 3abed22fd6..ab2b9a7c5c 100644 --- a/go.sum +++ b/go.sum @@ -1,39 +1,70 @@ +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= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= +github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +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/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.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= 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.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= +github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +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.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/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/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/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/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-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-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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 v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +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.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +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/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.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -41,26 +72,38 @@ 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-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= -github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +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= +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/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-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= +github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= +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.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +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.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= @@ -70,32 +113,44 @@ github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/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.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= -github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +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.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= +github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= 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 v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +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/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/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= -github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= -github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +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/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= +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/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= github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4= github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= @@ -106,8 +161,8 @@ 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/gomobile v0.1.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY= -github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= +github.com/sagernet/gomobile v0.1.6 h1:JkR1ToKOrdoiwULte4pYS5HYdPBzl2N+JNuuwVuLs0k= +github.com/sagernet/gomobile v0.1.6/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= 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/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= @@ -137,6 +192,10 @@ github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwX github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= 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-mod.4.0.20250512093633-e1bc1888c814 h1:B6ejgOuM1BrX4TzWvm1h/LQAOZW1T1jP4PSZe8b/49o= +github.com/sagernet/tailscale v1.80.3-mod.4.0.20250512093633-e1bc1888c814/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= +github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A= +github.com/sagernet/tailscale v1.80.3-mod.5/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.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= @@ -151,7 +210,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -159,10 +217,31 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= -github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= +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= +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/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= +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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= @@ -170,28 +249,36 @@ github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvv 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.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= +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= 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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +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/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.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -199,31 +286,34 @@ golang.org/x/sys v0.21.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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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/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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +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/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= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= +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-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= 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= @@ -232,3 +322,5 @@ 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= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/include/registry.go b/include/registry.go index cbf793f460..866c506afb 100644 --- a/include/registry.go +++ b/include/registry.go @@ -92,6 +92,7 @@ func EndpointRegistry() *endpoint.Registry { registry := endpoint.NewRegistry() registerWireGuardEndpoint(registry) + registerTailscaleEndpoint(registry) return registry } @@ -110,6 +111,7 @@ func DNSTransportRegistry() *dns.TransportRegistry { registerQUICTransports(registry) registerDHCPTransport(registry) + registerTailscaleTransport(registry) return registry } diff --git a/include/tailscale.go b/include/tailscale.go new file mode 100644 index 0000000000..05eed2cd39 --- /dev/null +++ b/include/tailscale.go @@ -0,0 +1,17 @@ +//go:build with_tailscale + +package include + +import ( + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/protocol/tailscale" +) + +func registerTailscaleEndpoint(registry *endpoint.Registry) { + tailscale.RegisterEndpoint(registry) +} + +func registerTailscaleTransport(registry *dns.TransportRegistry) { + tailscale.RegistryTransport(registry) +} diff --git a/include/tailscale_stub.go b/include/tailscale_stub.go new file mode 100644 index 0000000000..ddf6485ecb --- /dev/null +++ b/include/tailscale_stub.go @@ -0,0 +1,27 @@ +//go:build !with_tailscale + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + 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" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerTailscaleEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.TailscaleEndpointOptions](registry, C.TypeTailscale, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) { + return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`) + }) +} + +func registerTailscaleTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.TailscaleDNSServerOptions](registry, C.DNSTypeTailscale, func(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleDNSServerOptions) (adapter.DNSTransport, error) { + return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`) + }) +} diff --git a/mkdocs.yml b/mkdocs.yml index 5758525a79..e1a74fb09d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -93,6 +93,7 @@ nav: - Predefined: configuration/dns/server/predefined.md - DHCP: configuration/dns/server/dhcp.md - FakeIP: configuration/dns/server/fakeip.md + - Tailscale: configuration/dns/server/tailscale.md - DNS Rule: configuration/dns/rule.md - DNS Rule Action: configuration/dns/rule_action.md - FakeIP: configuration/dns/fakeip.md @@ -127,6 +128,7 @@ nav: - Endpoint: - configuration/endpoint/index.md - WireGuard: configuration/endpoint/wireguard.md + - Tailscale: configuration/endpoint/tailscale.md - Inbound: - configuration/inbound/index.md - Direct: configuration/inbound/direct.md diff --git a/option/tailscale.go b/option/tailscale.go new file mode 100644 index 0000000000..30579fc77e --- /dev/null +++ b/option/tailscale.go @@ -0,0 +1,24 @@ +package option + +import ( + "net/netip" +) + +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"` + 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"` +} + +type TailscaleDNSServerOptions struct { + Endpoint string `json:"endpoint,omitempty"` + AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"` +} diff --git a/protocol/tailscale/dns_transport.go b/protocol/tailscale/dns_transport.go new file mode 100644 index 0000000000..0c83c69811 --- /dev/null +++ b/protocol/tailscale/dns_transport.go @@ -0,0 +1,320 @@ +package tailscale + +import ( + "context" + "net" + "net/http" + "net/netip" + "net/url" + "os" + "reflect" + "strings" + "sync" + + "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" + "github.com/sagernet/sing-box/dns/transport" + "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" + nDNS "github.com/sagernet/tailscale/net/dns" + "github.com/sagernet/tailscale/types/dnstype" + "github.com/sagernet/tailscale/wgengine/router" + "github.com/sagernet/tailscale/wgengine/wgcfg" + + mDNS "github.com/miekg/dns" + "go4.org/netipx" + "golang.org/x/net/http2" +) + +func RegistryTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.TailscaleDNSServerOptions](registry, C.DNSTypeTailscale, NewDNSTransport) +} + +type DNSTransport struct { + dns.TransportAdapter + ctx context.Context + logger logger.ContextLogger + endpointTag string + acceptDefaultResolvers bool + dnsRouter adapter.DNSRouter + endpointManager adapter.EndpointManager + cfg *wgcfg.Config + dnsCfg *nDNS.Config + endpoint *Endpoint + routePrefixes []netip.Prefix + routes map[string][]adapter.DNSTransport + hosts map[string][]netip.Addr + defaultResolvers []adapter.DNSTransport +} + +func NewDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleDNSServerOptions) (adapter.DNSTransport, error) { + if options.Endpoint == "" { + return nil, E.New("missing tailscale endpoint tag") + } + return &DNSTransport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypeTailscale, tag, nil), + ctx: ctx, + logger: logger, + endpointTag: options.Endpoint, + acceptDefaultResolvers: options.AcceptDefaultResolvers, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + endpointManager: service.FromContext[adapter.EndpointManager](ctx), + }, nil +} + +func (t *DNSTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateInitialize { + return nil + } + rawOutbound, loaded := t.endpointManager.Get(t.endpointTag) + if !loaded { + return E.New("endpoint not found: ", t.endpointTag) + } + ep, isTailscale := rawOutbound.(*Endpoint) + if !isTailscale { + return E.New("endpoint is not Tailscale: ", t.endpointTag) + } + if ep.onReconfig != nil { + return E.New("only one Tailscale DNS server is allowed for single endpoint") + } + ep.onReconfig = t.onReconfig + t.endpoint = ep + return nil +} + +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")) + } +} + +func (t *DNSTransport) updateDNSServers(routeConfig *router.Config, dnsConfig *nDNS.Config) error { + t.routePrefixes = buildRoutePrefixes(routeConfig) + directDialerOnce := sync.OnceValue(func() N.Dialer { + directDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{})) + return &DNSDialer{transport: t, fallbackDialer: directDialer} + }) + routes := make(map[string][]adapter.DNSTransport) + for domain, resolvers := range dnsConfig.Routes { + var myResolvers []adapter.DNSTransport + for _, resolver := range resolvers { + myResolver, err := t.createResolver(directDialerOnce, resolver) + if err != nil { + return err + } + myResolvers = append(myResolvers, myResolver) + } + routes[domain.WithTrailingDot()] = myResolvers + } + hosts := make(map[string][]netip.Addr) + for domain, addresses := range dnsConfig.Hosts { + hosts[domain.WithTrailingDot()] = addresses + } + var defaultResolvers []adapter.DNSTransport + for _, resolver := range dnsConfig.DefaultResolvers { + myResolver, err := t.createResolver(directDialerOnce, resolver) + if err != nil { + return err + } + defaultResolvers = append(defaultResolvers, myResolver) + } + t.routes = routes + t.hosts = hosts + t.defaultResolvers = defaultResolvers + if len(defaultResolvers) > 0 { + t.logger.Info("updated ", len(routes), " routes, ", len(hosts), " hosts, default resolvers: ", + strings.Join(common.Map(dnsConfig.DefaultResolvers, func(it *dnstype.Resolver) string { return it.Addr }), " ")) + } else { + t.logger.Info("updated ", len(routes), " routes, ", len(hosts), " hosts") + } + return nil +} + +func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dnstype.Resolver) (adapter.DNSTransport, error) { + serverURL, parseURLErr := url.Parse(resolver.Addr) + var myDialer N.Dialer + if parseURLErr == nil && serverURL.Scheme == "http" { + myDialer = t.endpoint + } else { + myDialer = directDialer() + } + if len(resolver.BootstrapResolution) > 0 { + bootstrapTransport := transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, M.SocksaddrFrom(resolver.BootstrapResolution[0], 53)) + myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", adapter.DNSQueryOptions{Transport: bootstrapTransport}, 0) + } + if serverAddr := M.ParseSocksaddr(resolver.Addr); serverAddr.IsValid() { + if serverAddr.Port == 0 { + serverAddr.Port = 53 + } + return transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil + } else if parseURLErr != nil { + return nil, E.Cause(parseURLErr, "parse resolver address") + } else { + switch serverURL.Scheme { + case "https": + serverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port()) + if serverAddr.Port == 0 { + serverAddr.Port = 443 + } + tlsConfig := common.Must1(tls.NewClient(t.ctx, 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 + case "http": + serverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port()) + if serverAddr.Port == 0 { + serverAddr.Port = 80 + } + return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, nil), nil + // case "tls": + default: + return nil, E.New("unknown resolver scheme: ", serverURL.Scheme) + } + } +} + +func buildRoutePrefixes(routeConfig *router.Config) []netip.Prefix { + var builder netipx.IPSetBuilder + for _, localAddr := range routeConfig.LocalAddrs { + builder.AddPrefix(localAddr) + } + for _, route := range routeConfig.Routes { + builder.AddPrefix(route) + } + for _, route := range routeConfig.LocalRoutes { + builder.AddPrefix(route) + } + for _, route := range routeConfig.SubnetRoutes { + builder.AddPrefix(route) + } + ipSet, err := builder.IPSet() + if err != nil { + return nil + } + return ipSet.Prefixes() +} + +func (t *DNSTransport) Close() error { + return nil +} + +func (t *DNSTransport) Raw() bool { + return true +} + +func (t *DNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + if len(message.Question) != 1 { + return nil, os.ErrInvalid + } + question := message.Question[0] + addresses, hostsLoaded := t.hosts[question.Name] + if hostsLoaded { + switch question.Qtype { + case mDNS.TypeA: + addresses4 := common.Filter(addresses, func(addr netip.Addr) bool { + return addr.Is4() + }) + if len(addresses4) > 0 { + return dns.FixedResponse(message.Id, question, addresses4, C.DefaultDNSTTL), nil + } + case mDNS.TypeAAAA: + addresses6 := common.Filter(addresses, func(addr netip.Addr) bool { + return addr.Is6() + }) + if len(addresses6) > 0 { + return dns.FixedResponse(message.Id, question, addresses6, C.DefaultDNSTTL), nil + } + } + } + for domainSuffix, transports := range t.routes { + if strings.HasSuffix(question.Name, domainSuffix) { + if len(transports) == 0 { + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Rcode: mDNS.RcodeNameError, + Response: true, + }, + Question: []mDNS.Question{question}, + }, nil + } + var lastErr error + for _, dnsTransport := range transports { + response, err := dnsTransport.Exchange(ctx, message) + if err != nil { + lastErr = err + continue + } + return response, nil + } + return nil, lastErr + } + } + if t.acceptDefaultResolvers { + if len(t.defaultResolvers) > 0 { + var lastErr error + for _, resolver := range t.defaultResolvers { + response, err := resolver.Exchange(ctx, message) + if err != nil { + lastErr = err + continue + } + return response, nil + } + return nil, lastErr + } else { + return nil, E.New("missing default resolvers") + } + } + return nil, dns.RCodeNameError +} + +type DNSDialer struct { + transport *DNSTransport + fallbackDialer N.Dialer +} + +func (d *DNSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if destination.IsFqdn() { + panic("invalid request here") + } + for _, prefix := range d.transport.routePrefixes { + if prefix.Contains(destination.Addr) { + return d.transport.endpoint.DialContext(ctx, network, destination) + } + } + return d.fallbackDialer.DialContext(ctx, network, destination) +} + +func (d *DNSDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if destination.IsFqdn() { + panic("invalid request here") + } + for _, prefix := range d.transport.routePrefixes { + if prefix.Contains(destination.Addr) { + return d.transport.endpoint.ListenPacket(ctx, destination) + } + } + return d.fallbackDialer.ListenPacket(ctx, destination) +} diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go new file mode 100644 index 0000000000..f24f931f49 --- /dev/null +++ b/protocol/tailscale/endpoint.go @@ -0,0 +1,496 @@ +package tailscale + +import ( + "context" + "fmt" + "net" + "net/netip" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" + "sync/atomic" + "syscall" + "time" + + "github.com/sagernet/gvisor/pkg/tcpip" + "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/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" + 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-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "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" + "github.com/sagernet/sing/service/filemanager" + "github.com/sagernet/tailscale/ipn" + tsDNS "github.com/sagernet/tailscale/net/dns" + "github.com/sagernet/tailscale/net/netmon" + "github.com/sagernet/tailscale/net/tsaddr" + "github.com/sagernet/tailscale/tsnet" + "github.com/sagernet/tailscale/types/ipproto" + "github.com/sagernet/tailscale/version" + "github.com/sagernet/tailscale/wgengine" + "github.com/sagernet/tailscale/wgengine/filter" +) + +func init() { + version.SetVersion("sing-box " + C.Version) +} + +func RegisterEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.TailscaleEndpointOptions](registry, C.TypeTailscale, NewEndpoint) +} + +type Endpoint struct { + endpoint.Adapter + ctx context.Context + router adapter.Router + logger logger.ContextLogger + dnsRouter adapter.DNSRouter + network adapter.NetworkManager + platformInterface platform.Interface + server *tsnet.Server + stack *stack.Stack + filter *atomic.Pointer[filter.Filter] + onReconfig wgengine.ReconfigListener + + exitNode string + exitNodeAllowLANAccess bool + advertiseRoutes []netip.Prefix + advertiseExitNode bool + + udpTimeout time.Duration +} + +func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) { + stateDirectory := options.StateDirectory + if stateDirectory == "" { + stateDirectory = "tailscale" + } + hostname := options.Hostname + if hostname == "" { + osHostname, _ := os.Hostname() + osHostname = strings.TrimSpace(osHostname) + hostname = osHostname + } + if hostname == "" { + hostname = "sing-box" + } + stateDirectory = filemanager.BasePath(ctx, os.ExpandEnv(stateDirectory)) + stateDirectory, _ = filepath.Abs(stateDirectory) + for _, advertiseRoute := range options.AdvertiseRoutes { + if advertiseRoute.Addr().IsUnspecified() && advertiseRoute.Bits() == 0 { + return nil, E.New("`advertise_routes` cannot be default, use `advertise_exit_node` instead.") + } + } + if options.AdvertiseExitNode && options.ExitNode != "" { + return nil, E.New("cannot advertise an exit node and use an exit node at the same time.") + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + var remoteIsDomain bool + if options.ControlURL != "" { + controlURL, err := url.Parse(options.ControlURL) + if err != nil { + return nil, E.Cause(err, "parse control URL") + } + remoteIsDomain = M.IsDomainName(controlURL.Hostname()) + } else { + // controlplane.tailscale.com + remoteIsDomain = true + } + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: remoteIsDomain, + ResolverOnDetour: true, + NewDialer: true, + }) + if err != nil { + return nil, err + } + dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + server := &tsnet.Server{ + Dir: stateDirectory, + Hostname: hostname, + Logf: func(format string, args ...any) { + logger.Trace(fmt.Sprintf(format, args...)) + }, + 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}, + LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) { + return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions()) + }, + DNS: &dnsConfigurtor{}, + } + return &Endpoint{ + Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), + ctx: ctx, + router: router, + logger: logger, + dnsRouter: dnsRouter, + network: service.FromContext[adapter.NetworkManager](ctx), + platformInterface: service.FromContext[platform.Interface](ctx), + server: server, + exitNode: options.ExitNode, + exitNodeAllowLANAccess: options.ExitNodeAllowLANAccess, + advertiseRoutes: options.AdvertiseRoutes, + advertiseExitNode: options.AdvertiseExitNode, + udpTimeout: udpTimeout, + }, nil +} + +func (t *Endpoint) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if t.platformInterface != nil { + err := t.network.UpdateInterfaces() + if err != nil { + return err + } + netmon.RegisterInterfaceGetter(func() ([]netmon.Interface, error) { + return common.Map(t.network.InterfaceFinder().Interfaces(), func(it control.Interface) netmon.Interface { + return netmon.Interface{ + Interface: &net.Interface{ + Index: it.Index, + MTU: it.MTU, + Name: it.Name, + HardwareAddr: it.HardwareAddr, + Flags: it.Flags, + }, + AltAddrs: common.Map(it.Addresses, func(it netip.Prefix) net.Addr { + return &net.IPNet{ + IP: it.Addr().AsSlice(), + Mask: net.CIDRMask(it.Bits(), it.Addr().BitLen()), + } + }), + } + }), nil + }) + if runtime.GOOS == "android" { + setAndroidProtectFunc(t.platformInterface) + } + } + err := t.server.Start() + if err != nil { + return err + } + if t.onReconfig != nil { + t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig) + } + + ipStack := t.server.ExportNetstack().ExportIPStack() + 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) + t.stack = ipStack + + localBackend := t.server.ExportLocalBackend() + perfs := &ipn.MaskedPrefs{ + ExitNodeIPSet: true, + AdvertiseRoutesSet: true, + } + if len(t.advertiseRoutes) > 0 { + perfs.AdvertiseRoutes = t.advertiseRoutes + } + if t.advertiseExitNode { + perfs.AdvertiseRoutes = append(perfs.AdvertiseRoutes, tsaddr.ExitRoutes()...) + } + _, err = localBackend.EditPrefs(perfs) + if err != nil { + return E.Cause(err, "update prefs") + } + t.filter = localBackend.ExportFilter() + + go t.watchState() + return nil +} + +func (t *Endpoint) watchState() { + localBackend := t.server.ExportLocalBackend() + localBackend.WatchNotifications(t.ctx, ipn.NotifyInitialState, nil, func(roNotify *ipn.Notify) (keepGoing bool) { + if roNotify.State != nil && *roNotify.State != ipn.NeedsLogin && *roNotify.State != ipn.NoState { + return false + } + authURL := localBackend.StatusWithoutPeers().AuthURL + if authURL != "" { + t.logger.Info("Waiting for authentication: ", authURL) + if t.platformInterface != nil { + err := t.platformInterface.SendNotification(&platform.Notification{ + Identifier: "tailscale-authentication", + TypeName: "Tailscale Authentication Notifications", + TypeID: 10, + Title: "Tailscale Authentication", + Body: F.ToString("Tailscale outbound[", t.Tag(), "] is waiting for authentication."), + OpenURL: authURL, + }) + if err != nil { + t.logger.Error("send authentication notification: ", err) + } + } + return false + } + return true + }) + if t.exitNode != "" { + localBackend.WatchNotifications(t.ctx, ipn.NotifyInitialState, nil, func(roNotify *ipn.Notify) (keepGoing bool) { + if roNotify.State == nil || *roNotify.State != ipn.Running { + return true + } + status, err := common.Must1(t.server.LocalClient()).Status(t.ctx) + if err != nil { + t.logger.Error("set exit node: ", err) + return + } + perfs := &ipn.MaskedPrefs{ + Prefs: ipn.Prefs{ + ExitNodeAllowLANAccess: t.exitNodeAllowLANAccess, + }, + ExitNodeIPSet: true, + ExitNodeAllowLANAccessSet: true, + } + err = perfs.SetExitNodeIP(t.exitNode, status) + if err != nil { + t.logger.Error("set exit node: ", err) + return true + } + _, err = localBackend.EditPrefs(perfs) + if err != nil { + t.logger.Error("set exit node: ", err) + return true + } + return false + }) + } +} + +func (t *Endpoint) Close() error { + netmon.RegisterInterfaceGetter(nil) + if runtime.GOOS == "android" { + setAndroidProtectFunc(nil) + } + return common.Close(common.PtrOrNil(t.server)) +} + +func (t *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch network { + case N.NetworkTCP: + t.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + 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 + } + return N.DialSerial(ctx, t, network, destination, destinationAddresses) + } + addr := tcpip.FullAddress{ + NIC: 1, + Port: destination.Port, + Addr: addressFromAddr(destination.Addr), + } + var networkProtocol tcpip.NetworkProtocolNumber + if destination.IsIPv4() { + networkProtocol = header.IPv4ProtocolNumber + } else { + networkProtocol = header.IPv6ProtocolNumber + } + switch N.NetworkName(network) { + case N.NetworkTCP: + tcpConn, err := gonet.DialContextTCP(ctx, t.stack, addr, networkProtocol) + if err != nil { + return nil, err + } + return tcpConn, nil + case N.NetworkUDP: + udpConn, err := gonet.DialUDP(t.stack, nil, &addr, networkProtocol) + if err != nil { + return nil, err + } + return udpConn, nil + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } +} + +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 + } + addr4, addr6 := t.server.TailscaleIPs() + bind := tcpip.FullAddress{ + NIC: 1, + } + var networkProtocol tcpip.NetworkProtocolNumber + if destination.IsIPv4() { + if !addr4.IsValid() { + return nil, E.New("missing Tailscale IPv4 address") + } + networkProtocol = header.IPv4ProtocolNumber + bind.Addr = addressFromAddr(addr4) + } else { + if !addr6.IsValid() { + return nil, E.New("missing Tailscale IPv6 address") + } + networkProtocol = header.IPv6ProtocolNumber + bind.Addr = addressFromAddr(addr6) + } + udpConn, err := gonet.DialUDP(t.stack, &bind, nil, networkProtocol) + if err != nil { + return nil, err + } + return udpConn, nil +} + +func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { + tsFilter := t.filter.Load() + if tsFilter != nil { + var ipProto ipproto.Proto + switch N.NetworkName(network) { + case N.NetworkTCP: + ipProto = ipproto.TCP + case N.NetworkUDP: + ipProto = ipproto.UDP + } + response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto) + switch response { + case filter.Drop: + return syscall.ECONNRESET + case filter.DropSilently: + return tun.ErrDrop + } + } + return t.router.PreMatch(adapter.InboundContext{ + Inbound: t.Tag(), + InboundType: t.Type(), + Network: network, + Source: source, + Destination: destination, + }) +} + +func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = source + addr4, addr6 := t.server.TailscaleIPs() + switch destination.Addr { + case addr4: + destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + case addr6: + destination.Addr = netip.IPv6Loopback() + } + metadata.Destination = destination + t.logger.InfoContext(ctx, "inbound connection from ", source) + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = source + metadata.Destination = destination + addr4, addr6 := t.server.TailscaleIPs() + switch destination.Addr { + case addr4: + metadata.OriginDestination = destination + destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) + case addr6: + metadata.OriginDestination = destination + destination.Addr = netip.IPv6Loopback() + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) + } + t.logger.InfoContext(ctx, "inbound packet connection from ", source) + t.logger.InfoContext(ctx, "inbound packet connection to ", destination) + t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func addressFromAddr(destination netip.Addr) tcpip.Address { + if destination.Is6() { + return tcpip.AddrFrom16(destination.As16()) + } else { + return tcpip.AddrFrom4(destination.As4()) + } +} + +type endpointDialer struct { + N.Dialer + logger logger.ContextLogger +} + +func (d *endpointDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch N.NetworkName(network) { + case N.NetworkTCP: + d.logger.InfoContext(ctx, "output connection to ", destination) + case N.NetworkUDP: + d.logger.InfoContext(ctx, "output packet connection to ", destination) + } + return d.Dialer.DialContext(ctx, network, destination) +} + +func (d *endpointDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + d.logger.InfoContext(ctx, "output packet connection") + return d.Dialer.ListenPacket(ctx, destination) +} + +type dnsConfigurtor struct { + baseConfig tsDNS.OSConfig +} + +func (c *dnsConfigurtor) SetDNS(cfg tsDNS.OSConfig) error { + c.baseConfig = cfg + return nil +} + +func (c *dnsConfigurtor) SupportsSplitDNS() bool { + return true +} + +func (c *dnsConfigurtor) GetBaseConfig() (tsDNS.OSConfig, error) { + return c.baseConfig, nil +} + +func (c *dnsConfigurtor) Close() error { + return nil +} diff --git a/protocol/tailscale/protect_android.go b/protocol/tailscale/protect_android.go new file mode 100644 index 0000000000..90ab615a8a --- /dev/null +++ b/protocol/tailscale/protect_android.go @@ -0,0 +1,16 @@ +package tailscale + +import ( + "github.com/sagernet/sing-box/experimental/libbox/platform" + "github.com/sagernet/tailscale/net/netns" +) + +func setAndroidProtectFunc(platformInterface platform.Interface) { + if platformInterface != nil { + netns.SetAndroidProtectFunc(func(fd int) error { + return platformInterface.AutoDetectInterfaceControl(fd) + }) + } else { + netns.SetAndroidProtectFunc(nil) + } +} diff --git a/protocol/tailscale/protect_nonandroid.go b/protocol/tailscale/protect_nonandroid.go new file mode 100644 index 0000000000..eeb56bf6db --- /dev/null +++ b/protocol/tailscale/protect_nonandroid.go @@ -0,0 +1,8 @@ +//go:build !android + +package tailscale + +import "github.com/sagernet/sing-box/experimental/libbox/platform" + +func setAndroidProtectFunc(platformInterface platform.Interface) { +} From 91c7b638e89fa32d5c84b519cc387bad0f351035 Mon Sep 17 00:00:00 2001 From: xchacha20-poly1305 Date: Wed, 12 Feb 2025 20:56:41 +0800 Subject: [PATCH 0368/1272] Remove single quotes of raw Moziila certs --- cmd/internal/update_certificates/main.go | 5 +- common/certificate/mozilla.go | 785 ++++++++++++----------- 2 files changed, 414 insertions(+), 376 deletions(-) diff --git a/cmd/internal/update_certificates/main.go b/cmd/internal/update_certificates/main.go index cf597c867d..744d77243f 100644 --- a/cmd/internal/update_certificates/main.go +++ b/cmd/internal/update_certificates/main.go @@ -60,7 +60,10 @@ func init() { generated.WriteString(record[nameIndex]) generated.WriteString("\n") generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`") - generated.WriteString(record[certIndex]) + cert := record[certIndex] + // Remove single quotes + cert = cert[1 : len(cert)-1] + generated.WriteString(cert) generated.WriteString("`))\n") } generated.WriteString("}\n") diff --git a/common/certificate/mozilla.go b/common/certificate/mozilla.go index 3bf1d17502..097091d0bc 100644 --- a/common/certificate/mozilla.go +++ b/common/certificate/mozilla.go @@ -10,7 +10,7 @@ func init() { mozillaIncluded = x509.NewCertPool() // Actalis Authentication Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 @@ -42,10 +42,10 @@ K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // TunTrust Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv @@ -77,10 +77,10 @@ nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Amazon Root CA 1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL @@ -99,10 +99,10 @@ N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy rqXRfboQnoZsG4q5WTP468SQvvG5 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Amazon Root CA 2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL @@ -132,10 +132,10 @@ n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE 76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H 9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT 4PsJYGw= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Amazon Root CA 3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG @@ -146,10 +146,10 @@ QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM YyRIHN8wfdVoOw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Amazon Root CA 4 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG @@ -161,10 +161,10 @@ M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW 1KyLa2tJElMzrdfkviT8tQp21KW8EA== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Starfield Services Root Certificate Authority - G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs @@ -187,10 +187,10 @@ qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn 0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN sSi6 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Certum CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM @@ -208,10 +208,10 @@ xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs 6GAqm4VKQPNriiTsBhYscw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Certum EC-384 CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT @@ -225,10 +225,10 @@ QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Certum Trusted Network CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU @@ -249,10 +249,10 @@ I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Certum Trusted Network CA 2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG @@ -285,10 +285,10 @@ b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P 5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi DrW5viSP ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Certum Trusted Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV @@ -320,10 +320,10 @@ P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP 0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Autoridad de Certificacion Firmaprofesional CIF A62634068 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 @@ -357,10 +357,10 @@ CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // FIRMAPROFESIONAL CA ROOT-A WEB - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB @@ -375,10 +375,10 @@ BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG XSaQpYXFuXqUPoeovQA= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // ANF Secure Server Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV @@ -411,10 +411,10 @@ OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Buypass Class 2 Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow @@ -444,10 +444,10 @@ I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h 3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Buypass Class 3 Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow @@ -477,10 +477,10 @@ kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq 4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Certainly Root E1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ @@ -492,10 +492,10 @@ BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Certainly Root R1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 @@ -525,10 +525,10 @@ Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 OV+KmalBWQewLK8= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Certigna - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ @@ -549,10 +549,10 @@ fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Certigna Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x @@ -587,10 +587,10 @@ faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw 3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // certSIGN ROOT CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP @@ -609,10 +609,10 @@ MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN 9u6wWk5JRFRYX0KD ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // certSIGN ROOT CA G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ @@ -642,10 +642,10 @@ pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE 1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX QRBdJ3NghVdJIgc= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // ePKI Root Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe @@ -677,10 +677,10 @@ o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D hNQ+IIX3Sj0rnP0qCglN6oH4EZw= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // HiPKI Root CA - G1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa @@ -710,10 +710,10 @@ Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // CommScope Public Trust ECC Root-01 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMw TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNa @@ -726,10 +726,10 @@ VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg 2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uS Um9poIyNStDuiw7LR47QjRE= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // CommScope Public Trust ECC Root-02 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMw TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRa @@ -742,10 +742,10 @@ VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/n ich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AV mkzw5l4lIhVtwodZ0LKOag== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // CommScope Public Trust RSA Root-01 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQEL BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1 @@ -775,10 +775,10 @@ Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0w lREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVoc icCMb3SgazNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // CommScope Public Trust RSA Root-02 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQEL BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2 @@ -808,10 +808,10 @@ PjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2d lklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670 v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17O rg3bhzjlP1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SecureSign Root CA12 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw @@ -831,10 +831,10 @@ mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA 8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV 55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SecureSign Root CA14 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw @@ -865,10 +865,10 @@ dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB 365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c JRNItX+S ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SecureSign Root CA15 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy @@ -881,10 +881,10 @@ Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT 9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp 4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // D-TRUST BR Root CA 1 2020 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 @@ -901,10 +901,45 @@ c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV dWNbFJWcHwHP2NVypw87 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) + + // D-TRUST BR Root CA 2 2023 + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw +OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr +i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE +gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8 +k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT +Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl +2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U +cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP +/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS +uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+ +0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N +DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+ +XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61 +GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI +FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n +riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR +VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc +LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn +4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD +hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG +koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46 +ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS +Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80 +knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ +hJ65bvspmZDogNOfJA== +-----END CERTIFICATE-----`)) // D-TRUST EV Root CA 1 2020 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 @@ -921,10 +956,45 @@ c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb gfM0agPnIjhQW+0ZT0MW ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) + + // D-TRUST EV Root CA 2 2023 + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw +OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK +F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE +7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe +EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6 +lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb +RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV +jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc +jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx +TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ +ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk +hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF +NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH +kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14 +QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4 +pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q +3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU +t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX +cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8 +ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT +2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs +7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP +gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst +Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh +XBxvWHZks/wCuPWdCg== +-----END CERTIFICATE-----`)) // D-TRUST Root CA 3 2013 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEDjCCAvagAwIBAgIDD92sMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkRF MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxHzAdBgNVBAMMFkQtVFJVU1QgUm9vdCBD QSAzIDIwMTMwHhcNMTMwOTIwMDgyNTUxWhcNMjgwOTIwMDgyNTUxWjBFMQswCQYD @@ -947,10 +1017,10 @@ t4RVLFzI9XRKEBmLo8ftNdYJSNMOwLo5qLBGArDbxohZwr78e7Erz35ih1WWzAFv m2chlTWL+BD8cRu3SzdppjvW7IvuwbDzJcmPkn2h6sPKRL8mpXSSnON065102ctN h9j8tGlsi6BDB2B4l+nZk3zCRrybN1Kj7Yo8E6l7U0tJmhEFLAtuVqwfLoJs4Gln tQ5tLdnkwBXxP/oYcuEVbSdbLTAoK59ImmQrme/ydUlfXA== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // D-TRUST Root Class 3 CA 2 2009 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha @@ -974,10 +1044,10 @@ o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y Johw1+qRzT65ysCQblrGXnRl11z+o+I= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // D-TRUST Root Class 3 CA 2 EV 2009 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw @@ -1001,10 +1071,10 @@ nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // D-Trust SBR Root CA 1 2022 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICXjCCAeOgAwIBAgIQUs/kjG2gSvc/gpcMgAmMlTAKBggqhkjOPQQDAzBJMQsw CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpELVRy dXN0IFNCUiBSb290IENBIDEgMjAyMjAeFw0yMjA3MDYxMTMwMDBaFw0zNzA3MDYx @@ -1018,10 +1088,10 @@ hjlodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Nicl9yb290X2Nh XzFfMjAyMi5jcmwwCgYIKoZIzj0EAwMDaQAwZgIxAJf53q5Lj5i1HkB/Mn1NVEPa ic3CqpI80YIec8/6TJIg+2MnxfVzPQk996dhhozzagIxAOcvfLj1JYw7OR82q431 hqIu4Xpk2mc5Av7+Mz/Zc7ZYWzr8sqTZYHh3zHmnpq5VvQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // D-Trust SBR Root CA 2 2022 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFrDCCA5SgAwIBAgIQVNWjlR49lbpyG5rQMSFKujANBgkqhkiG9w0BAQ0FADBJ MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpE LVRydXN0IFNCUiBSb290IENBIDIgMjAyMjAeFw0yMjA3MDcwNzMwMDBaFw0zNzA3 @@ -1053,10 +1123,10 @@ JpmgVDuhEm1wYs7T+bi9IvzUmtS74jgWL7d9OcKwqQPpnM9+GI123F8Ru+tC7FAJ PlzskDHYGnK6P2kH7pg0wjSk1toT1qmE8gCGwFS6HhGw4rnEB7SR56rmMVZvsUTE KmK8ybBlnDT8DBpT3yEXu8JtoQrm8bCqRAlQSTh6XXHiMS4ZsN+VQgR9hIjOCiNn azidFt4G/ihwOKVarvyD7Q== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // T-TeleSec GlobalRoot Class 2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl @@ -1078,10 +1148,10 @@ IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN 9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP BSeOE6Fuwg== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // T-TeleSec GlobalRoot Class 3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl @@ -1103,10 +1173,10 @@ WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ 91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p TpPDpFQUWw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Telekom Security SMIME ECC Root 2021 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICRzCCAc2gAwIBAgIQFSrdFMkY0aRWQIamJa8HXzAKBggqhkjOPQQDAzBlMQsw CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH bWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIEVDQyBSb290IDIw @@ -1120,10 +1190,10 @@ vZA6SzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD AwNoADBlAjEA1rxIkodHA8dwOyW2H65GZ3N0ACdL5KUEogPfXiitbl4DyN1onLa/ lBBIlS8P/xiLAjABQDOel5dNBfJ0VAzNOf1qawnBJD9hjjiht+jXRBURYv8OYTdH S0B/Sl+yZ1pzdcI= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Telekom Security SMIME RSA Root 2023 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFtzCCA5+gAwIBAgIQDH5i9XlzO51Djotj7ZGVuDANBgkqhkiG9w0BAQwFADBl MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 eSBHbWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIFJTQSBSb290 @@ -1155,10 +1225,10 @@ NDnPpZo2qsjtebzP9s4EUwvaslAjfLw+Jq3wDkO7JsuuwkDeNx8KoFHNY522T9jG R3y82LTtnovzEeKotT7srnA+fiK7NUgXYGIUkTCjdj2mUTaLHw3dajEcpe3dlqNu cg8TTaqnqVx4+QMSGJM3RRKJPfi+yr3ZvgzZGGSnyEE+dYIhOH1l9KDUE0sHeCn5 nX7Mhz/E2i6I3eML3FpRWunZEk+eAtv3BSVR ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Telekom Security TLS ECC Root 2020 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw @@ -1172,10 +1242,10 @@ MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn 27iQ7t0l ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Telekom Security TLS RSA Root 2023 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy @@ -1207,10 +1277,10 @@ L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Baltimore CyberTrust Root - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX @@ -1230,10 +1300,10 @@ jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert Assured ID Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv @@ -1254,10 +1324,10 @@ fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert Assured ID Root G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv @@ -1278,10 +1348,10 @@ lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo IhNzbM8m9Yop5w== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert Assured ID Root G3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg @@ -1295,10 +1365,10 @@ BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv 6pZjamVFkpUBtA== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert Global Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD @@ -1319,10 +1389,10 @@ hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert Global Root G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH @@ -1343,10 +1413,10 @@ Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert Global Root G3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe @@ -1360,10 +1430,10 @@ BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 sycX ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert High Assurance EV Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j @@ -1385,10 +1455,10 @@ hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep +OkuE6N36B9K ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert SMIME ECC P384 Root G5 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICHDCCAaOgAwIBAgIQBT9uoAYBcn3tP8OjtqPW7zAKBggqhkjOPQQDAzBQMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKDAmBgNVBAMTH0Rp Z2lDZXJ0IFNNSU1FIEVDQyBQMzg0IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN @@ -1401,10 +1471,10 @@ wmQyF/7gZ5AurTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggq hkjOPQQDAwNnADBkAjA3RPUygONx6/Rtz3zMkZrDbnHY0iNdkk2CQm1cYZX2kfWn CPZql+mclC2YcP0ztgkCMAc8L7lYgl4Po2Kok2fwIMNpvwMsO1CnO69BOMlSSJHW Dvu8YDB8ZD8SHkV/UT70pg== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert SMIME RSA4096 Root G5 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFajCCA1KgAwIBAgIQBfa6BCODRst9XOa5W7ocVTANBgkqhkiG9w0BAQwFADBP MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJzAlBgNVBAMT HkRpZ2lDZXJ0IFNNSU1FIFJTQTQwOTYgUm9vdCBHNTAeFw0yMTAxMTUwMDAwMDBa @@ -1434,10 +1504,10 @@ CQxy5BMjYEyjyhcue2cA29DN6nofOSZXiTB3y07llUVPX/s2XD35ILU6ECVPkzJa 7sGW6OlWBLBJYU3seKidGMH/2OovVu+VK3sEXmfjVUDtOQT5C3n1aoxcD4makMfN i97bJjWhbs2zQvKiDzsMjpP/FM/895P35EEIbhlSEQ9TGXN4DM/YhYH4rVXIsJ5G Y6+cUu5cv/DAWzceCSDSPiPGoRVKDjZ+MMV5arwiiNkMUkAf3U4PZyYW0q0XHA== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert TLS ECC P384 Root G5 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 @@ -1450,10 +1520,10 @@ B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 DXZDjC5Ty3zfDBeWUA== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert TLS RSA4096 Root G5 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN @@ -1483,10 +1553,10 @@ p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DigiCert Trusted Root G4 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg @@ -1517,10 +1587,10 @@ cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 /YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DIGITALSIGN GLOBAL ROOT ECDSA CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICajCCAfCgAwIBAgIUNi2PcoiiKCfkAP8kxi3k6/qdtuEwCgYIKoZIzj0EAwMw ZDELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmljYWRv cmEgRGlnaXRhbDEpMCcGA1UEAwwgRElHSVRBTFNJR04gR0xPQkFMIFJPT1QgRUNE @@ -1534,10 +1604,10 @@ GDAWgBTOr0qLGnXi8TjnAvAWrV7qZNV7tDAdBgNVHQ4EFgQUzq9Kixp14vE45wLw Fq1e6mTVe7QwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMAqIxHGc RANNjbTHvKiu2TAnNWprFmPX/OdZ4aeJG0wxmiNVRObzQyHVRydvbVcBqgIxAPuy 6uKXf1G1n0jrvG81iahkcKtXds3AxhRgyn/iggBz98w16o4km+UIWccEjHN4/g== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // DIGITALSIGN GLOBAL ROOT RSA CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFtTCCA52gAwIBAgIUXVnIyqsJV/XmtdoplARq/8XUlYcwDQYJKoZIhvcNAQEN BQAwYjELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmlj YWRvcmEgRGlnaXRhbDEnMCUGA1UEAwweRElHSVRBTFNJR04gR0xPQkFMIFJPT1Qg @@ -1569,10 +1639,10 @@ NrskGnkcUdH+E7v/eCzcpL4v9sVLU8+nTJlecKxZiASuZAS/e6Z6TdPod72hflAV 8+9JMIVNIVeq2yx1l62BAYeisXCdHgZaA2CxP6ZtgizUFLGBpeg9iB20cixYN4qO OJS4c92p4Lj2d6KzfFjermk6tYulGrvy2HQGnP1icyAhdrF+cJ4Z1OsXYhk4mc02 K0f+McvfueSsCNPYpuvUnn5LZKRVXSsXyQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // CA Disig Root R2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy @@ -1602,10 +1672,10 @@ gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GLOBALTRUST 2020 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx @@ -1636,10 +1706,10 @@ MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn 4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // emSign ECC Root CA - C3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw @@ -1652,10 +1722,10 @@ BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c 3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J 0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // emSign ECC Root CA - G3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g @@ -1669,10 +1739,10 @@ zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD +JbNR6iC8hZVdyR+EhCVBCyj ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // emSign Root CA - C1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw @@ -1692,10 +1762,10 @@ kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT +xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // emSign Root CA - G1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH @@ -1716,10 +1786,10 @@ GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH 6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx iN66zB+Afko= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // AffirmTrust Commercial - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL @@ -1738,10 +1808,10 @@ vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // AffirmTrust Networking - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL @@ -1760,10 +1830,10 @@ QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // AffirmTrust Premium - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG @@ -1793,10 +1863,10 @@ YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e KeC2uAloGRwYQw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // AffirmTrust Premium ECC - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ @@ -1808,10 +1878,10 @@ A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Entrust Root Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW @@ -1837,10 +1907,10 @@ AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP 9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m 0vdXcDazv/wor3ElhVsT/h5/WrQ8 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Entrust Root Certification Authority - EC1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu @@ -1857,10 +1927,10 @@ Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Entrust Root Certification Authority - G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs @@ -1884,10 +1954,10 @@ Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v 1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Entrust Root Certification Authority - G4 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg @@ -1922,10 +1992,10 @@ b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk 5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Entrust.net Certification Authority (2048) - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 @@ -1949,10 +2019,10 @@ zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er fF6adulZkMV8gzURZVE= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Atos TrustedRoot 2011 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM @@ -1972,10 +2042,10 @@ DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Atos TrustedRoot Root CA ECC G2 2020 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICMTCCAbagAwIBAgIMC3MoERh0MBzvbwiEMAoGCCqGSM49BAMDMEsxCzAJBgNV BAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRSb290 IFJvb3QgQ0EgRUNDIEcyIDIwMjAwHhcNMjAxMjE1MDgzOTEwWhcNNDAxMjEwMDgz @@ -1988,10 +2058,10 @@ shufvlwfjP2ztvuzDgmHMB0GA1UdDgQWBBRbH8RxbLIbn75cH4z9s7b7sw4JhzAO BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOzgmf3d5FTByx/oPijX FVlKgspTMOzrNqW5yM6TR1bIYabhbZJTlY/241VT8N165wIxALCH1RuzYPyRjYDK ohtRSzhUy6oee9flRJUWLzxEeC4luuqQ5OxS7lfsA4TzXtsWDQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Atos TrustedRoot Root CA ECC TLS 2021 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 @@ -2004,10 +2074,10 @@ KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo 9H1/IISpQuQo ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Atos TrustedRoot Root CA RSA G2 2020 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFfzCCA2egAwIBAgIMR7opRlU+FpKXsKtAMA0GCSqGSIb3DQEBDAUAMEsxCzAJ BgNVBAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRS b290IFJvb3QgQ0EgUlNBIEcyIDIwMjAwHhcNMjAxMjE1MDg0MTIzWhcNNDAxMjEw @@ -2038,10 +2108,10 @@ VD190nZ12V4+MkinjPKecgz4uFi4FyOlFId1WHoAgQciOWpMlKC1otunLMGw8aOb wEz3bXDqMZ/xrn0+cyjZod/6k/CbsPDizSUgde/ifTIFyZt27su9MR75lJhLJFhW SMDeBky9pjRd7RZhY3P7GeL6W9iXddRtnmA5XpSLAizrmc5gKm4bjKdLvP025pgf ZfJ/8eOPTIBGNli2oWXLzhxEdQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Atos TrustedRoot Root CA RSA TLS 2021 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 @@ -2071,10 +2141,10 @@ AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GlobalSign - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx @@ -2105,26 +2175,10 @@ pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R 8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GlobalSign - mozillaIncluded.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-----'`)) - - // GlobalSign - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 @@ -2144,10 +2198,26 @@ jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH WD9f ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) + + // GlobalSign + mozillaIncluded.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-----`)) // GlobalSign Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw @@ -2167,10 +2237,10 @@ yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GlobalSign Root E46 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw @@ -2182,10 +2252,10 @@ yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ 7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 +RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GlobalSign Root R46 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy @@ -2215,10 +2285,10 @@ u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP 4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GlobalSign Secure Mail Root E45 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICITCCAaegAwIBAgIQdlP+qicdlUZd1vGe5biQCjAKBggqhkjOPQQDAzBSMQsw CQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UEAxMf R2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IEU0NTAeFw0yMDAzMTgwMDAwMDBa @@ -2231,10 +2301,10 @@ DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3xNei1/CQAL9VreUTLYe1aaxFJYw CgYIKoZIzj0EAwMDaAAwZQIwE7C+13EgPuSrnM42En1fTB8qtWlFM1/TLVqy5IjH 3go2QjJ5naZruuH5RCp7isMSAjEAoGYcToedh8ntmUwbCu4tYMM3xx3NtXKw2cbv vPL/P/BS3QjnqmR5w+RpV5EvpMt8 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GlobalSign Secure Mail Root R45 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFcDCCA1igAwIBAgIQdlP+qExQq5+NMrUdA49X3DANBgkqhkiG9w0BAQwFADBS MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UE AxMfR2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IFI0NTAeFw0yMDAzMTgwMDAw @@ -2265,10 +2335,10 @@ l+6NJllXu3jwelAwCbBgqp9O3Mk+HjrcYpMzsDpUdG8sMUXRaxEyamh29j32ahNe JJjn6h2az3iCB2D3TRDTgZpFjZ6vm9yAx0OylWikww7oCkcVv1Qz3AHn1aYec9h6 sr8vreNVMJ7fDkG84BH1oQyoIuHjAKNOcHyS4wTRekKKdZBZ45vRTKJkvXN5m2/y s8H2PA== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Go Daddy Class 2 Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 @@ -2291,10 +2361,10 @@ TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf ReYNnyicsbkqWletNw+vHX/bvZ8= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Go Daddy Root Certificate Authority - G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp @@ -2316,10 +2386,10 @@ gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo 2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI 4uJEvlz36hz1 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Starfield Class 2 Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw @@ -2342,10 +2412,10 @@ eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Starfield Root Certificate Authority - G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs @@ -2367,10 +2437,10 @@ sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GlobalSign - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw @@ -2381,10 +2451,10 @@ uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ +wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GTS Root R1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw @@ -2414,10 +2484,10 @@ Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT 0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm 2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GTS Root R2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw @@ -2447,10 +2517,10 @@ TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV 7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl 6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GTS Root R3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw @@ -2462,10 +2532,10 @@ jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // GTS Root R4 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw @@ -2477,10 +2547,10 @@ HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D 9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Hongkong Post Root CA 3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n @@ -2513,10 +2583,10 @@ JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG mpv0 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // ACCVRAIZ1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ @@ -2559,10 +2629,10 @@ I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // AC RAIZ FNMT-RCM - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ @@ -2593,10 +2663,10 @@ fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp 9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // AC RAIZ FNMT-RCM SERVIDORES SEGUROS - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S @@ -2611,10 +2681,10 @@ VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy v+c= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Staat der Nederlanden Root CA - G3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX @@ -2645,10 +2715,10 @@ fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq 1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM 94B7IWcnMFk= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w @@ -2673,10 +2743,10 @@ IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c 8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // HARICA Client ECC Root CA 2021 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICWjCCAeGgAwIBAgIQMWjZ2OFiVx7SGUSI5hB98DAKBggqhkjOPQQDAzBvMQsw CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh cmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBFQ0Mg @@ -2690,10 +2760,10 @@ AQH/BAUwAwEB/zAdBgNVHQ4EFgQUUgjSvjKBJf31GpfsTl8au1PNkK0wDgYDVR0P AQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMEwxRUZPqOa+w3eyGhhLLYh7WOar lGtEA7AX/9+Cc0RRLP2THQZ7FNKJ7EAM7yEBLgIwL8kuWmwsHdmV4J6wuVxSfPb4 OMou8dQd8qJJopX4wVheT/5zCu8xsKsjWBOMi947 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // HARICA Client RSA Root CA 2021 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFqjCCA5KgAwIBAgIQVVL4HtsbJCyeu5YYzQIoPjANBgkqhkiG9w0BAQsFADBv MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBS @@ -2725,10 +2795,10 @@ ndSV9ZmqRuqurL/0FBkk6Izs4/W8BmiKKgwFXwqXdafcfsD913oY3zDROEsfsJhw v8x8c/BuxDGlpJcdrL/ObCFKvicjZ/MGVoEKkY624QMFMyzaNAhNTlAjrR+lxdR6 /uoJ7KcoYItGfLXqm91P+edrFcaIz0Pb5SfcBFZub0YV8VYt6FwMc8MjgTggy8kM ac8sqzuEYDMZUv1pFDM= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // HARICA TLS ECC Root CA 2021 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v @@ -2742,10 +2812,10 @@ AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // HARICA TLS RSA Root CA 2021 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg @@ -2777,10 +2847,10 @@ BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU 63ZTGI0RmLo= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Hellenic Academic and Research Institutions ECC RootCA 2015 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl @@ -2796,10 +2866,10 @@ MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Hellenic Academic and Research Institutions RootCA 2015 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT @@ -2833,10 +2903,10 @@ bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 vm9qp/UsQu0yrbYhnr68 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // IdenTrust Commercial Root CA 1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw @@ -2866,10 +2936,10 @@ l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG 4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A 7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // IdenTrust Public Sector Root CA 1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN @@ -2899,10 +2969,10 @@ WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv 8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // ISRG Root X1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 @@ -2932,10 +3002,10 @@ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // ISRG Root X2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 @@ -2948,10 +3018,10 @@ AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 /q4AaOeMSQ+2b1tbFfLn ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Izenpe.com - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD @@ -2984,10 +3054,10 @@ UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SZAFIR ROOT CA2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw @@ -3007,10 +3077,10 @@ nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // LAWtrust Root CA2 (4096) - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFmDCCA4CgAwIBAgIEVRpusTANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJa QTERMA8GA1UEChMITEFXdHJ1c3QxITAfBgNVBAMTGExBV3RydXN0IFJvb3QgQ0Ey ICg0MDk2KTAgFw0yMzAyMTQwOTE5MzhaGA8yMDUzMDIxNDA5NDkzOFowQzELMAkG @@ -3041,10 +3111,10 @@ NAdigkz0fseHdA6wIR4JIIDBsxU9Rm3T8QaSP++glYocbncxtut4KQx77oKlT36k KV6eqi34jsDz/A0GhZtO3PfiCXzQFFEeerMjr/rRYSpltQHZuOMHyiR20vBKvu+G BIBCFXARaH7Xx7v+506bnJWlHEqkydAJjKrOSNIekpfXEentZsw33PXXG3SbpupC rF0y4Fj0gUf/0hLifhzcSXaWwx2fS8pcKjdbPYrROJsh2uO/RUPT4Fh3Hyg= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // e-Szigno Root CA 2017 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv @@ -3058,10 +3128,10 @@ A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ +efcMQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Microsec e-Szigno Root CA 2009 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G @@ -3084,10 +3154,10 @@ ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c 2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Microsoft ECC Root Certificate Authority 2017 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw @@ -3101,10 +3171,10 @@ BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Microsoft RSA Root Certificate Authority 2017 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 @@ -3136,10 +3206,10 @@ GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB RA+GsCyRxj3qrg+E ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // NAVER Global Root Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 @@ -3171,10 +3241,10 @@ LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX 5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul 9XXeifdy ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // NetLock Arany (Class Gold) Főtanúsítvány - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl @@ -3197,10 +3267,10 @@ bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // OISTE WISeKey Global Root GA CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl @@ -3223,10 +3293,10 @@ vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ /L7fCg0= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // OISTE WISeKey Global Root GB CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i @@ -3247,10 +3317,10 @@ gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // OISTE WISeKey Global Root GC CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg @@ -3264,10 +3334,10 @@ BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV 57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // QuoVadis Root CA 1 G3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 @@ -3297,10 +3367,10 @@ NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // QuoVadis Root CA 2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV @@ -3332,10 +3402,10 @@ mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y 4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza 8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // QuoVadis Root CA 2 G3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 @@ -3365,10 +3435,10 @@ dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // QuoVadis Root CA 3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV @@ -3405,10 +3475,10 @@ zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK 4SVhM7JZG+Ju1zdXtg2pEto= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // QuoVadis Root CA 3 G3 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 @@ -3438,10 +3508,10 @@ u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Security Communication ECC RootCA1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx @@ -3454,10 +3524,10 @@ ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu 9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Security Communication RootCA2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX @@ -3477,10 +3547,10 @@ okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy 1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // AAA Certificate Services - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj @@ -3504,10 +3574,10 @@ Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // COMODO Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV @@ -3531,10 +3601,10 @@ RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB ZQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // COMODO ECC Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT @@ -3549,10 +3619,10 @@ BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // COMODO RSA Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV @@ -3585,10 +3655,10 @@ S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl 0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB NVOFBkpdn627G190 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Sectigo Public Email Protection Root E46 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICMTCCAbegAwIBAgIQbvXTp0GOoFlApzBr0kBlVjAKBggqhkjOPQQDAzBaMQsw CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQDEyhT ZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgRTQ2MB4XDTIxMDMy @@ -3601,10 +3671,10 @@ HQYDVR0OBBYEFC1OjKfCI7JXqQZrPmsrifPDXkfOMA4GA1UdDwEB/wQEAwIBhjAP BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCSnRpZY0VYjhsW5H16 bDZIMB8rcueQMzT9JKLGBoxvOzJXWvj+xkkSU5rZELKZUXICMAUlKjMh/JPmIqLM cFUoNVaiB8QhhCMaTEyZUJmSFMtK3Fb79dOPaiz1cTr4izsDng== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Sectigo Public Email Protection Root R46 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFgDCCA2igAwIBAgIQHUSeuQ2DkXSu3fLriLemozANBgkqhkiG9w0BAQwFADBa MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQD EyhTZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgUjQ2MB4XDTIx @@ -3635,10 +3705,10 @@ IS+0AlmzLdBykLoLFHR4S8/BX1VyjlQrE876WAzTuyzZqZFh+PjxtnvevKnMkgTM S2tfc4C2Ie1QT9d2h27O39K3vWKhfVhiaEVStj/eEtvtBGmedoiqAW3ahsdgG8NS rDfsUHGAciohRQpTRzwZ643SWQTeJbDrHzVvYH3Xtca7CyeN4E1U5c8dJgFuOzXI IBKJg/DS7Vg7NJ27MfUy/THzVho= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Sectigo Public Server Authentication Root E46 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN @@ -3651,10 +3721,10 @@ WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q 4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Sectigo Public Server Authentication Root R46 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw @@ -3685,10 +3755,10 @@ dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp 0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // USERTrust ECC Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT @@ -3703,10 +3773,10 @@ A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // USERTrust RSA Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV @@ -3739,10 +3809,10 @@ qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG jjxDah2nGN59PRbxYvnKkKj9 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SSL.com Client ECC Root CA 2022 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICQDCCAcagAwIBAgIQdvhIHq7wPHAf4D8lVAGD1TAKBggqhkjOPQQDAzBRMQsw CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQDDB9T U0wuY29tIENsaWVudCBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzAzMloX @@ -3756,10 +3826,10 @@ U81SGi9dYKDDXfuyHBwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUC ME0HES0R+7kmwyHdcuEX/MHPFOpJznGHjtZT3BHNXVSKr9kt9IxR6rxmR+J/lYNg ZQIxAIwhTE+75bBQ35BiSebMkdv4P11xkQiOT5LJf6Zc6hN+7W3E6MMqb1wR4aXz alqaTQ== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SSL.com Client RSA Root CA 2022 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFjzCCA3egAwIBAgIQdq/uiJMVRbZQU5uAnKTfmjANBgkqhkiG9w0BAQsFADBR MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQD DB9TU0wuY29tIENsaWVudCBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzEw @@ -3790,10 +3860,10 @@ miZPPiDPT0PzEIx/EGF6NsqqC33Mn0dEWa6llcaZU+MHaz1JELAY/10OhUMUS+dr OdmsupJ13UPKugGVrRqBKzrw48UvDBhNEMauwO3+BVJ/GQXLqa81CAw4IuT+VuVT Pr/k1rPZCMM91TMygSTFqeFlEbgyMzBxGEkdGkXGmhSKWDkobvPLUblJJmR4A8eR EYOpuZA0tm+qBZ6FKFeZvn8nBkliTaH8CeErRglMFJtWj0U= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SSL.com EV Root Certification Authority ECC - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp @@ -3808,10 +3878,10 @@ MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX 5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SSL.com EV Root Certification Authority RSA R2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy @@ -3844,10 +3914,10 @@ QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SSL.com Root Certification Authority ECC - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 @@ -3862,10 +3932,10 @@ EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SSL.com Root Certification Authority RSA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp @@ -3898,10 +3968,10 @@ K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY Ic2wBlX7Jz9TkHCpBB5XJ7k= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SSL.com TLS ECC Root CA 2022 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 @@ -3914,10 +3984,10 @@ GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp 15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SSL.com TLS RSA Root CA 2022 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX @@ -3948,10 +4018,10 @@ AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU 98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SwissSign Gold CA - G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF @@ -3983,45 +4053,10 @@ hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ ------END CERTIFICATE-----'`)) - - // SwissSign Silver CA - G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- -MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE -BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu -IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow -RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY -U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A -MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv -Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br -YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF -nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH -6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt -eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ -c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ -MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH -HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf -jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 -5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB -rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU -F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c -wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 -cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB -AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp -WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 -xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ -2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ -IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 -aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X -em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR -dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ -OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ -hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy -tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // TWCA CYBER Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 @@ -4052,10 +4087,10 @@ Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // TWCA Global Root CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 @@ -4085,10 +4120,10 @@ nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy KwbQBM0= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // TWCA Global Root CA G2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFlTCCA32gAwIBAgIQQAE0jMIAAAAAAAAAAZdY9DANBgkqhkiG9w0BAQwFADBU MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 IENBMR8wHQYDVQQDExZUV0NBIEdsb2JhbCBSb290IENBIEcyMB4XDTIyMTEyMjA2 @@ -4119,10 +4154,10 @@ UamsVsAhTqMUdAU6vOO/bT1OP16lpG0pv4RRdVOOhhr1UXAqDRxOQOH9o+OlK2eQ ksdsroW/OpsXFcqcKpPUTTkNvCAIo42IbAkNjK5EIU3JcezYJtcXni0RGDyjIn24 J1S/aMg7QsyPXk7n3MLF+mpED41WiHrfiYRsoLM+PfFlAAmI6irrQM6zXawyF67B m+nQwfVJlN2nznxaB+uuIJwXMJJpk3Lzmltxm/5q33owaY6zLtsPLN0= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // TWCA Root Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz @@ -4142,10 +4177,10 @@ XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Telia Root CA v2 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 @@ -4176,10 +4211,10 @@ Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA rBPuUBQemMc= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // TeliaSonera Root CA v1 - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD @@ -4208,10 +4243,10 @@ vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Secure Global CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx @@ -4232,10 +4267,10 @@ H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // SecureTrust CA - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz @@ -4256,10 +4291,10 @@ NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR 3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Trustwave Global Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 @@ -4292,10 +4327,10 @@ VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK yeC2nOnOcXHebD8WpHk= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Trustwave Global ECC P256 Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 @@ -4309,10 +4344,10 @@ FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // Trustwave Global ECC P384 Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 @@ -4328,10 +4363,10 @@ A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu Sw== ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) // XRamp Global Certification Authority - mozillaIncluded.AppendCertsFromPEM([]byte(`'-----BEGIN CERTIFICATE----- + mozillaIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY @@ -4355,5 +4390,5 @@ qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ O+7ETPTsJ3xCwnR8gooJybQDJbw= ------END CERTIFICATE-----'`)) +-----END CERTIFICATE-----`)) } From f18889369f9d47ac3a4033c2d07622bbb51a45fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 16 Feb 2025 11:49:24 +0800 Subject: [PATCH 0369/1272] Add back port hopping to hysteria 1 --- docs/configuration/outbound/hysteria.md | 29 ++++++++++++++++++++++ docs/configuration/outbound/hysteria.zh.md | 29 ++++++++++++++++++++++ option/hysteria.go | 7 +++++- protocol/hysteria/outbound.go | 24 ++++++++++-------- 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/docs/configuration/outbound/hysteria.md b/docs/configuration/outbound/hysteria.md index 90190e051f..b326e06dcd 100644 --- a/docs/configuration/outbound/hysteria.md +++ b/docs/configuration/outbound/hysteria.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [server_ports](#server_ports) + :material-plus: [hop_interval](#hop_interval) + ### Structure ```json @@ -7,6 +16,10 @@ "server": "127.0.0.1", "server_port": 1080, + "server_ports": [ + "2080:3000" + ], + "hop_interval": "", "up": "100 Mbps", "up_mbps": 100, "down": "100 Mbps", @@ -38,6 +51,22 @@ The server address. The server port. +#### server_ports + +!!! question "Since sing-box 1.12.0" + +Server port range list. + +Conflicts with `server_port`. + +#### hop_interval + +!!! question "Since sing-box 1.12.0" + +Port hopping interval. + +`30s` is used by default. + #### up, down ==Required== diff --git a/docs/configuration/outbound/hysteria.zh.md b/docs/configuration/outbound/hysteria.zh.md index c6ee2313f0..4f4c00bb75 100644 --- a/docs/configuration/outbound/hysteria.zh.md +++ b/docs/configuration/outbound/hysteria.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [server_ports](#server_ports) + :material-plus: [hop_interval](#hop_interval) + ### 结构 ```json @@ -7,6 +16,10 @@ "server": "127.0.0.1", "server_port": 1080, + "server_ports": [ + "2080:3000" + ], + "hop_interval": "", "up": "100 Mbps", "up_mbps": 100, "down": "100 Mbps", @@ -38,6 +51,22 @@ 服务器端口。 +#### server_ports + +!!! question "自 sing-box 1.12.0 起" + +服务器端口范围列表。 + +与 `server_port` 冲突。 + +#### hop_interval + +!!! question "自 sing-box 1.12.0 起" + +端口跳跃间隔。 + +默认使用 `30s`。 + #### up, down ==必填== diff --git a/option/hysteria.go b/option/hysteria.go index 6332be11bd..186759010e 100644 --- a/option/hysteria.go +++ b/option/hysteria.go @@ -1,6 +1,9 @@ package option -import "github.com/sagernet/sing/common/byteformats" +import ( + "github.com/sagernet/sing/common/byteformats" + "github.com/sagernet/sing/common/json/badoption" +) type HysteriaInboundOptions struct { ListenOptions @@ -26,6 +29,8 @@ type HysteriaUser struct { type HysteriaOutboundOptions struct { DialerOptions ServerOptions + ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"` + HopInterval badoption.Duration `json:"hop_interval,omitempty"` Up *byteformats.NetworkBytesCompat `json:"up,omitempty"` UpMbps int `json:"up_mbps,omitempty"` Down *byteformats.NetworkBytesCompat `json:"down,omitempty"` diff --git a/protocol/hysteria/outbound.go b/protocol/hysteria/outbound.go index 7e20b005f8..42a37ee6c1 100644 --- a/protocol/hysteria/outbound.go +++ b/protocol/hysteria/outbound.go @@ -4,6 +4,7 @@ import ( "context" "net" "os" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" @@ -69,17 +70,18 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps } client, err := hysteria.NewClient(hysteria.ClientOptions{ - Context: ctx, - Dialer: outboundDialer, - Logger: logger, - ServerAddress: options.ServerOptions.Build(), - SendBPS: sendBps, - ReceiveBPS: receiveBps, - XPlusPassword: options.Obfs, - Password: password, - TLSConfig: tlsConfig, - UDPDisabled: !common.Contains(networkList, N.NetworkUDP), - + Context: ctx, + Dialer: outboundDialer, + Logger: logger, + ServerAddress: options.ServerOptions.Build(), + ServerPorts: options.ServerPorts, + HopInterval: time.Duration(options.HopInterval), + SendBPS: sendBps, + ReceiveBPS: receiveBps, + XPlusPassword: options.Obfs, + Password: password, + TLSConfig: tlsConfig, + UDPDisabled: !common.Contains(networkList, N.NetworkUDP), ConnReceiveWindow: options.ReceiveWindowConn, StreamReceiveWindow: options.ReceiveWindow, DisableMTUDiscovery: options.DisableMTUDiscovery, From c1189e2a7b1f39cd341499fae536ef31d7d6561d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 20 Feb 2025 15:13:14 +0800 Subject: [PATCH 0370/1272] Improve resolve action --- docs/configuration/dns/rule_action.md | 4 +-- docs/configuration/dns/rule_action.zh.md | 4 +-- docs/configuration/route/rule_action.md | 36 ++++++++++++++++--- docs/configuration/route/rule_action.zh.md | 31 +++++++++++++++-- option/rule_action.go | 7 ++-- route/route.go | 7 ++-- route/rule/rule_action.go | 40 ++++++++++++++++------ 7 files changed, 103 insertions(+), 26 deletions(-) diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index c3f8c2cb57..33c283fa14 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -16,7 +16,7 @@ icon: material/new-box "server": "", "strategy": "", "disable_cache": false, - "rewrite_ttl": 0, + "rewrite_ttl": null, "client_subnet": null } ``` @@ -49,7 +49,7 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. -Will overrides `dns.client_subnet` and `servers.[].client_subnet`. +Will overrides `dns.client_subnet`. ### route-options diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 427f8a8d77..03843ec5d7 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -17,7 +17,7 @@ icon: material/new-box "strategy": "", "disable_cache": false, - "rewrite_ttl": 0, + "rewrite_ttl": null, "client_subnet": null } ``` @@ -50,7 +50,7 @@ icon: material/new-box 如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 -将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 +将覆盖 `dns.client_subnet`. ### route-options diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 567b9eb69c..0f04b1659d 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -5,7 +5,10 @@ icon: material/new-box !!! quote "Changes in sing-box 1.12.0" :material-plus: [tls_fragment](#tls_fragment) - :material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay) + :material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay) + :material-plus: [resolve.disable_cache](#disable_cache) + :material-plus: [resolve.rewrite_ttl](#rewrite_ttl) + :material-plus: [resolve.client_subnet](#client_subnet) ## Final actions @@ -210,19 +213,44 @@ Timeout for sniffing. ```json { "action": "resolve", + "server": "", "strategy": "", - "server": "" + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null } ``` `resolve` resolve request destination from domain to IP addresses. +#### server + +Specifies DNS server tag to use instead of selecting through DNS routing. + #### strategy DNS resolution strategy, available values are: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. `dns.strategy` will be used by default. -#### server +#### disable_cache -Specifies DNS server tag to use instead of selecting through DNS routing. +!!! question "Since sing-box 1.12.0" + +Disable cache and save cache in this query. + +#### rewrite_ttl + +!!! question "Since sing-box 1.12.0" + +Rewrite TTL in DNS responses. + +#### client_subnet + +!!! question "Since sing-box 1.12.0" + +Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. + +If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. + +Will overrides `dns.client_subnet`. diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index a8eca8a24d..f0c9161029 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -206,19 +206,44 @@ UDP 连接超时时间。 ```json { "action": "resolve", + "server": "", "strategy": "", - "server": "" + "disable_cache": false, + "rewrite_ttl": null, + "client_subnet": null } ``` `resolve` 将请求的目标从域名解析为 IP 地址。 +#### server + +指定要使用的 DNS 服务器的标签,而不是通过 DNS 路由进行选择。 + #### strategy DNS 解析策略,可用值有:`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、`ipv6_only`。 默认使用 `dns.strategy`。 -#### server +#### disable_cache -指定要使用的 DNS 服务器的标签,而不是通过 DNS 路由进行选择。 +!!! question "自 sing-box 1.12.0 起" + +在此查询中禁用缓存。 + +#### rewrite_ttl + +!!! question "自 sing-box 1.12.0 起" + +重写 DNS 回应中的 TTL。 + +#### client_subnet + +!!! question "自 sing-box 1.12.0 起" + +默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 + +将覆盖 `dns.client_subnet`. diff --git a/option/rule_action.go b/option/rule_action.go index f07d729872..00d3ae7a4d 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -288,6 +288,9 @@ type RouteActionSniff struct { } type RouteActionResolve struct { - Strategy DomainStrategy `json:"strategy,omitempty"` - Server string `json:"server,omitempty"` + Server string `json:"server,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } diff --git a/route/route.go b/route/route.go index e70d57b409..df71f9efc2 100644 --- a/route/route.go +++ b/route/route.go @@ -664,8 +664,11 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon } } addresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{ - Transport: transport, - Strategy: action.Strategy, + Transport: transport, + Strategy: action.Strategy, + DisableCache: action.DisableCache, + RewriteTTL: action.RewriteTTL, + ClientSubnet: action.ClientSubnet, }) if err != nil { return err diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 2b1c66d013..2599d064f2 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -89,8 +89,11 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti return sniffAction, sniffAction.build() case C.RuleActionTypeResolve: return &RuleActionResolve{ - Strategy: C.DomainStrategy(action.ResolveOptions.Strategy), - Server: action.ResolveOptions.Server, + Server: action.ResolveOptions.Server, + Strategy: C.DomainStrategy(action.ResolveOptions.Strategy), + DisableCache: action.ResolveOptions.DisableCache, + RewriteTTL: action.ResolveOptions.RewriteTTL, + ClientSubnet: action.ResolveOptions.ClientSubnet.Build(netip.Prefix{}), }, nil default: panic(F.ToString("unknown rule action: ", action.Action)) @@ -397,8 +400,11 @@ func (r *RuleActionSniff) String() string { } type RuleActionResolve struct { - Strategy C.DomainStrategy - Server string + Server string + Strategy C.DomainStrategy + DisableCache bool + RewriteTTL *uint32 + ClientSubnet netip.Prefix } func (r *RuleActionResolve) Type() string { @@ -406,13 +412,25 @@ func (r *RuleActionResolve) Type() string { } func (r *RuleActionResolve) String() string { - if r.Strategy == C.DomainStrategyAsIS && r.Server == "" { - return F.ToString("resolve") - } else if r.Strategy != C.DomainStrategyAsIS && r.Server == "" { - return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ")") - } else if r.Strategy == C.DomainStrategyAsIS && r.Server != "" { - return F.ToString("resolve(", r.Server, ")") + var options []string + if r.Server != "" { + options = append(options, r.Server) + } + if r.Strategy != C.DomainStrategyAsIS { + options = append(options, F.ToString(option.DomainStrategy(r.Strategy))) + } + if r.DisableCache { + options = append(options, "disable_cache") + } + if r.RewriteTTL != nil { + options = append(options, F.ToString("rewrite_ttl=", *r.RewriteTTL)) + } + if r.ClientSubnet.IsValid() { + options = append(options, F.ToString("client_subnet=", r.ClientSubnet)) + } + if len(options) == 0 { + return "resolve" } else { - return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ",", r.Server, ")") + return F.ToString("resolve(", strings.Join(options, ","), ")") } } From 5c4211e849eb2ac8b68b65f4585ec783379b817f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 20 Feb 2025 16:40:53 +0800 Subject: [PATCH 0371/1272] Get darwin local DNS server from libresolv --- dns/transport/local/local.go | 1 + dns/transport/local/resolv_darwin_cgo.go | 53 ++++++++++++++++++++++++ dns/transport/local/resolv_default.go | 23 ++++++++++ dns/transport/local/resolv_unix.go | 30 +++----------- dns/transport_manager.go | 4 +- protocol/tun/hook.go | 3 ++ protocol/tun/inbound.go | 3 ++ 7 files changed, 90 insertions(+), 27 deletions(-) create mode 100644 dns/transport/local/resolv_darwin_cgo.go create mode 100644 dns/transport/local/resolv_default.go create mode 100644 protocol/tun/hook.go diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index e5e8aef97a..7f02c6083b 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -27,6 +27,7 @@ var _ adapter.DNSTransport = (*Transport)(nil) type Transport struct { dns.TransportAdapter + ctx context.Context hosts *hosts.File dialer N.Dialer } diff --git a/dns/transport/local/resolv_darwin_cgo.go b/dns/transport/local/resolv_darwin_cgo.go new file mode 100644 index 0000000000..dfcb8a8abf --- /dev/null +++ b/dns/transport/local/resolv_darwin_cgo.go @@ -0,0 +1,53 @@ +//go:build darwin && cgo + +package local + +/* +#include +#include +#include +#include +*/ +import "C" + +import ( + "time" + + E "github.com/sagernet/sing/common/exceptions" + + "github.com/miekg/dns" +) + +func dnsReadConfig(_ string) *dnsConfig { + if C.res_init() != 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(C._res.retry), + } + for i := 0; i < int(C._res.nscount); i++ { + ns := C._res.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 := C._res.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_default.go b/dns/transport/local/resolv_default.go new file mode 100644 index 0000000000..0a7d881004 --- /dev/null +++ b/dns/transport/local/resolv_default.go @@ -0,0 +1,23 @@ +package local + +import ( + "os" + "strings" + _ "unsafe" + + "github.com/miekg/dns" +) + +//go:linkname defaultNS net.defaultNS +var defaultNS []string + +func dnsDefaultSearch() []string { + hn, err := os.Hostname() + if err != nil { + return nil + } + if i := strings.IndexRune(hn, '.'); i >= 0 && i < len(hn)-1 { + return []string{dns.Fqdn(hn[i+1:])} + } + return nil +} diff --git a/dns/transport/local/resolv_unix.go b/dns/transport/local/resolv_unix.go index 6594ae4100..c395314539 100644 --- a/dns/transport/local/resolv_unix.go +++ b/dns/transport/local/resolv_unix.go @@ -1,4 +1,4 @@ -//go:build !windows +//go:build !windows && !(darwin && cgo) package local @@ -9,7 +9,8 @@ import ( "os" "strings" "time" - _ "unsafe" + + "github.com/miekg/dns" ) func dnsReadConfig(name string) *dnsConfig { @@ -69,13 +70,13 @@ func dnsReadConfig(name string) *dnsConfig { } case "domain": if len(f) > 1 { - conf.search = []string{ensureRooted(f[1])} + conf.search = []string{dns.Fqdn(f[1])} } case "search": conf.search = make([]string, 0, len(f)-1) for i := 1; i < len(f); i++ { - name := ensureRooted(f[i]) + name := dns.Fqdn(f[i]) if name == "." { continue } @@ -137,27 +138,6 @@ func dnsReadConfig(name string) *dnsConfig { return conf } -//go:linkname defaultNS net.defaultNS -var defaultNS []string - -func dnsDefaultSearch() []string { - hn, err := os.Hostname() - if err != nil { - return nil - } - if i := strings.IndexRune(hn, '.'); i >= 0 && i < len(hn)-1 { - return []string{ensureRooted(hn[i+1:])} - } - return nil -} - -func ensureRooted(s string) string { - if len(s) > 0 && s[len(s)-1] == '.' { - return s - } - return s + "." -} - const big = 0xFFFFFF func dtoi(s string) (n int, i int, ok bool) { diff --git a/dns/transport_manager.go b/dns/transport_manager.go index 4497923b46..8666a1b9f7 100644 --- a/dns/transport_manager.go +++ b/dns/transport_manager.go @@ -56,12 +56,12 @@ func (m *TransportManager) Start(stage adapter.StartStage) error { } m.started = true m.stage = stage - outbounds := m.transports + transports := m.transports m.access.Unlock() if stage == adapter.StartStateStart { return m.startTransports(m.transports) } else { - for _, outbound := range outbounds { + for _, outbound := range transports { err := adapter.LegacyStart(outbound, stage) if err != nil { return E.Cause(err, stage, " dns/", outbound.Type(), "[", outbound.Tag(), "]") diff --git a/protocol/tun/hook.go b/protocol/tun/hook.go new file mode 100644 index 0000000000..1afa643f2d --- /dev/null +++ b/protocol/tun/hook.go @@ -0,0 +1,3 @@ +package tun + +var HookBeforeCreatePlatformInterface func() diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index ff02655316..7c80ebba20 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -359,6 +359,9 @@ func (t *Inbound) Start(stage adapter.StartStage) error { if t.platformInterface != nil { tunInterface, err = t.platformInterface.OpenTun(&tunOptions, t.platformOptions) } else { + if HookBeforeCreatePlatformInterface != nil { + HookBeforeCreatePlatformInterface() + } tunInterface, err = tun.New(tunOptions) } monitor.Finish() From 9a5f69f4352a18d15124d6c8fcd20da681fb9942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 20 Feb 2025 17:18:47 +0800 Subject: [PATCH 0372/1272] Add fallback local DNS server for iOS --- common/tls/ech_client.go | 2 +- dns/client.go | 78 +++++++++- dns/rcode.go | 34 ++--- dns/router.go | 4 +- dns/transport/local/local.go | 4 - dns/transport/local/local_fallback.go | 201 ++++++++++++++++++++++++++ dns/transport/local/resolv_windows.go | 3 - dns/transport/predefined.go | 2 +- experimental/libbox/dns.go | 2 +- protocol/dns/handle.go | 2 +- protocol/tailscale/dns_transport.go | 2 +- 11 files changed, 291 insertions(+), 43 deletions(-) create mode 100644 dns/transport/local/local_fallback.go diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go index 8021935049..9c3744b4a1 100644 --- a/common/tls/ech_client.go +++ b/common/tls/ech_client.go @@ -221,7 +221,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam return nil, err } if response.Rcode != mDNS.RcodeSuccess { - return nil, dns.RCodeError(response.Rcode) + return nil, dns.RcodeError(response.Rcode) } for _, rr := range response.Answer { switch resource := rr.(type) { diff --git a/dns/client.go b/dns/client.go index 79b6fce574..d44883b54a 100644 --- a/dns/client.go +++ b/dns/client.go @@ -17,7 +17,7 @@ import ( "github.com/sagernet/sing/contrab/freelru" "github.com/sagernet/sing/contrab/maphash" - "github.com/miekg/dns" + dns "github.com/miekg/dns" ) var ( @@ -484,7 +484,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) { if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError { - return nil, RCodeError(response.Rcode) + return nil, RcodeError(response.Rcode) } addresses := make([]netip.Addr, 0, len(response.Answer)) for _, rawAnswer := range response.Answer { @@ -508,10 +508,10 @@ func wrapError(err error) error { switch dnsErr := err.(type) { case *net.DNSError: if dnsErr.IsNotFound { - return RCodeNameError + return RcodeNameError } case *net.AddrError: - return RCodeNameError + return RcodeNameError } return err } @@ -561,3 +561,73 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim } return &response } + +func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToLive uint32) *dns.Msg { + response := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: id, + Rcode: dns.RcodeSuccess, + Response: true, + }, + Question: []dns.Question{question}, + Answer: []dns.RR{ + &dns.CNAME{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeCNAME, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + Target: record, + }, + }, + } + return &response +} + +func FixedResponseTXT(id uint16, question dns.Question, records []string, timeToLive uint32) *dns.Msg { + response := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: id, + Rcode: dns.RcodeSuccess, + Response: true, + }, + Question: []dns.Question{question}, + Answer: []dns.RR{ + &dns.TXT{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + Txt: records, + }, + }, + } + return &response +} + +func FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeToLive uint32) *dns.Msg { + response := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: id, + Rcode: dns.RcodeSuccess, + Response: true, + }, + Question: []dns.Question{question}, + } + for _, record := range records { + response.Answer = append(response.Answer, &dns.MX{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + Preference: record.Pref, + Mx: record.Host, + }) + } + return &response +} diff --git a/dns/rcode.go b/dns/rcode.go index 5b7e52ccb8..085454741e 100644 --- a/dns/rcode.go +++ b/dns/rcode.go @@ -1,33 +1,17 @@ package dns -import F "github.com/sagernet/sing/common/format" +import ( + mDNS "github.com/miekg/dns" +) const ( - RCodeSuccess RCodeError = 0 // NoError - RCodeFormatError RCodeError = 1 // FormErr - RCodeServerFailure RCodeError = 2 // ServFail - RCodeNameError RCodeError = 3 // NXDomain - RCodeNotImplemented RCodeError = 4 // NotImp - RCodeRefused RCodeError = 5 // Refused + RcodeFormatError RcodeError = mDNS.RcodeFormatError + RcodeNameError RcodeError = mDNS.RcodeNameError + RcodeRefused RcodeError = mDNS.RcodeRefused ) -type RCodeError uint16 +type RcodeError int -func (e RCodeError) Error() string { - switch e { - case RCodeSuccess: - return "success" - case RCodeFormatError: - return "format error" - case RCodeServerFailure: - return "server failure" - case RCodeNameError: - return "name error" - case RCodeNotImplemented: - return "not implemented" - case RCodeRefused: - return "refused" - default: - return F.ToString("unknown error: ", uint16(e)) - } +func (e RcodeError) Error() string { + return mDNS.RcodeToString[int(e)] } diff --git a/dns/router.go b/dns/router.go index f79aae8517..dd6b8780e2 100644 --- a/dns/router.go +++ b/dns/router.go @@ -336,13 +336,13 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ } } else if len(responseAddrs) == 0 { r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") - err = RCodeNameError + err = RcodeNameError } } responseAddrs, cached = r.client.LookupCache(domain, options.Strategy) if cached { if len(responseAddrs) == 0 { - return nil, RCodeNameError + return nil, RcodeNameError } return responseAddrs, nil } diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index 7f02c6083b..e4236275d6 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -19,10 +19,6 @@ import ( 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 { diff --git a/dns/transport/local/local_fallback.go b/dns/transport/local/local_fallback.go new file mode 100644 index 0000000000..0a7cd9f0a1 --- /dev/null +++ b/dns/transport/local/local_fallback.go @@ -0,0 +1,201 @@ +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 + } + return &FallbackTransport{ + DNSTransport: transport, + ctx: ctx, + }, nil +} + +func (f *FallbackTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + platformInterface := service.FromContext[platform.Interface](f.ctx) + if platformInterface == nil { + 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) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + if f.fallback { + return f.DNSTransport.Exchange(ctx, message) + } + question := message.Question[0] + domain := dns.FqdnToDomain(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 := f.resolver.LookupNetIP(ctx, network, domain) + 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, domain) + 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, domain) + 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, domain) + 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, domain) + 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/resolv_windows.go b/dns/transport/local/resolv_windows.go index 577e7a129e..f6b810901f 100644 --- a/dns/transport/local/resolv_windows.go +++ b/dns/transport/local/resolv_windows.go @@ -69,9 +69,6 @@ func dnsReadConfig(_ string) *dnsConfig { return conf } -//go:linkname defaultNS net.defaultNS -var defaultNS []string - func adapterAddresses() ([]*windows.IpAdapterAddresses, error) { var b []byte l := uint32(15000) // recommended initial size diff --git a/dns/transport/predefined.go b/dns/transport/predefined.go index 3f11288681..dbb78e5c5d 100644 --- a/dns/transport/predefined.go +++ b/dns/transport/predefined.go @@ -79,5 +79,5 @@ func (t *PredefinedTransport) Exchange(ctx context.Context, message *mDNS.Msg) ( } } } - return nil, dns.RCodeNameError + return nil, dns.RcodeNameError } diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index a7ccd2a2e6..7e143442ad 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -134,7 +134,7 @@ func (c *ExchangeContext) RawSuccess(result []byte) { } func (c *ExchangeContext) ErrorCode(code int32) { - c.error = dns.RCodeError(code) + c.error = dns.RcodeError(code) } func (c *ExchangeContext) ErrnoCode(code int32) { diff --git a/protocol/dns/handle.go b/protocol/dns/handle.go index c4ad79d9ca..765e5051b9 100644 --- a/protocol/dns/handle.go +++ b/protocol/dns/handle.go @@ -26,7 +26,7 @@ func HandleStreamDNSRequest(ctx context.Context, router adapter.DNSRouter, conn return err } if queryLength == 0 { - return dns.RCodeFormatError + return dns.RcodeFormatError } buffer := buf.NewSize(int(queryLength)) defer buffer.Release() diff --git a/protocol/tailscale/dns_transport.go b/protocol/tailscale/dns_transport.go index 0c83c69811..3447b6b2cf 100644 --- a/protocol/tailscale/dns_transport.go +++ b/protocol/tailscale/dns_transport.go @@ -287,7 +287,7 @@ func (t *DNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M return nil, E.New("missing default resolvers") } } - return nil, dns.RCodeNameError + return nil, dns.RcodeNameError } type DNSDialer struct { From 7743c6e881d2fc681aafc8000b1561886ac85ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 22 Feb 2025 08:00:59 +0800 Subject: [PATCH 0373/1272] Migrate to stdlib ECH support --- .golangci.yml | 1 - .goreleaser.fury.yaml | 1 - .goreleaser.yaml | 1 - Dockerfile | 2 +- Makefile | 5 +- cmd/internal/build_libbox/main.go | 2 +- cmd/sing-box/cmd_generate_ech.go | 5 +- common/badtls/read_wait_ech.go | 31 --- common/tls/client.go | 7 +- common/tls/ech.go | 174 +++++++++++++ common/tls/ech_client.go | 244 ----------------- common/tls/ech_keygen.go | 19 +- common/tls/ech_quic.go | 55 ---- common/tls/ech_server.go | 278 -------------------- common/tls/ech_stub.go | 18 +- common/tls/mkcert.go | 6 +- common/tls/server.go | 7 +- common/tls/std_client.go | 3 + common/tls/std_server.go | 48 +++- docs/configuration/shared/tls.md | 38 ++- docs/configuration/shared/tls.zh.md | 56 ++-- docs/deprecated.md | 11 + docs/deprecated.zh.md | 9 + docs/installation/build-from-source.md | 36 ++- docs/installation/build-from-source.zh.md | 6 +- experimental/deprecated/constants.go | 10 + experimental/deprecated/stderr.go | 16 +- go.mod | 1 - go.sum | 2 - option/tls.go | 26 +- test/box_test.go | 2 +- test/ech_test.go | 6 +- test/go.mod | 133 ++++++---- test/go.sum | 303 ++++++++++++++-------- 34 files changed, 666 insertions(+), 896 deletions(-) delete mode 100644 common/badtls/read_wait_ech.go create mode 100644 common/tls/ech.go delete mode 100644 common/tls/ech_client.go delete mode 100644 common/tls/ech_quic.go delete mode 100644 common/tls/ech_server.go diff --git a/.golangci.yml b/.golangci.yml index cdd53eca51..28b45337f0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -27,7 +27,6 @@ run: - with_quic - with_dhcp - with_wireguard - - with_ech - with_utls - with_reality_server - with_acme diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml index 8f3835f4ab..c149b1f440 100644 --- a/.goreleaser.fury.yaml +++ b/.goreleaser.fury.yaml @@ -14,7 +14,6 @@ builds: - with_quic - with_dhcp - with_wireguard - - with_ech - with_utls - with_reality_server - with_acme diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 86090bb779..23e0771f88 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -16,7 +16,6 @@ builds: - with_quic - with_dhcp - with_wireguard - - with_ech - with_utls - with_reality_server - with_acme diff --git a/Dockerfile b/Dockerfile index 6c3cfd396c..c9b32b8e10 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_ech,with_utls,with_reality_server,with_acme,with_clash_api" \ + "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api" \ -o /go/bin/sing-box \ -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \ ./cmd/sing-box diff --git a/Makefile b/Makefile index 7d3eb324ee..3d4f78fb56 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,9 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_acme -TAGS_GO121 = with_ech TAGS_GO123 = with_tailscale -TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121),$(TAGS_GO123) -TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server +TAGS ?= $(TAGS_GO120),$(TAGS_GO123) +TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server 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 8b251fee16..8f82ff36a0 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -59,7 +59,7 @@ func init() { 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_ech", "with_utls", "with_clash_api") + sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api") iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack") memcTags = append(memcTags, "with_tailscale") debugTags = append(debugTags, "debug") diff --git a/cmd/sing-box/cmd_generate_ech.go b/cmd/sing-box/cmd_generate_ech.go index 4b5b7be32d..7942d588bd 100644 --- a/cmd/sing-box/cmd_generate_ech.go +++ b/cmd/sing-box/cmd_generate_ech.go @@ -9,8 +9,6 @@ import ( "github.com/spf13/cobra" ) -var pqSignatureSchemesEnabled bool - var commandGenerateECHKeyPair = &cobra.Command{ Use: "ech-keypair ", Short: "Generate TLS ECH key pair", @@ -24,12 +22,11 @@ var commandGenerateECHKeyPair = &cobra.Command{ } func init() { - commandGenerateECHKeyPair.Flags().BoolVar(&pqSignatureSchemesEnabled, "pq-signature-schemes-enabled", false, "Enable PQ signature schemes") commandGenerate.AddCommand(commandGenerateECHKeyPair) } func generateECHKeyPair(serverName string) error { - configPem, keyPem, err := tls.ECHKeygenDefault(serverName, pqSignatureSchemesEnabled) + configPem, keyPem, err := tls.ECHKeygenDefault(serverName) if err != nil { return err } diff --git a/common/badtls/read_wait_ech.go b/common/badtls/read_wait_ech.go deleted file mode 100644 index 6a0d5b5ff8..0000000000 --- a/common/badtls/read_wait_ech.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build go1.21 && !without_badtls && with_ech - -package badtls - -import ( - "net" - _ "unsafe" - - "github.com/sagernet/cloudflare-tls" - "github.com/sagernet/sing/common" -) - -func init() { - tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { - tlsConn, loaded := common.Cast[*tls.Conn](conn) - if !loaded { - return - } - return true, func() error { - return echReadRecord(tlsConn) - }, func() error { - return echHandlePostHandshakeMessage(tlsConn) - } - }) -} - -//go:linkname echReadRecord github.com/sagernet/cloudflare-tls.(*Conn).readRecord -func echReadRecord(c *tls.Conn) error - -//go:linkname echHandlePostHandshakeMessage github.com/sagernet/cloudflare-tls.(*Conn).handlePostHandshakeMessage -func echHandlePostHandshakeMessage(c *tls.Conn) error diff --git a/common/tls/client.go b/common/tls/client.go index 4d6b0c540e..5e05c990d9 100644 --- a/common/tls/client.go +++ b/common/tls/client.go @@ -29,15 +29,12 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun if !options.Enabled { return nil, nil } - if options.ECH != nil && options.ECH.Enabled { - return NewECHClient(ctx, serverAddress, options) - } else if options.Reality != nil && options.Reality.Enabled { + if options.Reality != nil && options.Reality.Enabled { return NewRealityClient(ctx, serverAddress, options) } else if options.UTLS != nil && options.UTLS.Enabled { return NewUTLSClient(ctx, serverAddress, options) - } else { - return NewSTDClient(ctx, serverAddress, options) } + return NewSTDClient(ctx, serverAddress, options) } func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) { diff --git a/common/tls/ech.go b/common/tls/ech.go new file mode 100644 index 0000000000..880ca27ca3 --- /dev/null +++ b/common/tls/ech.go @@ -0,0 +1,174 @@ +//go:build go1.24 + +package tls + +import ( + "context" + "crypto/tls" + "encoding/base64" + "encoding/pem" + "net" + "os" + "strings" + + "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" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" + "golang.org/x/crypto/cryptobyte" +) + +func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) { + var echConfig []byte + if len(options.ECH.Config) > 0 { + echConfig = []byte(strings.Join(options.ECH.Config, "\n")) + } else if options.ECH.ConfigPath != "" { + content, err := os.ReadFile(options.ECH.ConfigPath) + if err != nil { + return nil, E.Cause(err, "read ECH config") + } + echConfig = content + } + //nolint:staticcheck + if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled { + deprecated.Report(ctx, deprecated.OptionLegacyECHOptions) + } + 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") + } + tlsConfig.EncryptedClientHelloConfigList = block.Bytes + return &STDClientConfig{tlsConfig}, nil + } else { + return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil + } +} + +func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, tlsConfig *tls.Config, echKeyPath *string) error { + var echKey []byte + if len(options.ECH.Key) > 0 { + echKey = []byte(strings.Join(options.ECH.Key, "\n")) + } else if options.ECH.KeyPath != "" { + content, err := os.ReadFile(options.ECH.KeyPath) + if err != nil { + return E.Cause(err, "read ECH keys") + } + echKey = content + *echKeyPath = options.ECH.KeyPath + } else { + return E.New("missing ECH keys") + } + block, rest := pem.Decode(echKey) + if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { + return E.New("invalid ECH keys pem") + } + echKeys, err := UnmarshalECHKeys(block.Bytes) + if err != nil { + return E.Cause(err, "parse ECH keys") + } + tlsConfig.EncryptedClientHelloKeys = echKeys + //nolint:staticcheck + if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled { + deprecated.Report(ctx, deprecated.OptionLegacyECHOptions) + } + return nil +} + +func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error { + echKey, err := os.ReadFile(echKeyPath) + if err != nil { + return E.Cause(err, "reload ECH keys from ", echKeyPath) + } + block, _ := pem.Decode(echKey) + if block == nil || block.Type != "ECH KEYS" { + return E.New("invalid ECH keys pem") + } + echKeys, err := UnmarshalECHKeys(block.Bytes) + if err != nil { + return E.Cause(err, "parse ECH keys") + } + tlsConfig.EncryptedClientHelloKeys = echKeys + return nil +} + +type STDECHClientConfig struct { + STDClientConfig + dnsRouter adapter.DNSRouter +} + +func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { + if len(s.config.EncryptedClientHelloConfigList) == 0 { + message := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + RecursionDesired: true, + }, + Question: []mDNS.Question{ + { + Name: mDNS.Fqdn(s.config.ServerName), + Qtype: mDNS.TypeHTTPS, + Qclass: mDNS.ClassINET, + }, + }, + } + response, err := s.dnsRouter.Exchange(ctx, message, adapter.DNSQueryOptions{}) + if err != nil { + return nil, E.Cause(err, "fetch ECH config list") + } + if response.Rcode != mDNS.RcodeSuccess { + return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list") + } + for _, rr := range response.Answer { + switch resource := rr.(type) { + case *mDNS.HTTPS: + for _, value := range resource.Value { + if value.Key().String() == "ech" { + echConfigList, err := base64.StdEncoding.DecodeString(value.String()) + if err != nil { + return nil, E.Cause(err, "decode ECH config") + } + s.config.EncryptedClientHelloConfigList = echConfigList + } + } + } + } + return nil, E.New("no ECH config found in DNS records") + } + tlsConn, err := s.Client(conn) + if err != nil { + return nil, err + } + err = tlsConn.HandshakeContext(ctx) + if err != nil { + return nil, err + } + return tlsConn, nil +} + +func (s *STDECHClientConfig) Clone() Config { + return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter} +} + +func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) { + var keys []tls.EncryptedClientHelloKey + rawString := cryptobyte.String(raw) + for !rawString.Empty() { + var key tls.EncryptedClientHelloKey + if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) { + return nil, E.New("error parsing private key") + } + if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) { + return nil, E.New("error parsing config") + } + keys = append(keys, key) + } + if len(keys) == 0 { + return nil, E.New("empty ECH keys") + } + return keys, nil +} diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go deleted file mode 100644 index 9c3744b4a1..0000000000 --- a/common/tls/ech_client.go +++ /dev/null @@ -1,244 +0,0 @@ -//go:build with_ech - -package tls - -import ( - "context" - "crypto/tls" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "net" - "net/netip" - "os" - "strings" - - cftls "github.com/sagernet/cloudflare-tls" - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/dns" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/ntp" - "github.com/sagernet/sing/service" - - mDNS "github.com/miekg/dns" -) - -type echClientConfig struct { - config *cftls.Config -} - -func (c *echClientConfig) ServerName() string { - return c.config.ServerName -} - -func (c *echClientConfig) SetServerName(serverName string) { - c.config.ServerName = serverName -} - -func (c *echClientConfig) NextProtos() []string { - return c.config.NextProtos -} - -func (c *echClientConfig) SetNextProtos(nextProto []string) { - c.config.NextProtos = nextProto -} - -func (c *echClientConfig) Config() (*STDConfig, error) { - return nil, E.New("unsupported usage for ECH") -} - -func (c *echClientConfig) Client(conn net.Conn) (Conn, error) { - return &echConnWrapper{cftls.Client(conn, c.config)}, nil -} - -func (c *echClientConfig) Clone() Config { - return &echClientConfig{ - config: c.config.Clone(), - } -} - -type echConnWrapper struct { - *cftls.Conn -} - -func (c *echConnWrapper) ConnectionState() tls.ConnectionState { - state := c.Conn.ConnectionState() - //nolint:staticcheck - return tls.ConnectionState{ - Version: state.Version, - HandshakeComplete: state.HandshakeComplete, - DidResume: state.DidResume, - CipherSuite: state.CipherSuite, - NegotiatedProtocol: state.NegotiatedProtocol, - NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual, - ServerName: state.ServerName, - PeerCertificates: state.PeerCertificates, - VerifiedChains: state.VerifiedChains, - SignedCertificateTimestamps: state.SignedCertificateTimestamps, - OCSPResponse: state.OCSPResponse, - TLSUnique: state.TLSUnique, - } -} - -func (c *echConnWrapper) Upstream() any { - return c.Conn -} - -func NewECHClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { - var serverName string - if options.ServerName != "" { - serverName = options.ServerName - } else if serverAddress != "" { - if _, err := netip.ParseAddr(serverName); err != nil { - serverName = serverAddress - } - } - if serverName == "" && !options.Insecure { - return nil, E.New("missing server_name or insecure=true") - } - - var tlsConfig cftls.Config - tlsConfig.Time = ntp.TimeFuncFromContext(ctx) - tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx) - if options.DisableSNI { - tlsConfig.ServerName = "127.0.0.1" - } else { - tlsConfig.ServerName = serverName - } - if options.Insecure { - tlsConfig.InsecureSkipVerify = options.Insecure - } else if options.DisableSNI { - tlsConfig.InsecureSkipVerify = true - tlsConfig.VerifyConnection = func(state cftls.ConnectionState) error { - verifyOptions := x509.VerifyOptions{ - DNSName: serverName, - Intermediates: x509.NewCertPool(), - } - for _, cert := range state.PeerCertificates[1:] { - verifyOptions.Intermediates.AddCert(cert) - } - _, err := state.PeerCertificates[0].Verify(verifyOptions) - return err - } - } - if len(options.ALPN) > 0 { - tlsConfig.NextProtos = options.ALPN - } - if options.MinVersion != "" { - minVersion, err := ParseTLSVersion(options.MinVersion) - if err != nil { - return nil, E.Cause(err, "parse min_version") - } - tlsConfig.MinVersion = minVersion - } - if options.MaxVersion != "" { - maxVersion, err := ParseTLSVersion(options.MaxVersion) - if err != nil { - return nil, E.Cause(err, "parse max_version") - } - tlsConfig.MaxVersion = maxVersion - } - if options.CipherSuites != nil { - find: - for _, cipherSuite := range options.CipherSuites { - for _, tlsCipherSuite := range cftls.CipherSuites() { - if cipherSuite == tlsCipherSuite.Name { - tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID) - continue find - } - } - return nil, E.New("unknown cipher_suite: ", cipherSuite) - } - } - var certificate []byte - if len(options.Certificate) > 0 { - certificate = []byte(strings.Join(options.Certificate, "\n")) - } else if options.CertificatePath != "" { - content, err := os.ReadFile(options.CertificatePath) - if err != nil { - return nil, E.Cause(err, "read certificate") - } - certificate = content - } - if len(certificate) > 0 { - certPool := x509.NewCertPool() - if !certPool.AppendCertsFromPEM(certificate) { - return nil, E.New("failed to parse certificate:\n\n", certificate) - } - tlsConfig.RootCAs = certPool - } - - // ECH Config - - tlsConfig.ECHEnabled = true - tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled - tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled - - var echConfig []byte - if len(options.ECH.Config) > 0 { - echConfig = []byte(strings.Join(options.ECH.Config, "\n")) - } else if options.ECH.ConfigPath != "" { - content, err := os.ReadFile(options.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") - } - echConfigs, err := cftls.UnmarshalECHConfigs(block.Bytes) - if err != nil { - return nil, E.Cause(err, "parse ECH configs") - } - tlsConfig.ClientECHConfigs = echConfigs - } else { - tlsConfig.GetClientECHConfigs = fetchECHClientConfig(ctx) - } - return &echClientConfig{&tlsConfig}, nil -} - -func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) { - return func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) { - message := &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - RecursionDesired: true, - }, - Question: []mDNS.Question{ - { - Name: serverName + ".", - Qtype: mDNS.TypeHTTPS, - Qclass: mDNS.ClassINET, - }, - }, - } - response, err := service.FromContext[adapter.DNSRouter](ctx).Exchange(ctx, message, adapter.DNSQueryOptions{}) - if err != nil { - return nil, err - } - if response.Rcode != mDNS.RcodeSuccess { - return nil, dns.RcodeError(response.Rcode) - } - for _, rr := range response.Answer { - switch resource := rr.(type) { - case *mDNS.HTTPS: - for _, value := range resource.Value { - if value.Key().String() == "ech" { - echConfig, err := base64.StdEncoding.DecodeString(value.String()) - if err != nil { - return nil, E.Cause(err, "decode ECH config") - } - return cftls.UnmarshalECHConfigs(echConfig) - } - } - default: - return nil, E.New("unknown resource record type: ", resource.Header().Rrtype) - } - } - return nil, E.New("no ECH config found") - } -} diff --git a/common/tls/ech_keygen.go b/common/tls/ech_keygen.go index 5053981f1a..b6c353ac94 100644 --- a/common/tls/ech_keygen.go +++ b/common/tls/ech_keygen.go @@ -1,5 +1,3 @@ -//go:build with_ech - package tls import ( @@ -7,14 +5,13 @@ import ( "encoding/binary" "encoding/pem" - cftls "github.com/sagernet/cloudflare-tls" E "github.com/sagernet/sing/common/exceptions" "github.com/cloudflare/circl/hpke" "github.com/cloudflare/circl/kem" ) -func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) { +func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) { cipherSuites := []echCipherSuite{ { kdf: hpke.KDF_HKDF_SHA256, @@ -24,13 +21,9 @@ func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (config aead: hpke.AEAD_ChaCha20Poly1305, }, } - keyConfig := []myECHKeyConfig{ {id: 0, kem: hpke.KEM_X25519_HKDF_SHA256}, } - if pqSignatureSchemesEnabled { - keyConfig = append(keyConfig, myECHKeyConfig{id: 1, kem: hpke.KEM_X25519_KYBER768_DRAFT00}) - } keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites) if err != nil { @@ -59,7 +52,6 @@ func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (config type echKeyConfigPair struct { id uint8 - key cftls.EXP_ECHKey rawKey []byte conf myECHKeyConfig rawConf []byte @@ -155,15 +147,6 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [ sk = append(sk, secBuf...) sk = be.AppendUint16(sk, uint16(len(b))) sk = append(sk, b...) - - cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk) - if err != nil { - return nil, E.Cause(err, "bug: can't parse generated ECH server key") - } - if len(cfECHKeys) != 1 { - return nil, E.New("bug: unexpected server key count") - } - pair.key = cfECHKeys[0] pair.rawKey = sk pairs = append(pairs, pair) diff --git a/common/tls/ech_quic.go b/common/tls/ech_quic.go deleted file mode 100644 index 4606090a7a..0000000000 --- a/common/tls/ech_quic.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build with_quic && with_ech - -package tls - -import ( - "context" - "net" - "net/http" - - "github.com/sagernet/cloudflare-tls" - "github.com/sagernet/quic-go/ech" - "github.com/sagernet/quic-go/http3_ech" - "github.com/sagernet/sing-quic" - M "github.com/sagernet/sing/common/metadata" -) - -var ( - _ qtls.Config = (*echClientConfig)(nil) - _ qtls.ServerConfig = (*echServerConfig)(nil) -) - -func (c *echClientConfig) Dial(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.Connection, error) { - return quic.Dial(ctx, conn, addr, c.config, config) -} - -func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.EarlyConnection, error) { - return quic.DialEarly(ctx, conn, addr, c.config, config) -} - -func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper { - return &http3.Transport{ - TLSClientConfig: c.config, - QUICConfig: quicConfig, - Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { - quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg) - if err != nil { - return nil, err - } - *quicConnPtr = quicConn - return quicConn, nil - }, - } -} - -func (c *echServerConfig) Listen(conn net.PacketConn, config *quic.Config) (qtls.Listener, error) { - return quic.Listen(conn, c.config, config) -} - -func (c *echServerConfig) ListenEarly(conn net.PacketConn, config *quic.Config) (qtls.EarlyListener, error) { - return quic.ListenEarly(conn, c.config, config) -} - -func (c *echServerConfig) ConfigureHTTP3() { - http3.ConfigureTLSConfig(c.config) -} diff --git a/common/tls/ech_server.go b/common/tls/ech_server.go deleted file mode 100644 index 03f5d87695..0000000000 --- a/common/tls/ech_server.go +++ /dev/null @@ -1,278 +0,0 @@ -//go:build with_ech - -package tls - -import ( - "context" - "crypto/tls" - "encoding/pem" - "net" - "os" - "strings" - - cftls "github.com/sagernet/cloudflare-tls" - "github.com/sagernet/fswatch" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/ntp" -) - -type echServerConfig struct { - config *cftls.Config - logger log.Logger - certificate []byte - key []byte - certificatePath string - keyPath string - echKeyPath string - watcher *fswatch.Watcher -} - -func (c *echServerConfig) ServerName() string { - return c.config.ServerName -} - -func (c *echServerConfig) SetServerName(serverName string) { - c.config.ServerName = serverName -} - -func (c *echServerConfig) NextProtos() []string { - return c.config.NextProtos -} - -func (c *echServerConfig) SetNextProtos(nextProto []string) { - c.config.NextProtos = nextProto -} - -func (c *echServerConfig) Config() (*STDConfig, error) { - return nil, E.New("unsupported usage for ECH") -} - -func (c *echServerConfig) Client(conn net.Conn) (Conn, error) { - return &echConnWrapper{cftls.Client(conn, c.config)}, nil -} - -func (c *echServerConfig) Server(conn net.Conn) (Conn, error) { - return &echConnWrapper{cftls.Server(conn, c.config)}, nil -} - -func (c *echServerConfig) Clone() Config { - return &echServerConfig{ - config: c.config.Clone(), - } -} - -func (c *echServerConfig) Start() error { - err := c.startWatcher() - if err != nil { - c.logger.Warn("create credentials watcher: ", err) - } - return nil -} - -func (c *echServerConfig) startWatcher() error { - var watchPath []string - if c.certificatePath != "" { - watchPath = append(watchPath, c.certificatePath) - } - if c.keyPath != "" { - watchPath = append(watchPath, c.keyPath) - } - if c.echKeyPath != "" { - watchPath = append(watchPath, c.echKeyPath) - } - if len(watchPath) == 0 { - return nil - } - watcher, err := fswatch.NewWatcher(fswatch.Options{ - Path: watchPath, - Callback: func(path string) { - err := c.credentialsUpdated(path) - if err != nil { - c.logger.Error(E.Cause(err, "reload credentials")) - } - }, - }) - if err != nil { - return err - } - err = watcher.Start() - if err != nil { - return err - } - c.watcher = watcher - return nil -} - -func (c *echServerConfig) credentialsUpdated(path string) error { - if path == c.certificatePath || path == c.keyPath { - if path == c.certificatePath { - certificate, err := os.ReadFile(c.certificatePath) - if err != nil { - return err - } - c.certificate = certificate - } else { - key, err := os.ReadFile(c.keyPath) - if err != nil { - return err - } - c.key = key - } - keyPair, err := cftls.X509KeyPair(c.certificate, c.key) - if err != nil { - return E.Cause(err, "parse key pair") - } - c.config.Certificates = []cftls.Certificate{keyPair} - c.logger.Info("reloaded TLS certificate") - } else { - echKeyContent, err := os.ReadFile(c.echKeyPath) - if err != nil { - return err - } - block, rest := pem.Decode(echKeyContent) - if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { - return E.New("invalid ECH keys pem") - } - echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes) - if err != nil { - return E.Cause(err, "parse ECH keys") - } - echKeySet, err := cftls.EXP_NewECHKeySet(echKeys) - if err != nil { - return E.Cause(err, "create ECH key set") - } - c.config.ServerECHProvider = echKeySet - c.logger.Info("reloaded ECH keys") - } - return nil -} - -func (c *echServerConfig) Close() error { - var err error - if c.watcher != nil { - err = E.Append(err, c.watcher.Close(), func(err error) error { - return E.Cause(err, "close credentials watcher") - }) - } - return err -} - -func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { - if !options.Enabled { - return nil, nil - } - var tlsConfig cftls.Config - if options.ACME != nil && len(options.ACME.Domain) > 0 { - return nil, E.New("acme is unavailable in ech") - } - tlsConfig.Time = ntp.TimeFuncFromContext(ctx) - if options.ServerName != "" { - tlsConfig.ServerName = options.ServerName - } - if len(options.ALPN) > 0 { - tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...) - } - if options.MinVersion != "" { - minVersion, err := ParseTLSVersion(options.MinVersion) - if err != nil { - return nil, E.Cause(err, "parse min_version") - } - tlsConfig.MinVersion = minVersion - } - if options.MaxVersion != "" { - maxVersion, err := ParseTLSVersion(options.MaxVersion) - if err != nil { - return nil, E.Cause(err, "parse max_version") - } - tlsConfig.MaxVersion = maxVersion - } - if options.CipherSuites != nil { - find: - for _, cipherSuite := range options.CipherSuites { - for _, tlsCipherSuite := range tls.CipherSuites() { - if cipherSuite == tlsCipherSuite.Name { - tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID) - continue find - } - } - return nil, E.New("unknown cipher_suite: ", cipherSuite) - } - } - var certificate []byte - var key []byte - if len(options.Certificate) > 0 { - certificate = []byte(strings.Join(options.Certificate, "\n")) - } else if options.CertificatePath != "" { - content, err := os.ReadFile(options.CertificatePath) - if err != nil { - return nil, E.Cause(err, "read certificate") - } - certificate = content - } - if len(options.Key) > 0 { - key = []byte(strings.Join(options.Key, "\n")) - } else if options.KeyPath != "" { - content, err := os.ReadFile(options.KeyPath) - if err != nil { - return nil, E.Cause(err, "read key") - } - key = content - } - - if certificate == nil { - return nil, E.New("missing certificate") - } else if key == nil { - return nil, E.New("missing key") - } - - keyPair, err := cftls.X509KeyPair(certificate, key) - if err != nil { - return nil, E.Cause(err, "parse x509 key pair") - } - tlsConfig.Certificates = []cftls.Certificate{keyPair} - - var echKey []byte - if len(options.ECH.Key) > 0 { - echKey = []byte(strings.Join(options.ECH.Key, "\n")) - } else if options.ECH.KeyPath != "" { - content, err := os.ReadFile(options.ECH.KeyPath) - if err != nil { - return nil, E.Cause(err, "read ECH key") - } - echKey = content - } else { - return nil, E.New("missing ECH key") - } - - block, rest := pem.Decode(echKey) - if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { - return nil, E.New("invalid ECH keys pem") - } - - echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes) - if err != nil { - return nil, E.Cause(err, "parse ECH keys") - } - - echKeySet, err := cftls.EXP_NewECHKeySet(echKeys) - if err != nil { - return nil, E.Cause(err, "create ECH key set") - } - - tlsConfig.ECHEnabled = true - tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled - tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled - tlsConfig.ServerECHProvider = echKeySet - - return &echServerConfig{ - config: &tlsConfig, - logger: logger, - certificate: certificate, - key: key, - certificatePath: options.CertificatePath, - keyPath: options.KeyPath, - echKeyPath: options.ECH.KeyPath, - }, nil -} diff --git a/common/tls/ech_stub.go b/common/tls/ech_stub.go index 4ee4272cf8..8c93690c6c 100644 --- a/common/tls/ech_stub.go +++ b/common/tls/ech_stub.go @@ -1,25 +1,23 @@ -//go:build !with_ech +//go:build !go1.24 package tls import ( "context" + "crypto/tls" - "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" ) -var errECHNotIncluded = E.New(`ECH is not included in this build, rebuild with -tags with_ech`) - -func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { - return nil, errECHNotIncluded +func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) { + return nil, E.New("ECH requires go1.24, please recompile your binary.") } -func NewECHClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { - return nil, errECHNotIncluded +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 ECHKeygenDefault(host string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) { - return "", "", errECHNotIncluded +func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error { + return E.New("ECH requires go1.24, please recompile your binary.") } diff --git a/common/tls/mkcert.go b/common/tls/mkcert.go index 12680c4834..4e0ed102bb 100644 --- a/common/tls/mkcert.go +++ b/common/tls/mkcert.go @@ -12,6 +12,9 @@ import ( ) func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) { + if timeFunc == nil { + timeFunc = time.Now + } privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour)) if err != nil { return nil, err @@ -24,9 +27,6 @@ func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() ti } func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) { - if timeFunc == nil { - timeFunc = time.Now - } key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return diff --git a/common/tls/server.go b/common/tls/server.go index 6afd89d64d..bcc5ddfafd 100644 --- a/common/tls/server.go +++ b/common/tls/server.go @@ -16,13 +16,10 @@ func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLS if !options.Enabled { return nil, nil } - if options.ECH != nil && options.ECH.Enabled { - return NewECHServer(ctx, logger, options) - } else if options.Reality != nil && options.Reality.Enabled { + if options.Reality != nil && options.Reality.Enabled { return NewRealityServer(ctx, logger, options) - } else { - return NewSTDServer(ctx, logger, options) } + return NewSTDServer(ctx, logger, options) } func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) { diff --git a/common/tls/std_client.go b/common/tls/std_client.go index 78f4e5cf9d..8e9327bf0a 100644 --- a/common/tls/std_client.go +++ b/common/tls/std_client.go @@ -127,5 +127,8 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb } tlsConfig.RootCAs = certPool } + if options.ECH != nil && options.ECH.Enabled { + return parseECHClientConfig(ctx, options, &tlsConfig) + } return &STDClientConfig{&tlsConfig}, nil } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 9ba405ba98..9c0ef1efcf 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -27,6 +27,7 @@ type STDServerConfig struct { key []byte certificatePath string keyPath string + echKeyPath string watcher *fswatch.Watcher } @@ -95,6 +96,9 @@ func (c *STDServerConfig) startWatcher() error { if c.keyPath != "" { watchPath = append(watchPath, c.keyPath) } + if c.echKeyPath != "" { + watchPath = append(watchPath, c.echKeyPath) + } watcher, err := fswatch.NewWatcher(fswatch.Options{ Path: watchPath, Callback: func(path string) { @@ -116,25 +120,33 @@ func (c *STDServerConfig) startWatcher() error { } func (c *STDServerConfig) certificateUpdated(path string) error { - if path == c.certificatePath { - certificate, err := os.ReadFile(c.certificatePath) + if path == c.certificatePath || path == c.keyPath { + if path == c.certificatePath { + certificate, err := os.ReadFile(c.certificatePath) + if err != nil { + return E.Cause(err, "reload certificate from ", c.certificatePath) + } + c.certificate = certificate + } else if path == c.keyPath { + key, err := os.ReadFile(c.keyPath) + if err != nil { + return E.Cause(err, "reload key from ", c.keyPath) + } + c.key = key + } + keyPair, err := tls.X509KeyPair(c.certificate, c.key) if err != nil { - return E.Cause(err, "reload certificate from ", c.certificatePath) + return E.Cause(err, "reload key pair") } - c.certificate = certificate - } else if path == c.keyPath { - key, err := os.ReadFile(c.keyPath) + c.config.Certificates = []tls.Certificate{keyPair} + c.logger.Info("reloaded TLS certificate") + } else if path == c.echKeyPath { + err := reloadECHKeys(c.echKeyPath, c.config) if err != nil { - return E.Cause(err, "reload key from ", c.keyPath) + return err } - c.key = key + c.logger.Info("reloaded ECH keys") } - keyPair, err := tls.X509KeyPair(c.certificate, c.key) - if err != nil { - return E.Cause(err, "reload key pair") - } - c.config.Certificates = []tls.Certificate{keyPair} - c.logger.Info("reloaded TLS certificate") return nil } @@ -243,6 +255,13 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound tlsConfig.Certificates = []tls.Certificate{keyPair} } } + var echKeyPath string + if options.ECH != nil && options.ECH.Enabled { + err = parseECHServerConfig(ctx, options, tlsConfig, &echKeyPath) + if err != nil { + return nil, err + } + } return &STDServerConfig{ config: tlsConfig, logger: logger, @@ -251,5 +270,6 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound key: key, certificatePath: options.CertificatePath, keyPath: options.KeyPath, + echKeyPath: echKeyPath, }, nil } diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index f2a716a7e0..e4d777182d 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.12.0" + + :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 "Changes in sing-box 1.10.0" :material-alert-decagram: [utls](#utls) @@ -34,10 +43,13 @@ }, "ech": { "enabled": false, - "pq_signature_schemes_enabled": false, - "dynamic_record_sizing_disabled": false, "key": [], - "key_path": "" + "key_path": "", + + // Deprecated + + "pq_signature_schemes_enabled": false, + "dynamic_record_sizing_disabled": false }, "reality": { "enabled": false, @@ -72,10 +84,12 @@ "certificate_path": "", "ech": { "enabled": false, - "pq_signature_schemes_enabled": false, - "dynamic_record_sizing_disabled": false, "config": [], - "config_path": "" + "config_path": "", + + // Deprecated + "pq_signature_schemes_enabled": false, + "dynamic_record_sizing_disabled": false }, "utls": { "enabled": false, @@ -246,16 +260,22 @@ Chrome fingerprint will be used if empty. ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello message. -The ECH key and configuration can be generated by `sing-box generate ech-keypair [--pq-signature-schemes-enabled]`. +The ECH key and configuration can be generated by `sing-box generate ech-keypair`. #### pq_signature_schemes_enabled -Enable support for post-quantum peer certificate signature schemes. +!!! failure "Deprecated in sing-box 1.12.0" -It is recommended to match the parameters of `sing-box generate ech-keypair`. + ECH support has been migrated to use stdlib in sing-box 1.12.0, which does not come with support for PQ signature schemes, so `pq_signature_schemes_enabled` has been deprecated and no longer works. + +Enable support for post-quantum peer certificate signature schemes. #### dynamic_record_sizing_disabled +!!! failure "Deprecated in sing-box 1.12.0" + + `dynamic_record_sizing_disabled` has nothing to do with ECH, was added by mistake, has been deprecated and no longer works. + Disables adaptive sizing of TLS records. When true, the largest possible TLS record size is always used. diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 6cfc5f36e4..1418dd5982 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.12.0 中的更改" + + :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) @@ -34,18 +43,21 @@ }, "ech": { "enabled": false, - "pq_signature_schemes_enabled": false, - "dynamic_record_sizing_disabled": false, "key": [], - "key_path": "" + "key_path": "", + + // 废弃的 + + "pq_signature_schemes_enabled": false, + "dynamic_record_sizing_disabled": false }, "reality": { "enabled": false, "handshake": { "server": "google.com", "server_port": 443, - ... - // 拨号字段 + + ... // 拨号字段 }, "private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", "short_id": [ @@ -240,19 +252,6 @@ ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ECH 配置和密钥可以通过 `sing-box generate ech-keypair [--pq-signature-schemes-enabled]` 生成。 -#### pq_signature_schemes_enabled - -启用对后量子对等证书签名方案的支持。 - -建议匹配 `sing-box generate ech-keypair` 的参数。 - -#### dynamic_record_sizing_disabled - -禁用 TLS 记录的自适应大小调整。 - -如果为 true,则始终使用最大可能的 TLS 记录大小。 -如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。 - #### key ==仅服务器== @@ -285,6 +284,27 @@ ECH PEM 配置路径 如果为空,将尝试从 DNS 加载。 +#### pq_signature_schemes_enabled + +!!! failure "已在 sing-box 1.12.0 废弃" + + ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支持后量子对等证书签名方案,因此 `pq_signature_schemes_enabled` 已被弃用且不再工作。 + +启用对后量子对等证书签名方案的支持。 + +建议匹配 `sing-box generate ech-keypair` 的参数。 + +#### dynamic_record_sizing_disabled + +!!! failure "已在 sing-box 1.12.0 废弃" + + `dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作。 + +禁用 TLS 记录的自适应大小调整。 + +如果为 true,则始终使用最大可能的 TLS 记录大小。 +如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。 + ### ACME 字段 #### domain diff --git a/docs/deprecated.md b/docs/deprecated.md index 128c0709d6..8e53bda6db 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -19,6 +19,17 @@ Legacy `outbound` DNS rules are deprecated and can be replaced by dial fields, check [Migration](../migration/#migrate-outbound-dns-rule-items-to-domain-resolver). +#### Legacy ECH fields + +ECH support has been migrated to use stdlib in sing-box 1.12.0, +which does not come with support for PQ signature schemes, +so `pq_signature_schemes_enabled` has been deprecated and no longer works. + +Also, `dynamic_record_sizing_disabled` has nothing to do with ECH, +was added by mistake, has been deprecated and no longer works. + +These fields will be removed in sing-box 1.13.0. + ## 1.11.0 #### Legacy special outbounds diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 2c0f557850..c090432b84 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -17,6 +17,15 @@ DNS 服务器已重构, 且可被拨号字段代替, 参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver). +#### 旧的 ECH 字段 + +ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支持后量子对等证书签名方案, +因此 `pq_signature_schemes_enabled` 已被弃用且不再工作。 + +另外,`dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作。 + +相关字段将在 sing-box 1.13.0 中被移除。 + ## 1.11.0 #### 旧的特殊出站 diff --git a/docs/installation/build-from-source.md b/docs/installation/build-from-source.md index 8c4d492379..c286c36626 100644 --- a/docs/installation/build-from-source.md +++ b/docs/installation/build-from-source.md @@ -6,19 +6,18 @@ icon: material/file-code ## :material-graph: Requirements +### sing-box 1.11 + +* Go 1.23.1 - ~ + ### sing-box 1.10 * Go 1.20.0 - ~ -* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled -* Go 1.21.0 - ~ with tag `with_ech` enabled ### sing-box 1.9 * Go 1.18.5 - 1.22.x * Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled -* Go 1.21.0 - 1.22.x with tag `with_ech` enabled - -You can download and install Go from: https://go.dev/doc/install, latest version is recommended. ## :material-fast-forward: Simple Build @@ -46,20 +45,19 @@ go build -tags "tag_a tag_b" ./cmd/sing-box ## :material-folder-settings: Build Tags -| Build Tag | Enabled by default | Description | -|------------------------------------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). | -| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). | -| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). | -| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). | -| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). | -| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). | -| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). | -| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | -| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). | -| `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/). | +| Build Tag | Enabled by default | Description | +|------------------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). | +| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). | +| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). | +| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). | +| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). | +| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). | +| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | +| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). | +| `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) | It is not recommended to change the default build tag list unless you really know what you are adding. diff --git a/docs/installation/build-from-source.zh.md b/docs/installation/build-from-source.zh.md index 7de7fcf400..c022292954 100644 --- a/docs/installation/build-from-source.zh.md +++ b/docs/installation/build-from-source.zh.md @@ -6,10 +6,13 @@ icon: material/file-code ## :material-graph: 要求 +### sing-box 1.11 + +* Go 1.23.1 - ~ + ### sing-box 1.10 * Go 1.20.0 - ~ -* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled * Go 1.21.0 - ~ with tag `with_ech` enabled ### sing-box 1.9 @@ -52,7 +55,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box | `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). | | `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). | | `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). | -| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). | | `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). | | `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). | | `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index d5c3ca4831..16216716a9 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -144,6 +144,7 @@ var OptionTUNGSO = Note{ 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{ @@ -179,6 +180,14 @@ 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", + MigrationLink: "https://sing-box.sagernet.org/deprecated/#legacy-ech-fields", +} + var Options = []Note{ OptionBadMatchSource, OptionGEOIP, @@ -194,4 +203,5 @@ var Options = []Note{ OptionLegacyDNSFakeIPOptions, OptionOutboundDNSRuleItem, OptionMissingDomainResolver, + OptionLegacyECHOptions, } diff --git a/experimental/deprecated/stderr.go b/experimental/deprecated/stderr.go index f29b4754f4..0dfb935409 100644 --- a/experimental/deprecated/stderr.go +++ b/experimental/deprecated/stderr.go @@ -28,11 +28,15 @@ func (f *stderrManager) ReportDeprecated(feature Note) { f.logger.Warn(feature.MessageWithLink()) return } - enable, enableErr := strconv.ParseBool(os.Getenv("ENABLE_DEPRECATED_" + feature.EnvName)) - if enableErr == nil && enable { - f.logger.Warn(feature.MessageWithLink()) - return + if feature.EnvName != "" { + enable, enableErr := strconv.ParseBool(os.Getenv("ENABLE_DEPRECATED_" + feature.EnvName)) + if enableErr == nil && enable { + f.logger.Warn(feature.MessageWithLink()) + return + } + f.logger.Error(feature.MessageWithLink()) + f.logger.Fatal("to continuing using this feature, set environment variable ENABLE_DEPRECATED_" + feature.EnvName + "=true") + } else { + f.logger.Error(feature.MessageWithLink()) } - f.logger.Error(feature.MessageWithLink()) - f.logger.Fatal("to continuing using this feature, set environment variable ENABLE_DEPRECATED_" + feature.EnvName + "=true") } diff --git a/go.mod b/go.mod index 3db283837a..5387346313 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a - github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.6 diff --git a/go.sum b/go.sum index ab2b9a7c5c..f715eca06e 100644 --- a/go.sum +++ b/go.sum @@ -155,8 +155,6 @@ github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU= 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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= -github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= 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= diff --git a/option/tls.go b/option/tls.go index 72aaaef1ac..13e753060b 100644 --- a/option/tls.go +++ b/option/tls.go @@ -83,19 +83,25 @@ type InboundRealityHandshakeOptions struct { } type InboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` - Key badoption.Listable[string] `json:"key,omitempty"` - KeyPath string `json:"key_path,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Key badoption.Listable[string] `json:"key,omitempty"` + KeyPath string `json:"key_path,omitempty"` + + // Deprecated: not supported by stdlib + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + // Deprecated: added by fault + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` } type OutboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,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"` + + // Deprecated: not supported by stdlib + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + // Deprecated: added by fault + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` } type OutboundUTLSOptions struct { diff --git a/test/box_test.go b/test/box_test.go index 08b50b64c2..bfffaa03a0 100644 --- a/test/box_test.go +++ b/test/box_test.go @@ -32,7 +32,7 @@ func TestMain(m *testing.M) { var globalCtx context.Context func init() { - globalCtx = box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()) + globalCtx = box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry()) } func startInstance(t *testing.T, options option.Options) *box.Box { diff --git a/test/ech_test.go b/test/ech_test.go index 865ffba003..91d13d3a26 100644 --- a/test/ech_test.go +++ b/test/ech_test.go @@ -15,7 +15,7 @@ import ( func TestECH(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") - echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) startInstance(t, option.Options{ Inbounds: []option.Inbound{ { @@ -108,7 +108,7 @@ func TestECH(t *testing.T) { func TestECHQUIC(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") - echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) startInstance(t, option.Options{ Inbounds: []option.Inbound{ { @@ -198,7 +198,7 @@ func TestECHQUIC(t *testing.T) { func TestECHHysteria2(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") - echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) startInstance(t, option.Options{ Inbounds: []option.Inbound{ { diff --git a/test/go.mod b/test/go.mod index 68fddc34b1..2baf3b4161 100644 --- a/test/go.mod +++ b/test/go.mod @@ -1,8 +1,8 @@ module test -go 1.23 +go 1.23.1 -toolchain go1.23.2 +toolchain go1.24.0 require github.com/sagernet/sing-box v0.0.0 @@ -11,110 +11,149 @@ 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.0 - github.com/sagernet/quic-go v0.48.2-beta.1 - github.com/sagernet/sing v0.6.0-beta.12 - github.com/sagernet/sing-dns v0.4.0-beta.2 - github.com/sagernet/sing-quic v0.4.0-beta.4 + github.com/gofrs/uuid/v5 v5.3.1 + github.com/sagernet/quic-go v0.49.0-beta.1 + github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff + 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/spyzhov/ajson v0.9.4 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.31.0 + golang.org/x/net v0.35.0 ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ajg/form v1.5.1 // indirect - github.com/andybalholm/brotli v1.0.6 // indirect - github.com/caddyserver/certmagic v0.20.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect + 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.2 // indirect + github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/caddyserver/certmagic v0.21.7 // 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/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/davecgh/go-spew v1.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/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/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-chi/chi/v5 v5.1.0 // indirect + github.com/fxamacker/cbor/v2 v2.6.0 // indirect + github.com/gaissmai/bart v0.11.1 // indirect + github.com/go-chi/chi/v5 v5.2.1 // indirect github.com/go-chi/render v1.0.3 // indirect + github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // 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 v0.0.0-20230315185526-52ccab3ef572 // 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/pprof v0.0.0-20231101202521-4ca4178f5c7a // 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.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect - github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect - github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // 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/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // 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/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/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect - github.com/mdlayher/socket v0.4.1 // indirect - github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect - github.com/mholt/acmez v1.2.0 // indirect - github.com/miekg/dns v1.1.62 // 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/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.9.7 // 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/oschwald/maxminddb-golang v1.12.0 // indirect - github.com/pierrec/lz4/v4 v4.1.14 // 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.0 // 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/safchain/ethtool v0.3.0 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect - github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // 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/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.0-alpha.1 // indirect - github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect - github.com/sagernet/sing-tun v0.6.0-beta.8 // indirect - github.com/sagernet/sing-vmess v0.2.0-beta.2 // indirect + github.com/sagernet/sing-mux v0.3.1 // indirect + github.com/sagernet/sing-shadowtls v0.2.0 // indirect + github.com/sagernet/sing-tun v0.6.1 // 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.79.0-mod.1 // indirect github.com/sagernet/utls v1.6.7 // indirect github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect - github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // 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-20240214030740-b535050b2aa4 // indirect + github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect + github.com/tcnksm/go-httpstat v0.2.0 // indirect + github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect github.com/vishvananda/netns v0.0.4 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect - go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect - go.opentelemetry.io/otel/metric v1.31.0 // indirect - go.opentelemetry.io/otel/sdk v1.31.0 // indirect - go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.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-20220726221520-4f986261bf13 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.31.0 // indirect + golang.org/x/crypto v0.33.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // 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/time v0.7.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.1 // 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 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 5266d18cca..d63f3057ee 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1,24 +1,46 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= -github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= +github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= +github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +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.2 h1:25azSh0o/LMcIkhS4ZutgRTIGwh8O3wuOhsThVM9K9o= +github.com/anytls/sing-anytls v0.0.2/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/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/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +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/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= +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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -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= @@ -31,10 +53,18 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 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/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= +github.com/fxamacker/cbor/v2 v2.6.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/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/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-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= +github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= 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= @@ -42,42 +72,63 @@ 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 v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +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/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= -github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +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= +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/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= -github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +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.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= +github.com/gorilla/csrf v1.7.2/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/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= +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/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/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= +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.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/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/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/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= @@ -89,48 +140,56 @@ github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/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.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= -github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4= -github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw= -github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= -github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= -github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +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/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/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= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +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= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= -github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= -github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/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= +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/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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= -github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= 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= @@ -141,31 +200,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.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg= -github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= +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.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s= -github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY= -github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= -github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= -github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= -github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g= -github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM= +github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff h1:5UGghwx8cI14qFa0ienrLekAYfhdKAiWvJUkY7rHmsI= +github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff/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.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= -github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= -github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw= -github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= -github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs= -github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk= +github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= +github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= +github.com/sagernet/sing-tun v0.6.1 h1:4l0+gnEKcGjlWfUVTD+W0BRApqIny/lU2ZliurE+VMo= +github.com/sagernet/sing-tun v0.6.1/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.79.0-mod.1 h1:wIuAH7VqBYJNk0h2+bTyk4F0OlSqHvyLDCBrD3i+XNI= +github.com/sagernet/tailscale v1.79.0-mod.1/go.mod h1:RKY5WjYLj3JJ7VO/8ZCw8eAFa4+kWU6A1Ftdk84uB14= 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= @@ -177,36 +236,60 @@ 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/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= -github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= +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= +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= +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-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= +github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= +github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= +github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +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/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= +github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= +github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= +github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= +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/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= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/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/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.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +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/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.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +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/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= @@ -215,53 +298,63 @@ 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/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-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= +go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +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-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= 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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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/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= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +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/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -276,17 +369,19 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/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/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= @@ -294,3 +389,5 @@ gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= 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= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= From 1699a7ce3371c87599441304e519b59cbe83a955 Mon Sep 17 00:00:00 2001 From: anytls Date: Thu, 20 Feb 2025 20:06:21 +0800 Subject: [PATCH 0374/1272] Add AnyTLS protocol --- constant/proxy.go | 1 + docs/configuration/inbound/anytls.md | 39 +++++++ docs/configuration/inbound/anytls.zh.md | 39 +++++++ docs/configuration/inbound/index.md | 1 + docs/configuration/inbound/index.zh.md | 1 + docs/configuration/outbound/anytls.md | 55 +++++++++ docs/configuration/outbound/anytls.zh.md | 55 +++++++++ docs/configuration/outbound/index.md | 1 + docs/configuration/outbound/index.zh.md | 1 + go.mod | 1 + go.sum | 4 + include/registry.go | 3 + option/anytls.go | 24 ++++ protocol/anytls/inbound.go | 135 +++++++++++++++++++++++ protocol/anytls/outbound.go | 129 ++++++++++++++++++++++ 15 files changed, 489 insertions(+) create mode 100644 docs/configuration/inbound/anytls.md create mode 100644 docs/configuration/inbound/anytls.zh.md create mode 100644 docs/configuration/outbound/anytls.md create mode 100644 docs/configuration/outbound/anytls.zh.md create mode 100644 option/anytls.go create mode 100644 protocol/anytls/inbound.go create mode 100644 protocol/anytls/outbound.go diff --git a/constant/proxy.go b/constant/proxy.go index 45e79f8455..787a12433f 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -19,6 +19,7 @@ const ( TypeTor = "tor" TypeSSH = "ssh" TypeShadowTLS = "shadowtls" + TypeAnyTLS = "anytls" TypeShadowsocksR = "shadowsocksr" TypeVLESS = "vless" TypeTUIC = "tuic" diff --git a/docs/configuration/inbound/anytls.md b/docs/configuration/inbound/anytls.md new file mode 100644 index 0000000000..ce4eefe654 --- /dev/null +++ b/docs/configuration/inbound/anytls.md @@ -0,0 +1,39 @@ +### Structure + +```json +{ + "type": "anytls", + "tag": "anytls-in", + + ... // Listen Fields + + "users": [ + { + "name": "sekai", + "password": "8JCsPssfgS8tiRwiMlhARg==" + } + ], + "padding_scheme": [], + "tls": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### users + +==Required== + +AnyTLS users. + +#### padding_scheme + +AnyTLS padding scheme line array. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). diff --git a/docs/configuration/inbound/anytls.zh.md b/docs/configuration/inbound/anytls.zh.md new file mode 100644 index 0000000000..27e78ba4ac --- /dev/null +++ b/docs/configuration/inbound/anytls.zh.md @@ -0,0 +1,39 @@ +### 结构 + +```json +{ + "type": "anytls", + "tag": "anytls-in", + + ... // 监听字段 + + "users": [ + { + "name": "sekai", + "password": "8JCsPssfgS8tiRwiMlhARg==" + } + ], + "padding_scheme": [], + "tls": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/)。 + +### 字段 + +#### users + +==必填== + +AnyTLS 用户。 + +#### padding_scheme + +AnyTLS 填充方案行数组。 + +#### tls + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 diff --git a/docs/configuration/inbound/index.md b/docs/configuration/inbound/index.md index 9ff4a7cf3e..27cc9fdbbd 100644 --- a/docs/configuration/inbound/index.md +++ b/docs/configuration/inbound/index.md @@ -30,6 +30,7 @@ | `tuic` | [TUIC](./tuic/) | :material-close: | | `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: | | `vless` | [VLESS](./vless/) | TCP | +| `anytls` | [AnyTLS](./anytls/) | TCP | | `tun` | [Tun](./tun/) | :material-close: | | `redirect` | [Redirect](./redirect/) | :material-close: | | `tproxy` | [TProxy](./tproxy/) | :material-close: | diff --git a/docs/configuration/inbound/index.zh.md b/docs/configuration/inbound/index.zh.md index 2c036340a7..1e0c0c4f65 100644 --- a/docs/configuration/inbound/index.zh.md +++ b/docs/configuration/inbound/index.zh.md @@ -30,6 +30,7 @@ | `tuic` | [TUIC](./tuic/) | :material-close: | | `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: | | `vless` | [VLESS](./vless/) | TCP | +| `anytls` | [AnyTLS](./anytls/) | TCP | | `tun` | [Tun](./tun/) | :material-close: | | `redirect` | [Redirect](./redirect/) | :material-close: | | `tproxy` | [TProxy](./tproxy/) | :material-close: | diff --git a/docs/configuration/outbound/anytls.md b/docs/configuration/outbound/anytls.md new file mode 100644 index 0000000000..42ee51d9a5 --- /dev/null +++ b/docs/configuration/outbound/anytls.md @@ -0,0 +1,55 @@ +### Structure + +```json +{ + "type": "anytls", + "tag": "anytls-out", + + "server": "127.0.0.1", + "server_port": 1080, + "password": "8JCsPssfgS8tiRwiMlhARg==", + "idle_session_check_interval": "30s", + "idle_session_timeout": "30s", + "tls": {}, + + ... // Dial Fields +} +``` + +### Fields + +#### server + +==Required== + +The server address. + +#### server_port + +==Required== + +The server port. + +#### password + +==Required== + +The AnyTLS password. + +#### idle_session_check_interval + +Interval checking for idle sessions. Default: 30s. + +#### idle_session_timeout + +In the check, close sessions that have been idle for longer than this. Default: 30s. + +#### tls + +==Required== + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/anytls.zh.md b/docs/configuration/outbound/anytls.zh.md new file mode 100644 index 0000000000..4f6421dfbb --- /dev/null +++ b/docs/configuration/outbound/anytls.zh.md @@ -0,0 +1,55 @@ +### 结构 + +```json +{ + "type": "anytls", + "tag": "anytls-out", + + "server": "127.0.0.1", + "server_port": 1080, + "password": "8JCsPssfgS8tiRwiMlhARg==", + "idle_session_check_interval": "30s", + "idle_session_timeout": "30s", + "tls": {}, + + ... // 拨号字段 +} +``` + +### 字段 + +#### server + +==必填== + +服务器地址。 + +#### server_port + +==必填== + +服务器端口。 + +#### password + +==必填== + +AnyTLS 密码。 + +#### idle_session_check_interval + +检查空闲会话的时间间隔。默认值:30秒。 + +#### idle_session_timeout + +在检查中,关闭闲置时间超过此值的会话。默认值:30秒。 + +#### tls + +==必填== + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/outbound/index.md b/docs/configuration/outbound/index.md index c5dc291769..b6094a1590 100644 --- a/docs/configuration/outbound/index.md +++ b/docs/configuration/outbound/index.md @@ -30,6 +30,7 @@ | `shadowtls` | [ShadowTLS](./shadowtls/) | | `tuic` | [TUIC](./tuic/) | | `hysteria2` | [Hysteria2](./hysteria2/) | +| `anytls` | [AnyTLS](./anytls/) | | `tor` | [Tor](./tor/) | | `ssh` | [SSH](./ssh/) | | `dns` | [DNS](./dns/) | diff --git a/docs/configuration/outbound/index.zh.md b/docs/configuration/outbound/index.zh.md index c7ee59e91d..1b6066e716 100644 --- a/docs/configuration/outbound/index.zh.md +++ b/docs/configuration/outbound/index.zh.md @@ -30,6 +30,7 @@ | `shadowtls` | [ShadowTLS](./shadowtls/) | | `tuic` | [TUIC](./tuic/) | | `hysteria2` | [Hysteria2](./hysteria2/) | +| `anytls` | [AnyTLS](./anytls/) | | `tor` | [Tor](./tor/) | | `ssh` | [SSH](./ssh/) | | `dns` | [DNS](./dns/) | diff --git a/go.mod b/go.mod index 5387346313..fe413b3b04 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/sagernet/sing-box go 1.23.1 require ( + github.com/anytls/sing-anytls v0.0.2 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 diff --git a/go.sum b/go.sum index f715eca06e..124f6b71bb 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,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.1 h1:Hex6GFUcgATWMWL2E9YgH/7oPgwdokiIF09UQi5BEC0= +github.com/anytls/sing-anytls v0.0.1/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= +github.com/anytls/sing-anytls v0.0.2 h1:25azSh0o/LMcIkhS4ZutgRTIGwh8O3wuOhsThVM9K9o= +github.com/anytls/sing-anytls v0.0.2/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.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= diff --git a/include/registry.go b/include/registry.go index 866c506afb..87aea5769b 100644 --- a/include/registry.go +++ b/include/registry.go @@ -15,6 +15,7 @@ import ( "github.com/sagernet/sing-box/dns/transport/local" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "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" @@ -53,6 +54,7 @@ func InboundRegistry() *inbound.Registry { naive.RegisterInbound(registry) shadowtls.RegisterInbound(registry) vless.RegisterInbound(registry) + anytls.RegisterInbound(registry) registerQUICInbounds(registry) registerStubForRemovedInbounds(registry) @@ -80,6 +82,7 @@ func OutboundRegistry() *outbound.Registry { ssh.RegisterOutbound(registry) shadowtls.RegisterOutbound(registry) vless.RegisterOutbound(registry) + anytls.RegisterOutbound(registry) registerQUICOutbounds(registry) registerWireGuardOutbound(registry) diff --git a/option/anytls.go b/option/anytls.go new file mode 100644 index 0000000000..0ac19cd194 --- /dev/null +++ b/option/anytls.go @@ -0,0 +1,24 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +type AnyTLSInboundOptions struct { + ListenOptions + InboundTLSOptionsContainer + Users []AnyTLSUser `json:"users,omitempty"` + PaddingScheme badoption.Listable[string] `json:"padding_scheme,omitempty"` +} + +type AnyTLSUser struct { + Name string `json:"name,omitempty"` + Password string `json:"password,omitempty"` +} + +type AnyTLSOutboundOptions struct { + DialerOptions + ServerOptions + OutboundTLSOptionsContainer + Password string `json:"password,omitempty"` + IdleSessionCheckInterval badoption.Duration `json:"idle_session_check_interval,omitempty"` + IdleSessionTimeout badoption.Duration `json:"idle_session_timeout,omitempty"` +} diff --git a/protocol/anytls/inbound.go b/protocol/anytls/inbound.go new file mode 100644 index 0000000000..89907f1d3c --- /dev/null +++ b/protocol/anytls/inbound.go @@ -0,0 +1,135 @@ +package anytls + +import ( + "context" + "net" + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + 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/auth" + 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" + + anytls "github.com/anytls/sing-anytls" + "github.com/anytls/sing-anytls/padding" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.AnyTLSInboundOptions](registry, C.TypeAnyTLS, NewInbound) +} + +type Inbound struct { + inbound.Adapter + tlsConfig tls.ServerConfig + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service *anytls.Service +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeAnyTLS, tag), + router: uot.NewRouter(router, logger), + logger: logger, + } + + if options.TLS != nil && options.TLS.Enabled { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + + paddingScheme := padding.DefaultPaddingScheme + if len(options.PaddingScheme) > 0 { + paddingScheme = []byte(strings.Join(options.PaddingScheme, "\n")) + } + + service, err := anytls.NewService(anytls.ServiceConfig{ + Users: common.Map(options.Users, func(it option.AnyTLSUser) anytls.User { + return (anytls.User)(it) + }), + PaddingScheme: paddingScheme, + Handler: (*inboundHandler)(inbound), + Logger: logger, + }) + if err != nil { + return nil, err + } + inbound.service = service + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) + return inbound, nil +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return err + } + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return common.Close(h.listener, h.tlsConfig) +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + if h.tlsConfig != nil { + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return + } + conn = tlsConn + } + err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +type inboundHandler Inbound + +func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //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 != "" { + metadata.User = userName + h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) + } else { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + } + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/protocol/anytls/outbound.go b/protocol/anytls/outbound.go new file mode 100644 index 0000000000..3a016d2eff --- /dev/null +++ b/protocol/anytls/outbound.go @@ -0,0 +1,129 @@ +package anytls + +import ( + "context" + "net" + "os" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/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" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" + + anytls "github.com/anytls/sing-anytls" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.AnyTLSOutboundOptions](registry, C.TypeAnyTLS, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + dialer N.Dialer + server M.Socksaddr + tlsConfig tls.Config + client *anytls.Client + uotClient *uot.Client + logger log.ContextLogger +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeAnyTLS, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + server: options.ServerOptions.Build(), + logger: logger, + } + if options.TLS == nil || !options.TLS.Enabled { + return nil, C.ErrTLSRequired + } + + tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + outbound.tlsConfig = tlsConfig + + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + }) + if err != nil { + return nil, err + } + outbound.dialer = outboundDialer + + client, err := anytls.NewClient(ctx, anytls.ClientConfig{ + Password: options.Password, + IdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(), + IdleSessionTimeout: options.IdleSessionTimeout.Build(), + DialOut: outbound.dialOut, + Logger: logger, + }) + if err != nil { + return nil, err + } + outbound.client = client + + outbound.uotClient = &uot.Client{ + Dialer: (anytlsDialer)(client.CreateProxy), + Version: uot.Version, + } + return outbound, nil +} + +type anytlsDialer func(ctx context.Context, destination M.Socksaddr) (net.Conn, error) + +func (d anytlsDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return d(ctx, destination) +} + +func (d anytlsDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +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 +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + return h.client.CreateProxy(ctx, destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return h.uotClient.DialContext(ctx, network, destination) + } + return nil, os.ErrInvalid +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return h.uotClient.ListenPacket(ctx, destination) +} + +func (h *Outbound) Close() error { + return common.Close(h.client) +} From 73de2a7d071f90b158c14b95141976d9b77ea20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 22 Feb 2025 17:41:03 +0800 Subject: [PATCH 0375/1272] documentation: Fix AnyTLS doc --- docs/configuration/inbound/anytls.md | 20 ++++++++++++++++++++ docs/configuration/inbound/anytls.zh.md | 20 ++++++++++++++++++++ docs/configuration/outbound/anytls.md | 6 ++++++ docs/configuration/outbound/anytls.zh.md | 6 ++++++ mkdocs.yml | 2 ++ 5 files changed, 54 insertions(+) diff --git a/docs/configuration/inbound/anytls.md b/docs/configuration/inbound/anytls.md index ce4eefe654..7eca943472 100644 --- a/docs/configuration/inbound/anytls.md +++ b/docs/configuration/inbound/anytls.md @@ -1,3 +1,9 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + ### Structure ```json @@ -34,6 +40,20 @@ AnyTLS users. AnyTLS padding scheme line array. +Default padding scheme: + +``` +stop=8 +0=34-120 +1=100-400 +2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500 +3=500-1000 +4=500-1000 +5=500-1000 +6=500-1000 +7=500-1000 +``` + #### tls TLS configuration, see [TLS](/configuration/shared/tls/#inbound). diff --git a/docs/configuration/inbound/anytls.zh.md b/docs/configuration/inbound/anytls.zh.md index 27e78ba4ac..5c119b724e 100644 --- a/docs/configuration/inbound/anytls.zh.md +++ b/docs/configuration/inbound/anytls.zh.md @@ -1,3 +1,9 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + ### 结构 ```json @@ -34,6 +40,20 @@ AnyTLS 用户。 AnyTLS 填充方案行数组。 +默认填充方案: + +``` +stop=8 +0=34-120 +1=100-400 +2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500 +3=500-1000 +4=500-1000 +5=500-1000 +6=500-1000 +7=500-1000 +``` + #### tls TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 diff --git a/docs/configuration/outbound/anytls.md b/docs/configuration/outbound/anytls.md index 42ee51d9a5..d5eb8fdf6b 100644 --- a/docs/configuration/outbound/anytls.md +++ b/docs/configuration/outbound/anytls.md @@ -1,3 +1,9 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + ### Structure ```json diff --git a/docs/configuration/outbound/anytls.zh.md b/docs/configuration/outbound/anytls.zh.md index 4f6421dfbb..1d0828b147 100644 --- a/docs/configuration/outbound/anytls.zh.md +++ b/docs/configuration/outbound/anytls.zh.md @@ -1,3 +1,9 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + ### 结构 ```json diff --git a/mkdocs.yml b/mkdocs.yml index e1a74fb09d..f786ac8479 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -144,6 +144,7 @@ nav: - VLESS: configuration/inbound/vless.md - TUIC: configuration/inbound/tuic.md - Hysteria2: configuration/inbound/hysteria2.md + - AnyTLS: configuration/inbound/anytls.md - Tun: configuration/inbound/tun.md - Redirect: configuration/inbound/redirect.md - TProxy: configuration/inbound/tproxy.md @@ -162,6 +163,7 @@ nav: - VLESS: configuration/outbound/vless.md - TUIC: configuration/outbound/tuic.md - Hysteria2: configuration/outbound/hysteria2.md + - AnyTLS: configuration/outbound/anytls.md - Tor: configuration/outbound/tor.md - SSH: configuration/outbound/ssh.md - DNS: configuration/outbound/dns.md From b4a8fa59f5ac1de57f2bbed21995990a1dd25428 Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Sun, 23 Feb 2025 01:23:37 +0100 Subject: [PATCH 0376/1272] Fix Outbound deadlock --- adapter/outbound/manager.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index 977fe4caba..44ac8bc59c 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -246,8 +246,6 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log. if err != nil { return err } - m.access.Lock() - defer m.access.Unlock() if m.started { for _, stage := range adapter.ListStartStages { err = adapter.LegacyStart(outbound, stage) @@ -256,6 +254,8 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log. } } } + m.access.Lock() + defer m.access.Unlock() if existsOutbound, loaded := m.outboundByTag[tag]; loaded { if m.started { err = common.Close(existsOutbound) From f2dad289fbe029f6d9eec532d961d60cc23fd556 Mon Sep 17 00:00:00 2001 From: libtry486 <89328481+libtry486@users.noreply.github.com> Date: Sun, 23 Feb 2025 08:27:41 +0800 Subject: [PATCH 0377/1272] documentation: Fix typo fix typo Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com> --- docs/configuration/dns/server/udp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/dns/server/udp.md b/docs/configuration/dns/server/udp.md index 4ffbf4d862..c551d3c9c5 100644 --- a/docs/configuration/dns/server/udp.md +++ b/docs/configuration/dns/server/udp.md @@ -4,7 +4,7 @@ icon: material/new-box !!! question "Since sing-box 1.12.0" -# TCP +# UDP ### Structure From 3e35390d8fb8b29a0e4bb805ecd6e5a4cce3c10d Mon Sep 17 00:00:00 2001 From: ReleTor <191429954+ReleTor@users.noreply.github.com> Date: Thu, 20 Feb 2025 17:39:39 +0800 Subject: [PATCH 0378/1272] documentation: Minor fixes --- docs/configuration/dns/server/http3.md | 2 +- docs/configuration/dns/server/https.md | 2 +- docs/configuration/dns/server/index.md | 26 ++++++------ docs/configuration/dns/server/index.zh.md | 46 +++++++++++++++++++++ docs/configuration/endpoint/index.md | 1 + docs/configuration/endpoint/index.zh.md | 5 ++- docs/configuration/endpoint/wireguard.zh.md | 2 +- docs/configuration/index.md | 2 + docs/configuration/index.zh.md | 2 + 9 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 docs/configuration/dns/server/index.zh.md diff --git a/docs/configuration/dns/server/http3.md b/docs/configuration/dns/server/http3.md index 89f000e847..dd81ba2dae 100644 --- a/docs/configuration/dns/server/http3.md +++ b/docs/configuration/dns/server/http3.md @@ -50,7 +50,7 @@ If domain name is used, `domain_resolver` must also be set to resolve IP address The port of the DNS server. -`853` will be used by default. +`443` will be used by default. #### path diff --git a/docs/configuration/dns/server/https.md b/docs/configuration/dns/server/https.md index 93dc71a4cd..46e69a558e 100644 --- a/docs/configuration/dns/server/https.md +++ b/docs/configuration/dns/server/https.md @@ -50,7 +50,7 @@ If domain name is used, `domain_resolver` must also be set to resolve IP address The port of the DNS server. -`853` will be used by default. +`443` will be used by default. #### path diff --git a/docs/configuration/dns/server/index.md b/docs/configuration/dns/server/index.md index 5393b0bdfe..0dae05c311 100644 --- a/docs/configuration/dns/server/index.md +++ b/docs/configuration/dns/server/index.md @@ -27,19 +27,19 @@ icon: material/alert-decagram The type of the DNS server. -| Type | Format | -|-----------------|-----------------------------------------------------| -| empty (default) | [Legacy](/configuration/dns/server/legacy/) | -| `tcp` | [TCP](/configuration/dns/server/tcp/) | -| `udp` | [UDP](/configuration/dns/server/udp/) | -| `tls` | [TLS](/configuration/dns/server/tls/) | -| `https` | [HTTPS](/configuration/dns/server/https/) | -| `quic` | [QUIC](/configuration/dns/server/quic/) | -| `h3` | [HTTP/3](/configuration/dns/server/http3/) | -| `predefined` | [Predefined](/configuration/dns/server/predefined/) | -| `dhcp` | [DHCP](/configuration/dns/server/dhcp/) | -| `fakeip` | [Fake IP](/configuration/dns/server/fakeip/) | - +| Type | Format | +|-----------------|-----------------------------| +| empty (default) | [Legacy](./legacy/) | +| `tcp` | [TCP](./tcp/) | +| `udp` | [UDP](./udp/) | +| `tls` | [TLS](./tls/) | +| `https` | [HTTPS](./https/) | +| `quic` | [QUIC](./quic/) | +| `h3` | [HTTP/3](./http3/) | +| `predefined` | [Predefined](./predefined/) | +| `dhcp` | [DHCP](./dhcp/) | +| `fakeip` | [Fake IP](./fakeip/) | +| `tailscale` | [Tailscale](./tailscale/) | #### tag diff --git a/docs/configuration/dns/server/index.zh.md b/docs/configuration/dns/server/index.zh.md new file mode 100644 index 0000000000..2ad9950210 --- /dev/null +++ b/docs/configuration/dns/server/index.zh.md @@ -0,0 +1,46 @@ +--- +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/) | +| `tcp` | [TCP](./tcp/) | +| `udp` | [UDP](./udp/) | +| `tls` | [TLS](./tls/) | +| `https` | [HTTPS](./https/) | +| `quic` | [QUIC](./quic/) | +| `h3` | [HTTP/3](./http3/) | +| `predefined` | [Predefined](./predefined/) | +| `dhcp` | [DHCP](./dhcp/) | +| `fakeip` | [Fake IP](./fakeip/) | +| `tailscale` | [Tailscale](./tailscale/) | + +#### tag + +DNS 服务器的标签。 diff --git a/docs/configuration/endpoint/index.md b/docs/configuration/endpoint/index.md index e40333db35..5e98c4cc21 100644 --- a/docs/configuration/endpoint/index.md +++ b/docs/configuration/endpoint/index.md @@ -26,6 +26,7 @@ Endpoint is protocols that has both inbound and outbound behavior. | Type | Format | |-------------|---------------------------| | `wireguard` | [WireGuard](./wireguard/) | +| `tailscale` | [Tailscale](./tailscale/) | #### tag diff --git a/docs/configuration/endpoint/index.zh.md b/docs/configuration/endpoint/index.zh.md index 69ba2d09ce..3060a6e6b4 100644 --- a/docs/configuration/endpoint/index.zh.md +++ b/docs/configuration/endpoint/index.zh.md @@ -23,9 +23,10 @@ icon: material/new-box ### 字段 -| 类型 | 格式 | +| 类型 | 格式 | |-------------|---------------------------| -| `wireguard` | [WireGuard](./wiregaurd/) | +| `wireguard` | [WireGuard](./wiregaurd/) | +| `tailscale` | [Tailscale](./tailscale/) | #### tag diff --git a/docs/configuration/endpoint/wireguard.zh.md b/docs/configuration/endpoint/wireguard.zh.md index 918e7cbfe3..f80c82b49d 100644 --- a/docs/configuration/endpoint/wireguard.zh.md +++ b/docs/configuration/endpoint/wireguard.zh.md @@ -41,7 +41,7 @@ icon: material/new-box ### 字段 -#### system_interface +#### system 使用系统设备。 diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 05e6a87da8..dc5d063776 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -9,6 +9,7 @@ sing-box uses JSON for configuration files. "log": {}, "dns": {}, "ntp": {}, + "certificate": {}, "endpoints": [], "inbounds": [], "outbounds": [], @@ -24,6 +25,7 @@ sing-box uses JSON for configuration files. | `log` | [Log](./log/) | | `dns` | [DNS](./dns/) | | `ntp` | [NTP](./ntp/) | +| `certificate` | [Certificate](./certificate/) | | `endpoints` | [Endpoint](./endpoint/) | | `inbounds` | [Inbound](./inbound/) | | `outbounds` | [Outbound](./outbound/) | diff --git a/docs/configuration/index.zh.md b/docs/configuration/index.zh.md index b639c72a28..a18cefd82e 100644 --- a/docs/configuration/index.zh.md +++ b/docs/configuration/index.zh.md @@ -9,6 +9,7 @@ sing-box 使用 JSON 作为配置文件格式。 "log": {}, "dns": {}, "ntp": {}, + "certificate": {}, "endpoints": [], "inbounds": [], "outbounds": [], @@ -24,6 +25,7 @@ sing-box 使用 JSON 作为配置文件格式。 | `log` | [日志](./log/) | | `dns` | [DNS](./dns/) | | `ntp` | [NTP](./ntp/) | +| `certificate` | [证书](./certificate/) | | `endpoints` | [端点](./endpoint/) | | `inbounds` | [入站](./inbound/) | | `outbounds` | [出站](./outbound/) | From 60b451e6cfbd73793efa1b704e3e84fc413ac233 Mon Sep 17 00:00:00 2001 From: anytls <198425817+anytls@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:40:12 +0800 Subject: [PATCH 0379/1272] Add MinIdleSession option to AnyTLS outbound Co-authored-by: anytls --- docs/configuration/outbound/anytls.md | 5 +++++ docs/configuration/outbound/anytls.zh.md | 5 +++++ go.mod | 2 +- go.sum | 6 ++---- option/anytls.go | 1 + protocol/anytls/outbound.go | 1 + 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/configuration/outbound/anytls.md b/docs/configuration/outbound/anytls.md index d5eb8fdf6b..4b3e037161 100644 --- a/docs/configuration/outbound/anytls.md +++ b/docs/configuration/outbound/anytls.md @@ -16,6 +16,7 @@ icon: material/new-box "password": "8JCsPssfgS8tiRwiMlhARg==", "idle_session_check_interval": "30s", "idle_session_timeout": "30s", + "min_idle_session": 5, "tls": {}, ... // Dial Fields @@ -50,6 +51,10 @@ Interval checking for idle sessions. Default: 30s. In the check, close sessions that have been idle for longer than this. Default: 30s. +#### min_idle_session + +In the check, at least the first `n` idle sessions are kept open. Default value: `n`=0 + #### tls ==Required== diff --git a/docs/configuration/outbound/anytls.zh.md b/docs/configuration/outbound/anytls.zh.md index 1d0828b147..1c888cfdd2 100644 --- a/docs/configuration/outbound/anytls.zh.md +++ b/docs/configuration/outbound/anytls.zh.md @@ -16,6 +16,7 @@ icon: material/new-box "password": "8JCsPssfgS8tiRwiMlhARg==", "idle_session_check_interval": "30s", "idle_session_timeout": "30s", + "min_idle_session": 5, "tls": {}, ... // 拨号字段 @@ -50,6 +51,10 @@ AnyTLS 密码。 在检查中,关闭闲置时间超过此值的会话。默认值:30秒。 +#### min_idle_session + +在检查中,至少前 `n` 个空闲会话保持打开状态。默认值:`n`=0 + #### tls ==必填== diff --git a/go.mod b/go.mod index fe413b3b04..894044e4fd 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/sagernet/sing-box go 1.23.1 require ( - github.com/anytls/sing-anytls v0.0.2 + github.com/anytls/sing-anytls v0.0.3 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 diff --git a/go.sum b/go.sum index 124f6b71bb..f139981877 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +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/anytls/sing-anytls v0.0.1 h1:Hex6GFUcgATWMWL2E9YgH/7oPgwdokiIF09UQi5BEC0= -github.com/anytls/sing-anytls v0.0.1/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= -github.com/anytls/sing-anytls v0.0.2 h1:25azSh0o/LMcIkhS4ZutgRTIGwh8O3wuOhsThVM9K9o= -github.com/anytls/sing-anytls v0.0.2/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= +github.com/anytls/sing-anytls v0.0.3 h1:JAQpETCUiZ1LFAPnO8vyJsdxypvKB4zinXwy9KiqWeQ= +github.com/anytls/sing-anytls v0.0.3/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.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= diff --git a/option/anytls.go b/option/anytls.go index 0ac19cd194..0f78526327 100644 --- a/option/anytls.go +++ b/option/anytls.go @@ -21,4 +21,5 @@ type AnyTLSOutboundOptions struct { Password string `json:"password,omitempty"` IdleSessionCheckInterval badoption.Duration `json:"idle_session_check_interval,omitempty"` IdleSessionTimeout badoption.Duration `json:"idle_session_timeout,omitempty"` + MinIdleSession int `json:"min_idle_session,omitempty"` } diff --git a/protocol/anytls/outbound.go b/protocol/anytls/outbound.go index 3a016d2eff..ce143bbe8d 100644 --- a/protocol/anytls/outbound.go +++ b/protocol/anytls/outbound.go @@ -63,6 +63,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL Password: options.Password, IdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(), IdleSessionTimeout: options.IdleSessionTimeout.Build(), + MinIdleSession: options.MinIdleSession, DialOut: outbound.dialOut, Logger: logger, }) From c51e9cbe062828eed03a3bdef19ac3ce64b86a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 24 Feb 2025 18:17:11 +0800 Subject: [PATCH 0380/1272] documentation: Fix missing hosts DNS server --- dns/transport/hosts/hosts.go | 4 +- docs/configuration/dns/server/hosts.md | 70 ++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 docs/configuration/dns/server/hosts.md diff --git a/dns/transport/hosts/hosts.go b/dns/transport/hosts/hosts.go index 29f6778a7a..773a1d2a3a 100644 --- a/dns/transport/hosts/hosts.go +++ b/dns/transport/hosts/hosts.go @@ -2,12 +2,14 @@ package hosts import ( "context" + "os" "github.com/sagernet/sing-box/adapter" 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/service/filemanager" mDNS "github.com/miekg/dns" ) @@ -29,7 +31,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt files = append(files, NewFile(DefaultPath)) } else { for _, path := range options.Path { - files = append(files, NewFile(path)) + files = append(files, NewFile(filemanager.BasePath(ctx, os.ExpandEnv(path)))) } } return &Transport{ diff --git a/docs/configuration/dns/server/hosts.md b/docs/configuration/dns/server/hosts.md new file mode 100644 index 0000000000..25c490edf8 --- /dev/null +++ b/docs/configuration/dns/server/hosts.md @@ -0,0 +1,70 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Hosts + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "hosts", + "tag": "", + + "path": [], + "predefined": {} + } + ] + } +} +``` + +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + +### Fields + +#### path + +List of paths to hosts files. + +`/etc/hosts` is used by default. + +`C:\Windows\System32\Drivers\etc\hosts` is used by default on Windows. + +Example: + +```json +{ + // "path": "/etc/hosts" + + "path": [ + "/etc/hosts", + "$HOME/.hosts" + ] +} +``` + +#### predefined + +Predefined hosts. + +Example: + +```json +{ + "predefined": { + "www.google.com": "127.0.0.1", + "localhost": [ + "127.0.0.1", + "::1" + ] + } +} +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index f786ac8479..1aaee8b184 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -84,6 +84,7 @@ nav: - configuration/dns/server/index.md - Legacy: configuration/dns/server/legacy.md - Local: configuration/dns/server/local.md + - Hosts: configuration/dns/server/hosts.md - TCP: configuration/dns/server/tcp.md - UDP: configuration/dns/server/udp.md - TLS: configuration/dns/server/tls.md From 668923c3925f2b467f5a27ae24d1c0ea150f8733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 26 Feb 2025 07:13:18 +0800 Subject: [PATCH 0381/1272] Fix DNS fallback --- dns/transport/local/local.go | 3 +++ dns/transport/local/local_fallback.go | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index e4236275d6..4e05ff0250 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -139,6 +139,9 @@ func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn stri } 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 + } var networks []string if useTCP { networks = []string{N.NetworkTCP} diff --git a/dns/transport/local/local_fallback.go b/dns/transport/local/local_fallback.go index 0a7cd9f0a1..cd2d198f52 100644 --- a/dns/transport/local/local_fallback.go +++ b/dns/transport/local/local_fallback.go @@ -58,8 +58,12 @@ func (f *FallbackTransport) Start(stage adapter.StartStage) error { return nil } +func (f *FallbackTransport) Close() error { + return nil +} + func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - if f.fallback { + if !f.fallback { return f.DNSTransport.Exchange(ctx, message) } question := message.Question[0] From 456fbecf16e4101767b635b877b98888da6716fd Mon Sep 17 00:00:00 2001 From: TargetLocked <32962687+TargetLocked@users.noreply.github.com> Date: Wed, 26 Feb 2025 07:28:42 +0800 Subject: [PATCH 0382/1272] Fix parsing legacy DNS options --- option/dns.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/option/dns.go b/option/dns.go index f441846870..c4186240f2 100644 --- a/option/dns.go +++ b/option/dns.go @@ -87,7 +87,7 @@ func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content } registry := service.FromContext[DNSTransportOptionsRegistry](ctx) if registry == nil { - return E.New("missing outbound options registry in context") + return E.New("missing DNS transport options registry in context") } var options any switch o.Type { @@ -102,7 +102,7 @@ func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content return E.New("unknown transport type: ", o.Type) } } - err = badjson.UnmarshallExcludedContext(ctx, content, (*_Outbound)(o), options) + err = badjson.UnmarshallExcludedContext(ctx, content, (*_NewDNSServerOptions)(o), options) if err != nil { return err } @@ -178,12 +178,10 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { if !serverAddr.IsValid() { return E.New("invalid server address") } - remoteOptions.Server = serverAddr.Addr.String() + remoteOptions.Server = serverAddr.AddrString() if serverAddr.Port != 0 && serverAddr.Port != 53 { remoteOptions.ServerPort = serverAddr.Port } - remoteOptions.Server = serverAddr.AddrString() - remoteOptions.ServerPort = serverAddr.Port case C.DNSTypeTCP: o.Type = C.DNSTypeTCP o.Options = &remoteOptions @@ -191,19 +189,17 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { if !serverAddr.IsValid() { return E.New("invalid server address") } - remoteOptions.Server = serverAddr.Addr.String() + remoteOptions.Server = serverAddr.AddrString() if serverAddr.Port != 0 && serverAddr.Port != 53 { remoteOptions.ServerPort = serverAddr.Port } - remoteOptions.Server = serverAddr.AddrString() - remoteOptions.ServerPort = serverAddr.Port case C.DNSTypeTLS, C.DNSTypeQUIC: o.Type = serverType serverAddr := M.ParseSocksaddr(serverURL.Host) if !serverAddr.IsValid() { return E.New("invalid server address") } - remoteOptions.Server = serverAddr.Addr.String() + remoteOptions.Server = serverAddr.AddrString() if serverAddr.Port != 0 && serverAddr.Port != 853 { remoteOptions.ServerPort = serverAddr.Port } @@ -222,7 +218,7 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { if !serverAddr.IsValid() { return E.New("invalid server address") } - httpsOptions.Server = serverAddr.Addr.String() + httpsOptions.Server = serverAddr.AddrString() if serverAddr.Port != 0 && serverAddr.Port != 443 { httpsOptions.ServerPort = serverAddr.Port } From 2d02b2b1cf1253c4f7376c3f1391e23c1e1d0ee7 Mon Sep 17 00:00:00 2001 From: Estel Date: Wed, 26 Feb 2025 07:29:17 +0800 Subject: [PATCH 0383/1272] documentation: Fix typo Signed-off-by: Estel --- docs/installation/package-manager.zh.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/installation/package-manager.zh.md b/docs/installation/package-manager.zh.md index 60905d1df3..f942e92465 100644 --- a/docs/installation/package-manager.zh.md +++ b/docs/installation/package-manager.zh.md @@ -63,7 +63,7 @@ curl -fsSL https://sing-box.app/install.sh | sh -s -- --version === ":material-linux: Linux" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |----------|---------------|------------------------------|---------------------------------------------------------------------------------------------------------------| | AUR | Arch Linux | `? -S sing-box` | [![AUR package](https://repology.org/badge/version-for-repo/aur/sing-box.svg)][aur] | | nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/sing-box.svg)][nixpkgs] | @@ -73,13 +73,13 @@ curl -fsSL https://sing-box.app/install.sh | sh -s -- --version === ":material-apple: macOS" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |----------|-------|-------------------------|------------------------------------------------------------------------------------------------| | Homebrew | macOS | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] | === ":material-microsoft-windows: Windows" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |------------|---------|---------------------------|-----------------------------------------------------------------------------------------------------| | Scoop | Windows | `scoop install sing-box` | [![Scoop package](https://repology.org/badge/version-for-repo/scoop/sing-box.svg)][scoop] | | Chocolatey | Windows | `choco install sing-box` | [![Chocolatey package](https://repology.org/badge/version-for-repo/chocolatey/sing-box.svg)][choco] | @@ -87,13 +87,13 @@ curl -fsSL https://sing-box.app/install.sh | sh -s -- --version === ":material-android: Android" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |--------|---------|--------------------|----------------------------------------------------------------------------------------------| | Termux | Android | `pkg add sing-box` | [![Termux package](https://repology.org/badge/version-for-repo/termux/sing-box.svg)][termux] | === ":material-freebsd: FreeBSD" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |------------|---------|------------------------|--------------------------------------------------------------------------------------------| | FreshPorts | FreeBSD | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] | From 50b0bd5c399943c61843e33102f6611a10f0af46 Mon Sep 17 00:00:00 2001 From: anytls <198425817+anytls@users.noreply.github.com> Date: Wed, 26 Feb 2025 07:29:43 +0800 Subject: [PATCH 0384/1272] Update sing-anytls Co-authored-by: anytls --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 894044e4fd..29970973ce 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/sagernet/sing-box go 1.23.1 require ( - github.com/anytls/sing-anytls v0.0.3 + github.com/anytls/sing-anytls v0.0.5 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 diff --git a/go.sum b/go.sum index f139981877..6847dcc7d0 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/anytls/sing-anytls v0.0.3 h1:JAQpETCUiZ1LFAPnO8vyJsdxypvKB4zinXwy9KiqWeQ= -github.com/anytls/sing-anytls v0.0.3/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= +github.com/anytls/sing-anytls v0.0.5 h1:I1NIh3zKTSXThLG5UgjsOOT/x2DZJqjfBzjuP/wZlDk= +github.com/anytls/sing-anytls v0.0.5/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.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= From 803811568e8f6d71474a8dec27d5c53c93c97dd3 Mon Sep 17 00:00:00 2001 From: Zephyruso <176294927+Zephyruso@users.noreply.github.com> Date: Wed, 26 Feb 2025 07:30:01 +0800 Subject: [PATCH 0385/1272] Fix missing AnyTLS display name --- constant/proxy.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/constant/proxy.go b/constant/proxy.go index 787a12433f..1044428b76 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -78,6 +78,8 @@ func ProxyDisplayName(proxyType string) string { return "TUIC" case TypeHysteria2: return "Hysteria2" + case TypeAnyTLS: + return "AnyTLS" case TypeSelector: return "Selector" case TypeURLTest: From dfcd9fb8c36516306cb8fecaeca42b538a194f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 26 Feb 2025 08:00:09 +0800 Subject: [PATCH 0386/1272] Fix domain resolver on direct outbound --- common/dialer/default.go | 2 ++ protocol/direct/outbound.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index 3b5e3dbf62..c5111b34d1 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -210,6 +210,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) { if !address.IsValid() { return nil, E.New("invalid address") + } else if address.IsFqdn() { + return nil, E.New("domain not resolved") } if d.networkStrategy == nil { switch N.NetworkName(network) { diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 9b454f58e7..7ad756f29f 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -45,7 +45,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if options.Detour != "" { return nil, E.New("`detour` is not supported in direct context") } - outboundDialer, err := dialer.New(ctx, options.DialerOptions, false) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, true) if err != nil { return nil, err } From b97947e8ac6ef2e931a4f3dfc47e9854e7b3c6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 26 Feb 2025 08:59:21 +0800 Subject: [PATCH 0387/1272] Move predefined DNS server to rule action --- common/dialer/resolve.go | 29 ++++--- constant/dns.go | 26 +++--- constant/rule.go | 1 + dns/router.go | 32 +++++++ dns/transport/predefined.go | 83 ------------------ docs/configuration/dns/rule_action.md | 60 ++++++++++++- docs/configuration/dns/rule_action.zh.md | 64 +++++++++++++- docs/configuration/dns/server/hosts.md | 28 ++++++- docs/configuration/dns/server/predefined.md | 93 --------------------- include/registry.go | 1 - mkdocs.yml | 1 - option/dns.go | 51 +++++++++-- option/dns_record.go | 61 +------------- option/rule_action.go | 12 +++ route/rule/rule_action.go | 29 +++++++ 15 files changed, 289 insertions(+), 282 deletions(-) delete mode 100644 dns/transport/predefined.go delete mode 100644 docs/configuration/dns/server/predefined.md diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index 66b74e3c55..49ed07038e 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -44,6 +44,20 @@ type resolveDialer struct { } func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer { + if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel { + return &resolveParallelNetworkDialer{ + resolveDialer{ + transport: service.FromContext[adapter.DNSTransportManager](ctx), + router: service.FromContext[adapter.DNSRouter](ctx), + dialer: dialer, + parallel: parallel, + server: server, + queryOptions: queryOptions, + fallbackDelay: fallbackDelay, + }, + parallelDialer, + } + } return &resolveDialer{ transport: service.FromContext[adapter.DNSTransportManager](ctx), router: service.FromContext[adapter.DNSRouter](ctx), @@ -60,21 +74,6 @@ type resolveParallelNetworkDialer struct { dialer ParallelInterfaceDialer } -func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer { - return &resolveParallelNetworkDialer{ - resolveDialer{ - transport: service.FromContext[adapter.DNSTransportManager](ctx), - router: service.FromContext[adapter.DNSRouter](ctx), - dialer: dialer, - parallel: parallel, - server: server, - queryOptions: queryOptions, - fallbackDelay: fallbackDelay, - }, - dialer, - } -} - func (d *resolveDialer) initialize() error { d.initOnce.Do(d.initServer) return d.initErr diff --git a/constant/dns.go b/constant/dns.go index 0a444ff0cf..99e1ec0edc 100644 --- a/constant/dns.go +++ b/constant/dns.go @@ -15,19 +15,19 @@ const ( ) const ( - DNSTypeLegacy = "legacy" - DNSTypeUDP = "udp" - DNSTypeTCP = "tcp" - DNSTypeTLS = "tls" - DNSTypeHTTPS = "https" - DNSTypeQUIC = "quic" - DNSTypeHTTP3 = "h3" - DNSTypeHosts = "hosts" - DNSTypeLocal = "local" - DNSTypePreDefined = "predefined" - DNSTypeFakeIP = "fakeip" - DNSTypeDHCP = "dhcp" - DNSTypeTailscale = "tailscale" + DNSTypeLegacy = "legacy" + DNSTypeLegacyRcode = "legacy_rcode" + DNSTypeUDP = "udp" + DNSTypeTCP = "tcp" + DNSTypeTLS = "tls" + DNSTypeHTTPS = "https" + DNSTypeQUIC = "quic" + DNSTypeHTTP3 = "h3" + DNSTypeLocal = "local" + DNSTypeHosts = "hosts" + DNSTypeFakeIP = "fakeip" + DNSTypeDHCP = "dhcp" + DNSTypeTailscale = "tailscale" ) const ( diff --git a/constant/rule.go b/constant/rule.go index c4a778384f..336c3b389a 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -33,6 +33,7 @@ const ( RuleActionTypeHijackDNS = "hijack-dns" RuleActionTypeSniff = "sniff" RuleActionTypeResolve = "resolve" + RuleActionTypePredefined = "predefined" ) const ( diff --git a/dns/router.go b/dns/router.go index dd6b8780e2..cba6cdcba4 100644 --- a/dns/router.go +++ b/dns/router.go @@ -190,6 +190,8 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, } case *R.RuleActionReject: return nil, currentRule, currentRuleIndex + case *R.RuleActionPredefined: + return nil, currentRule, currentRuleIndex } } } @@ -267,6 +269,21 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte case C.RuleActionRejectMethodDrop: return nil, tun.ErrDrop } + case *R.RuleActionPredefined: + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: action.Rcode, + }, + Question: message.Question, + Answer: action.Answer, + Ns: action.Ns, + Extra: action.Extra, + }, nil } } var responseCheck func(responseAddrs []netip.Addr) bool @@ -383,6 +400,20 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ case C.RuleActionRejectMethodDrop: return nil, tun.ErrDrop } + case *R.RuleActionPredefined: + if action.Rcode != mDNS.RcodeSuccess { + err = RcodeError(action.Rcode) + } else { + for _, answer := range action.Answer { + switch record := answer.(type) { + case *mDNS.A: + responseAddrs = append(responseAddrs, M.AddrFromIP(record.A)) + case *mDNS.AAAA: + responseAddrs = append(responseAddrs, M.AddrFromIP(record.AAAA)) + } + } + } + goto response } } var responseCheck func(responseAddrs []netip.Addr) bool @@ -402,6 +433,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ printResult() } } +response: printResult() if len(responseAddrs) > 0 { r.logger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " ")) diff --git a/dns/transport/predefined.go b/dns/transport/predefined.go deleted file mode 100644 index dbb78e5c5d..0000000000 --- a/dns/transport/predefined.go +++ /dev/null @@ -1,83 +0,0 @@ -package transport - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - 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" - E "github.com/sagernet/sing/common/exceptions" - - mDNS "github.com/miekg/dns" -) - -var _ adapter.DNSTransport = (*PredefinedTransport)(nil) - -func RegisterPredefined(registry *dns.TransportRegistry) { - dns.RegisterTransport[option.PredefinedDNSServerOptions](registry, C.DNSTypePreDefined, NewPredefined) -} - -type PredefinedTransport struct { - dns.TransportAdapter - responses []*predefinedResponse -} - -type predefinedResponse struct { - questions []mDNS.Question - answer *mDNS.Msg -} - -func NewPredefined(ctx context.Context, logger log.ContextLogger, tag string, options option.PredefinedDNSServerOptions) (adapter.DNSTransport, error) { - var responses []*predefinedResponse - for _, response := range options.Responses { - questions, msg, err := response.Build() - if err != nil { - return nil, err - } - responses = append(responses, &predefinedResponse{ - questions: questions, - answer: msg, - }) - } - if len(responses) == 0 { - return nil, E.New("empty predefined responses") - } - return &PredefinedTransport{ - TransportAdapter: dns.NewTransportAdapter(C.DNSTypePreDefined, tag, nil), - responses: responses, - }, nil -} - -func (t *PredefinedTransport) Reset() { -} - -func (t *PredefinedTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - for _, response := range t.responses { - for _, question := range response.questions { - if func() bool { - if question.Name == "" && question.Qtype == mDNS.TypeNone { - return true - } else if question.Name == "" { - return common.Any(message.Question, func(it mDNS.Question) bool { - return it.Qtype == question.Qtype - }) - } else if question.Qtype == mDNS.TypeNone { - return common.Any(message.Question, func(it mDNS.Question) bool { - return it.Name == question.Name - }) - } else { - return common.Contains(message.Question, question) - } - }() { - copyAnswer := *response.answer - copyAnswer.Id = message.Id - copyAnswer.Question = message.Question - return ©Answer, nil - } - } - } - return nil, dns.RcodeNameError -} diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index 33c283fa14..62c81c326f 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -4,7 +4,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.12.0" - :material-plus: [strategy](#strategy) + :material-plus: [strategy](#strategy) + :material-plus: [predefined](#predefined) !!! question "Since sing-box 1.11.0" @@ -31,6 +32,8 @@ Tag of target server. #### strategy +!!! question "Since sing-box 1.12.0" + Set domain strategy for this query. One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. @@ -69,7 +72,7 @@ Will overrides `dns.client_subnet`. ```json { "action": "reject", - "method": "default", // default + "method": "", "no_drop": false } ``` @@ -81,8 +84,61 @@ Will overrides `dns.client_subnet`. - `default`: Reply with NXDOMAIN. - `drop`: Drop the request. +`default` will be used by default. + #### no_drop If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. Not available when `method` is set to drop. + +### predefined + +!!! question "Since sing-box 1.12.0" + +```json +{ + "action": "predefined", + "rcode": "", + "answer": [], + "ns": [], + "extra": [] +} +``` + +`predefined` responds with predefined DNS records. + +#### rcode + +The response code. + +| Value | Value in the legacy rcode server | Description | +|------------|----------------------------------|-----------------| +| `NOERROR` | `success` | Ok | +| `FORMERR` | `format_error` | Bad request | +| `SERVFAIL` | `server_failure` | Server failure | +| `NXDOMAIN` | `name_error` | Not found | +| `NOTIMP` | `not_implemented` | Not implemented | +| `REFUSED` | `refused` | Refused | + +`NOERROR` will be used by default. + +#### answer + +List of text DNS record to respond as answers. + +Examples: + +| Record Type | Example | +|-------------|-------------------------------| +| `A` | `localhost. IN A 127.0.0.1` | +| `AAAA` | `localhost. IN AAAA ::1` | +| `TXT` | `localhost. IN TXT \"Hello\"` | + +#### ns + +List of text DNS record to respond as name servers. + +#### extra + +List of text DNS record to respond as extra records. diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 03843ec5d7..7aa3da6dad 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -4,7 +4,8 @@ icon: material/new-box !!! quote "sing-box 1.12.0 中的更改" - :material-plus: [strategy](#strategy) + :material-plus: [strategy](#strategy) + :material-plus: [predefined](#predefined) !!! question "自 sing-box 1.11.0 起" @@ -12,9 +13,9 @@ icon: material/new-box ```json { - "action": "route", // 默认 + "action": "route", + // 默认 "server": "", - "strategy": "", "disable_cache": false, "rewrite_ttl": null, @@ -32,6 +33,8 @@ icon: material/new-box #### strategy +!!! question "自 sing-box 1.12.0 起" + 为此查询设置域名策略。 可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 @@ -70,7 +73,7 @@ icon: material/new-box ```json { "action": "reject", - "method": "default", // default + "method": "", "no_drop": false } ``` @@ -82,8 +85,61 @@ icon: material/new-box - `default`: 返回 NXDOMAIN。 - `drop`: 丢弃请求。 +默认使用 `defualt`。 + #### no_drop 如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 当 `method` 设为 `drop` 时不可用。 + +### predefined + +!!! question "自 sing-box 1.12.0 起" + +```json +{ + "action": "predefined", + "rcode": "", + "answer": [], + "ns": [], + "extra": [] +} +``` + +`predefined` 以预定义的 DNS 记录响应。 + +#### rcode + +响应码。 + +| 值 | 旧 rcode DNS 服务器中的值 | 描述 | +|------------|--------------------|-----------------| +| `NOERROR` | `success` | Ok | +| `FORMERR` | `format_error` | Bad request | +| `SERVFAIL` | `server_failure` | Server failure | +| `NXDOMAIN` | `name_error` | Not found | +| `NOTIMP` | `not_implemented` | Not implemented | +| `REFUSED` | `refused` | Refused | + +默认使用 `NOERROR`。 + +#### answer + +用于作为回答响应的文本 DNS 记录列表。 + +例子: + +| 记录类型 | 例子 | +|--------|-------------------------------| +| `A` | `localhost. IN A 127.0.0.1` | +| `AAAA` | `localhost. IN AAAA ::1` | +| `TXT` | `localhost. IN TXT \"Hello\"` | + +#### ns + +用于作为名称服务器响应的文本 DNS 记录列表。 + +#### extra + +用于作为额外记录响应的文本 DNS 记录列表。 diff --git a/docs/configuration/dns/server/hosts.md b/docs/configuration/dns/server/hosts.md index 25c490edf8..ce859cca37 100644 --- a/docs/configuration/dns/server/hosts.md +++ b/docs/configuration/dns/server/hosts.md @@ -67,4 +67,30 @@ Example: ] } } -``` \ No newline at end of file +``` + +### Examples + +=== "Use hosts if available" + + ```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/predefined.md b/docs/configuration/dns/server/predefined.md deleted file mode 100644 index ac75d6bbe0..0000000000 --- a/docs/configuration/dns/server/predefined.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -icon: material/new-box ---- - -!!! question "Since sing-box 1.12.0" - -# Predefined - -### Structure - -```json -{ - "dns": { - "servers": [ - { - "type": "predefined", - "tag": "", - "responses": [] - } - ] - } -} -``` - -### Fields - -#### responses - -==Required== - -List of [Response](#response-structure). - -### Response Structure - -```json -{ - "query": [], - "query_type": [], - "rcode": "", - "answer": [], - "ns": [], - "extra": [] -} -``` - -!!! note "" - - You can ignore the JSON Array [] tag when the content is only one item - -### Response Fields - -#### query - -List of domain name to match. - -#### query_type - -List of query type to match. - -#### rcode - -The response code. - -| Value | Value in the legacy rcode server | Description | -|------------|----------------------------------|-----------------| -| `NOERROR` | `success` | Ok | -| `FORMERR` | `format_error` | Bad request | -| `SERVFAIL` | `server_failure` | Server failure | -| `NXDOMAIN` | `name_error` | Not found | -| `NOTIMP` | `not_implemented` | Not implemented | -| `REFUSED` | `refused` | Refused | - -`NOERROR` will be used by default. - -#### answer - -List of text DNS record to respond as answers. - -Examples: - -| Record Type | Example | -|-------------|-------------------------------| -| `A` | `localhost. IN A 127.0.0.1` | -| `AAAA` | `localhost. IN AAAA ::1` | -| `TXT` | `localhost. IN TXT \"Hello\"` | - -#### ns - -List of text DNS record to respond as name servers. - -#### extra - -List of text DNS record to respond as extra records. diff --git a/include/registry.go b/include/registry.go index 87aea5769b..9be1f2b4f6 100644 --- a/include/registry.go +++ b/include/registry.go @@ -107,7 +107,6 @@ func DNSTransportRegistry() *dns.TransportRegistry { transport.RegisterUDP(registry) transport.RegisterTLS(registry) transport.RegisterHTTPS(registry) - transport.RegisterPredefined(registry) hosts.RegisterTransport(registry) local.RegisterTransport(registry) fakeip.RegisterTransport(registry) diff --git a/mkdocs.yml b/mkdocs.yml index 1aaee8b184..8ee45c948f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -91,7 +91,6 @@ nav: - QUIC: configuration/dns/server/quic.md - HTTPS: configuration/dns/server/https.md - HTTP3: configuration/dns/server/http3.md - - Predefined: configuration/dns/server/predefined.md - DHCP: configuration/dns/server/dhcp.md - FakeIP: configuration/dns/server/fakeip.md - Tailscale: configuration/dns/server/tailscale.md diff --git a/option/dns.go b/option/dns.go index c4186240f2..64dbea3896 100644 --- a/option/dns.go +++ b/option/dns.go @@ -46,7 +46,46 @@ func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) e } legacyOptions := o.LegacyDNSOptions o.LegacyDNSOptions = LegacyDNSOptions{} - return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions) + err = badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions) + if err != nil { + return err + } + rcodeMap := make(map[string]int) + o.Servers = common.Filter(o.Servers, func(it NewDNSServerOptions) bool { + if it.Type == C.DNSTypeLegacyRcode { + rcodeMap[it.Tag] = it.Options.(int) + return false + } + return true + }) + if len(rcodeMap) > 0 { + for i := 0; i < len(o.Rules); i++ { + rewriteRcode(rcodeMap, &o.Rules[i]) + } + } + return nil +} + +func rewriteRcode(rcodeMap map[string]int, rule *DNSRule) { + switch rule.Type { + case C.RuleTypeDefault: + rewriteRcodeAction(rcodeMap, &rule.DefaultOptions.DNSRuleAction) + case C.RuleTypeLogical: + rewriteRcodeAction(rcodeMap, &rule.LogicalOptions.DNSRuleAction) + } +} + +func rewriteRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) { + if ruleAction.Action != C.RuleActionTypeRoute { + return + } + rcode, loaded := rcodeMap[ruleAction.RouteOptions.Server] + if !loaded { + return + } + ruleAction.Action = C.RuleActionTypePredefined + ruleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode)) + return } type DNSClientOptions struct { @@ -243,14 +282,8 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { default: return E.New("unknown rcode: ", serverURL.Host) } - o.Type = C.DNSTypePreDefined - o.Options = &PredefinedDNSServerOptions{ - Responses: []DNSResponseOptions{ - { - RCode: common.Ptr(DNSRCode(rcode)), - }, - }, - } + o.Type = C.DNSTypeLegacyRcode + o.Options = rcode case C.DNSTypeDHCP: o.Type = C.DNSTypeDHCP dhcpOptions := DHCPDNSServerOptions{} diff --git a/option/dns_record.go b/option/dns_record.go index c76a76c6f0..fa72b61b73 100644 --- a/option/dns_record.go +++ b/option/dns_record.go @@ -3,30 +3,14 @@ package option import ( "encoding/base64" - "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" - "github.com/sagernet/sing/common/json/badoption" M "github.com/sagernet/sing/common/metadata" "github.com/miekg/dns" ) -type PredefinedDNSServerOptions struct { - Responses []DNSResponseOptions `json:"responses,omitempty"` -} - -type DNSResponseOptions struct { - Query badoption.Listable[string] `json:"query,omitempty"` - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - - RCode *DNSRCode `json:"rcode,omitempty"` - Answer badoption.Listable[DNSRecordOptions] `json:"answer,omitempty"` - Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"` - Extra badoption.Listable[DNSRecordOptions] `json:"extra,omitempty"` -} - type DNSRCode int func (r DNSRCode) MarshalJSON() ([]byte, error) { @@ -64,49 +48,6 @@ func (r *DNSRCode) Build() int { return int(*r) } -func (o DNSResponseOptions) Build() ([]dns.Question, *dns.Msg, error) { - var questions []dns.Question - if len(o.Query) == 0 && len(o.QueryType) == 0 { - questions = []dns.Question{{Qclass: dns.ClassINET}} - } else if len(o.Query) == 0 { - for _, queryType := range o.QueryType { - questions = append(questions, dns.Question{ - Qtype: uint16(queryType), - Qclass: dns.ClassINET, - }) - } - } else if len(o.QueryType) == 0 { - for _, domain := range o.Query { - questions = append(questions, dns.Question{ - Name: dns.Fqdn(domain), - Qclass: dns.ClassINET, - }) - } - } else { - for _, queryType := range o.QueryType { - for _, domain := range o.Query { - questions = append(questions, dns.Question{ - Name: dns.Fqdn(domain), - Qtype: uint16(queryType), - Qclass: dns.ClassINET, - }) - } - } - } - return questions, &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Response: true, - Rcode: o.RCode.Build(), - Authoritative: true, - RecursionDesired: true, - RecursionAvailable: true, - }, - Answer: common.Map(o.Answer, DNSRecordOptions.build), - Ns: common.Map(o.Ns, DNSRecordOptions.build), - Extra: common.Map(o.Extra, DNSRecordOptions.build), - }, nil -} - type DNSRecordOptions struct { dns.RR fromBase64 bool @@ -156,6 +97,6 @@ func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error { return nil } -func (o DNSRecordOptions) build() dns.RR { +func (o DNSRecordOptions) Build() dns.RR { return o.RR } diff --git a/option/rule_action.go b/option/rule_action.go index 00d3ae7a4d..7c05dce68a 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -92,6 +92,7 @@ type _DNSRuleAction struct { RouteOptions DNSRouteActionOptions `json:"-"` RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"` RejectOptions RejectActionOptions `json:"-"` + PredefinedOptions DNSRouteActionPredefined `json:"-"` } type DNSRuleAction _DNSRuleAction @@ -109,6 +110,8 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) { v = r.RouteOptionsOptions case C.RuleActionTypeReject: v = r.RejectOptions + case C.RuleActionTypePredefined: + v = r.PredefinedOptions default: return nil, E.New("unknown DNS rule action: " + r.Action) } @@ -129,6 +132,8 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e v = &r.RouteOptionsOptions case C.RuleActionTypeReject: v = &r.RejectOptions + case C.RuleActionTypePredefined: + v = &r.PredefinedOptions default: return E.New("unknown DNS rule action: " + r.Action) } @@ -294,3 +299,10 @@ type RouteActionResolve struct { RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` } + +type DNSRouteActionPredefined struct { + Rcode *DNSRCode `json:"rcode,omitempty"` + Answer badoption.Listable[DNSRecordOptions] `json:"answer,omitempty"` + Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"` + Extra badoption.Listable[DNSRecordOptions] `json:"extra,omitempty"` +} diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 2599d064f2..8d6391d9e3 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -21,6 +21,8 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + + "github.com/miekg/dns" ) func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { @@ -127,6 +129,13 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) NoDrop: action.RejectOptions.NoDrop, logger: logger, } + case C.RuleActionTypePredefined: + return &RuleActionPredefined{ + Rcode: action.PredefinedOptions.Rcode.Build(), + Answer: common.Map(action.PredefinedOptions.Answer, option.DNSRecordOptions.Build), + Ns: common.Map(action.PredefinedOptions.Ns, option.DNSRecordOptions.Build), + Extra: common.Map(action.PredefinedOptions.Extra, option.DNSRecordOptions.Build), + } default: panic(F.ToString("unknown rule action: ", action.Action)) } @@ -434,3 +443,23 @@ func (r *RuleActionResolve) String() string { return F.ToString("resolve(", strings.Join(options, ","), ")") } } + +type RuleActionPredefined struct { + Rcode int + Answer []dns.RR + Ns []dns.RR + Extra []dns.RR +} + +func (r *RuleActionPredefined) Type() string { + return C.RuleActionTypePredefined +} + +func (r *RuleActionPredefined) String() string { + var options []string + options = append(options, dns.RcodeToString[r.Rcode]) + options = append(options, common.Map(r.Answer, dns.RR.String)...) + options = append(options, common.Map(r.Ns, dns.RR.String)...) + options = append(options, common.Map(r.Extra, dns.RR.String)...) + return F.ToString("predefined(", strings.Join(options, ","), ")") +} From ff416aacaf7ea85056451416902e6549f529c122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 27 Feb 2025 09:40:20 +0800 Subject: [PATCH 0388/1272] Fix anytls dialer usage --- protocol/anytls/outbound.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/protocol/anytls/outbound.go b/protocol/anytls/outbound.go index ce143bbe8d..b026ed1803 100644 --- a/protocol/anytls/outbound.go +++ b/protocol/anytls/outbound.go @@ -51,8 +51,9 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL outbound.tlsConfig = tlsConfig outboundDialer, err := dialer.NewWithOptions(dialer.Options{ - Context: ctx, - Options: options.DialerOptions, + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain(), }) if err != nil { return nil, err From 4accaccf777eedf3d8851bdbb64a6fcfe2634756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 6 Mar 2025 14:02:16 +0800 Subject: [PATCH 0389/1272] documentation: Fix missing `ip_accept_any` DNS rule option --- docs/configuration/dns/rule.md | 38 +++++++++++++------- docs/configuration/dns/rule.zh.md | 46 +++++++++++++++--------- docs/configuration/dns/rule_action.zh.md | 3 +- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 3039bffce3..5d5cb7c3a1 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -4,6 +4,7 @@ icon: material/alert-decagram !!! quote "Changes in sing-box 1.12.0" + :material-plus: [ip_accept_any](#ip_accept_any) :material-delete-clock: [outbound](#outbound) !!! quote "Changes in sing-box 1.11.0" @@ -77,15 +78,6 @@ icon: material/alert-decagram "domain_regex": [ "^stun\\..+" ], - "geosite": [ - "cn" - ], - "source_geoip": [ - "private" - ], - "geoip": [ - "cn" - ], "source_ip_cidr": [ "10.0.0.0/24", "192.168.0.1" @@ -96,6 +88,7 @@ icon: material/alert-decagram "192.168.0.1" ], "ip_is_private": false, + "ip_accept_any": false, "source_port": [ 12345 ], @@ -147,8 +140,6 @@ icon: material/alert-decagram "geoip-cn", "geosite-cn" ], - // deprecated - "rule_set_ipcidr_match_source": false, "rule_set_ip_cidr_match_source": false, "rule_set_ip_cidr_accept_empty": false, "invert": false, @@ -156,7 +147,20 @@ icon: material/alert-decagram "direct" ], "action": "route", - "server": "local" + "server": "local", + + // Deprecated + + "rule_set_ipcidr_match_source": false, + "geosite": [ + "cn" + ], + "source_geoip": [ + "private" + ], + "geoip": [ + "cn" + ] }, { "type": "logical", @@ -451,7 +455,9 @@ Only takes effect for address requests (A/AAAA/HTTPS). When the query results do #### geoip -!!! question "Since sing-box 1.9.0" +!!! failure "Removed in sing-box 1.12.0" + + GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match GeoIP with query response. @@ -473,6 +479,12 @@ Match private IP with query response. Make `ip_cidr` rules in rule-sets accept empty query response. +#### ip_accept_any + +!!! question "Since sing-box 1.12.0" + +Match any IP with query response. + ### Logical Fields #### type diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 94ca453509..8973eba2bc 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -4,6 +4,7 @@ icon: material/alert-decagram !!! quote "sing-box 1.12.0 中的更改" + :material-plus: [ip_accept_any](#ip_accept_any) :material-delete-clock: [outbound](#outbound) !!! quote "sing-box 1.11.0 中的更改" @@ -77,15 +78,6 @@ icon: material/alert-decagram "domain_regex": [ "^stun\\..+" ], - "geosite": [ - "cn" - ], - "source_geoip": [ - "private" - ], - "geoip": [ - "cn" - ], "source_ip_cidr": [ "10.0.0.0/24", "192.168.0.1" @@ -96,6 +88,7 @@ icon: material/alert-decagram "192.168.0.1" ], "ip_is_private": false, + "ip_accept_any": false, "source_port": [ 12345 ], @@ -147,8 +140,6 @@ icon: material/alert-decagram "geoip-cn", "geosite-cn" ], - // 已弃用 - "rule_set_ipcidr_match_source": false, "rule_set_ip_cidr_match_source": false, "rule_set_ip_cidr_accept_empty": false, "invert": false, @@ -156,7 +147,19 @@ icon: material/alert-decagram "direct" ], "action": "route", - "server": "local" + "server": "local", + + // 已弃用 + "rule_set_ipcidr_match_source": false, + "geosite": [ + "cn" + ], + "source_geoip": [ + "private" + ], + "geoip": [ + "cn" + ] }, { "type": "logical", @@ -232,17 +235,17 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 #### geosite -!!! failure "已在 sing-box 1.8.0 废弃" +!!! failure "已在 sing-box 1.12.0 中被移除" - Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。 + GeoSite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。 匹配 Geosite。 #### source_geoip -!!! failure "已在 sing-box 1.8.0 废弃" +!!! failure "已在 sing-box 1.12.0 中被移除" - GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。 + GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。 匹配源 GeoIP。 @@ -451,7 +454,10 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. #### geoip -!!! question "自 sing-box 1.9.0 起" +!!! failure "已在 sing-box 1.12.0 中被移除" + + GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。 + 与查询响应匹配 GeoIP。 @@ -467,6 +473,12 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. 与查询响应匹配非公开 IP。 +#### ip_accept_any + +!!! question "自 sing-box 1.12.0 起" + +匹配任意 IP。 + #### rule_set_ip_cidr_accept_empty !!! question "自 sing-box 1.10.0 起" diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 7aa3da6dad..c2921ace26 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -13,8 +13,7 @@ icon: material/new-box ```json { - "action": "route", - // 默认 + "action": "route", // 默认 "server": "", "strategy": "", "disable_cache": false, From 32873d06bc275ea833a2c564dc11cd7761e3ef58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 6 Mar 2025 14:06:55 +0800 Subject: [PATCH 0390/1272] Fix UDP DNS server crash --- dns/transport/udp.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dns/transport/udp.go b/dns/transport/udp.go index 5099c6f6d8..289b1fe2e8 100644 --- a/dns/transport/udp.go +++ b/dns/transport/udp.go @@ -110,13 +110,6 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M conn.access.Lock() delete(conn.callbacks, messageId) conn.access.Unlock() - callback.access.Lock() - select { - case <-callback.done: - default: - close(callback.done) - } - callback.access.Unlock() }() rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes()) if err != nil { From 0c523980fff00e4197cd027c0864c90ef0b2b4ab Mon Sep 17 00:00:00 2001 From: k9982874 Date: Mon, 10 Mar 2025 13:57:59 +0800 Subject: [PATCH 0391/1272] Fix hosts DNS server --- dns/client.go | 4 ++-- dns/transport/hosts/hosts.go | 20 +++++++++++++++++--- dns/transport/hosts/hosts_file.go | 2 +- dns/transport/hosts/hosts_test.go | 4 ++-- option/dns.go | 4 ++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/dns/client.go b/dns/client.go index d44883b54a..f456f878b5 100644 --- a/dns/client.go +++ b/dns/client.go @@ -537,7 +537,7 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim Question: []dns.Question{question}, } for _, address := range addresses { - if address.Is4() { + if address.Is4() && question.Qtype == dns.TypeA { response.Answer = append(response.Answer, &dns.A{ Hdr: dns.RR_Header{ Name: question.Name, @@ -547,7 +547,7 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim }, A: address.AsSlice(), }) - } else { + } else if address.Is6() && question.Qtype == dns.TypeAAAA { response.Answer = append(response.Answer, &dns.AAAA{ Hdr: dns.RR_Header{ Name: question.Name, diff --git a/dns/transport/hosts/hosts.go b/dns/transport/hosts/hosts.go index 773a1d2a3a..0a1dd395bd 100644 --- a/dns/transport/hosts/hosts.go +++ b/dns/transport/hosts/hosts.go @@ -2,6 +2,7 @@ package hosts import ( "context" + "net/netip" "os" "github.com/sagernet/sing-box/adapter" @@ -22,11 +23,15 @@ var _ adapter.DNSTransport = (*Transport)(nil) type Transport struct { dns.TransportAdapter - files []*File + files []*File + predefined map[string][]netip.Addr } func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) { - var files []*File + var ( + files []*File + predefined = make(map[string][]netip.Addr) + ) if len(options.Path) == 0 { files = append(files, NewFile(DefaultPath)) } else { @@ -34,9 +39,15 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt files = append(files, NewFile(filemanager.BasePath(ctx, os.ExpandEnv(path)))) } } + if options.Predefined != nil { + for _, entry := range options.Predefined.Entries() { + predefined[mDNS.CanonicalName(entry.Key)] = entry.Value + } + } return &Transport{ TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil), files: files, + predefined: predefined, }, nil } @@ -45,8 +56,11 @@ func (t *Transport) Reset() { func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { question := message.Question[0] - domain := dns.FqdnToDomain(question.Name) + domain := mDNS.CanonicalName(question.Name) if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { + if addresses, ok := t.predefined[domain]; ok { + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil + } for _, file := range t.files { addresses := file.Lookup(domain) if len(addresses) > 0 { diff --git a/dns/transport/hosts/hosts_file.go b/dns/transport/hosts/hosts_file.go index 7ff34f692c..ec384882a8 100644 --- a/dns/transport/hosts/hosts_file.go +++ b/dns/transport/hosts/hosts_file.go @@ -34,7 +34,7 @@ func (f *File) Lookup(name string) []netip.Addr { f.access.Lock() defer f.access.Unlock() f.update() - return f.byName[name] + return f.byName[dns.CanonicalName(name)] } func (f *File) update() { diff --git a/dns/transport/hosts/hosts_test.go b/dns/transport/hosts/hosts_test.go index 944aa437a4..3ae160b789 100644 --- a/dns/transport/hosts/hosts_test.go +++ b/dns/transport/hosts/hosts_test.go @@ -11,6 +11,6 @@ import ( func TestHosts(t *testing.T) { t.Parallel() - require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost.")) - require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost.")) + require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost")) + require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost")) } diff --git a/option/dns.go b/option/dns.go index 64dbea3896..04eb442d2f 100644 --- a/option/dns.go +++ b/option/dns.go @@ -316,8 +316,8 @@ type LegacyDNSServerOptions struct { } type HostsDNSServerOptions struct { - Path badoption.Listable[string] `json:"path,omitempty"` - Predefined badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"` + Path badoption.Listable[string] `json:"path,omitempty"` + Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"` } type LocalDNSServerOptions struct { From 56409ff2696a6ba67bab7f1ed3a5ea5a3504e9ae Mon Sep 17 00:00:00 2001 From: anytls <198425817+anytls@users.noreply.github.com> Date: Mon, 10 Mar 2025 14:03:22 +0800 Subject: [PATCH 0392/1272] Update sing-anytls Co-authored-by: anytls --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 29970973ce..40fa334246 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/sagernet/sing-box go 1.23.1 require ( - github.com/anytls/sing-anytls v0.0.5 + github.com/anytls/sing-anytls v0.0.6 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 diff --git a/go.sum b/go.sum index 6847dcc7d0..5083d0132a 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/anytls/sing-anytls v0.0.5 h1:I1NIh3zKTSXThLG5UgjsOOT/x2DZJqjfBzjuP/wZlDk= -github.com/anytls/sing-anytls v0.0.5/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= +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/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.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= From 1ed21085bbf1681dd6dd1056131d37133c6ed011 Mon Sep 17 00:00:00 2001 From: Restia-Ashbell <107416976+Restia-Ashbell@users.noreply.github.com> Date: Mon, 10 Mar 2025 14:17:06 +0800 Subject: [PATCH 0393/1272] documentation: Fix typo --- docs/migration.md | 4 ++-- docs/migration.zh.md | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/migration.md b/docs/migration.md index 62e09e71a6..895f48f1b6 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -567,7 +567,7 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r "server_port": 2080, "domain_resolver": { "server": "local", - "rewrite_tll": 60, + "rewrite_ttl": 60, "client_subnet": "1.1.1.1" }, // or "domain_resolver": "local", @@ -579,7 +579,7 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r "route": { "default_domain_resolver": { "server": "local", - "rewrite_tll": 60, + "rewrite_ttl": 60, "client_subnet": "1.1.1.1" } } diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 1204466938..f82f252169 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -565,13 +565,21 @@ DNS 服务器已经重构。 "type": "socks", "server": "example.org", "server_port": 2080, - "domain_resolver": "local", + "domain_resolver": { + "server": "local", + "rewrite_ttl": 60, + "client_subnet": "1.1.1.1" + }, + // 或 "domain_resolver": "local", } ], + + // 或 + "route": { "default_domain_resolver": { "server": "local", - "rewrite_tll": 60, + "rewrite_ttl": 60, "client_subnet": "1.1.1.1" } } From b17a024f6ccbbb671fe01f1a6bb79833580f530c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 13 Mar 2025 19:47:29 +0800 Subject: [PATCH 0394/1272] Fix http3 DNS server connecting to wrong address --- dns/transport/quic/http3.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index a5181ae08b..73ae7c1339 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -23,7 +23,6 @@ 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" @@ -101,8 +100,7 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options headers: headers, transport: &http3.Transport{ Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) { - destinationAddr := M.ParseSocksaddr(addr) - conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, destinationAddr) + conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, serverAddr) if dialErr != nil { return nil, dialErr } From 23cf8c49e090b9db71420717acdebc9e40a85c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 13 Mar 2025 19:52:50 +0800 Subject: [PATCH 0395/1272] Fix DNS lookup context pollution --- dns/router.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dns/router.go b/dns/router.go index cba6cdcba4..c09f733442 100644 --- a/dns/router.go +++ b/dns/router.go @@ -390,7 +390,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ ruleIndex = -1 for { dnsCtx := adapter.OverrideContext(ctx) - transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &options) + dnsOptions := options + transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &dnsOptions) if rule != nil { switch action := rule.Action().(type) { case *R.RuleActionReject: @@ -423,10 +424,10 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ return rule.MatchAddressLimit(metadata) } } - if options.Strategy == C.DomainStrategyAsIS { - options.Strategy = r.defaultDomainStrategy + if dnsOptions.Strategy == C.DomainStrategyAsIS { + dnsOptions.Strategy = r.defaultDomainStrategy } - responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, options, responseCheck) + responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, dnsOptions, responseCheck) if responseCheck == nil || err == nil { break } From 390e30ae7b700e01adaf728f507e9fabc5606660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 15 Mar 2025 10:12:10 +0800 Subject: [PATCH 0396/1272] Make `domain_resolver` optional when only one DNS server is configured --- common/dialer/dialer.go | 7 ++++++- docs/configuration/shared/dial.md | 4 ++++ docs/configuration/shared/dial.zh.md | 4 ++++ option/outbound.go | 9 ++++++++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 0efc7e9d43..00ebbeacc0 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -104,7 +104,12 @@ func NewWithOptions(options Options) (N.Dialer, error) { } else if options.NewDialer { return nil, E.New("missing domain resolver for domain server address") } else { - deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) + transports := dnsTransport.Transports() + if len(transports) < 2 { + dnsQueryOptions.Transport = dnsTransport.Default() + } else { + deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) + } } dialer = NewResolveDialer( options.Context, diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index e852d57ba7..7f0bf25166 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -106,6 +106,10 @@ Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". `outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0. +!!! info "" + + `domain_resolver` or `route.default_domain_resolver` is optional when only one DNS server is configured. + Set domain resolver to use for resolving domain names. This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field. diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 66e1436ee8..4a7d956260 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -105,6 +105,10 @@ icon: material/new-box `outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项。 +!!! info "" + + 当只有一个 DNS 服务器已配置时,`domain_resolver` 或 `route.default_domain_resolver` 是可选的。 + 用于设置解析域名的域名解析器。 此选项的格式与 [路由 DNS 规则动作](/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。 diff --git a/option/outbound.go b/option/outbound.go index a523936f5b..09945df982 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -116,7 +116,14 @@ func (o *DomainResolveOptions) UnmarshalJSON(bytes []byte) error { o.Server = stringValue return nil } - return json.Unmarshal(bytes, (*_DomainResolveOptions)(o)) + err = json.Unmarshal(bytes, (*_DomainResolveOptions)(o)) + if err != nil { + return err + } + if o.Server == "" { + return E.New("empty domain_resolver.server") + } + return nil } func (o *DialerOptions) TakeDialerOptions() DialerOptions { From 7073f2a272cdf7d9d33e25857833bd2abcfcd77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 15 Mar 2025 15:58:12 +0800 Subject: [PATCH 0397/1272] option: Fix marshal legacy DNS options --- dns/transport/https.go | 2 +- dns/transport/quic/http3.go | 2 +- dns/transport/quic/quic.go | 2 +- dns/transport/tcp.go | 2 +- dns/transport/tls.go | 2 +- dns/transport/udp.go | 2 +- option/dns.go | 100 +++++++++++++++++++++++++----------- option/outbound.go | 4 +- 8 files changed, 79 insertions(+), 37 deletions(-) diff --git a/dns/transport/https.go b/dns/transport/https.go index 1cfb25740b..bd150d5f31 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -91,7 +91,7 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options if err != nil { return nil, err } - serverAddr := options.ServerOptions.Build() + serverAddr := options.DNSServerAddressOptions.Build() if serverAddr.Port == 0 { serverAddr.Port = 443 } diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index 73ae7c1339..e2a75b505d 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -88,7 +88,7 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options if err != nil { return nil, err } - serverAddr := options.ServerOptions.Build() + serverAddr := options.DNSServerAddressOptions.Build() if serverAddr.Port == 0 { serverAddr.Port = 443 } diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index d3844c2b55..4ae9ac161e 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -54,7 +54,7 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{"doq"}) } - serverAddr := options.ServerOptions.Build() + serverAddr := options.DNSServerAddressOptions.Build() if serverAddr.Port == 0 { serverAddr.Port = 853 } diff --git a/dns/transport/tcp.go b/dns/transport/tcp.go index 6061585e08..4abeee2fa3 100644 --- a/dns/transport/tcp.go +++ b/dns/transport/tcp.go @@ -35,7 +35,7 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o if err != nil { return nil, err } - serverAddr := options.ServerOptions.Build() + serverAddr := options.DNSServerAddressOptions.Build() if serverAddr.Port == 0 { serverAddr.Port = 53 } diff --git a/dns/transport/tls.go b/dns/transport/tls.go index 28fa885a55..ce88d42550 100644 --- a/dns/transport/tls.go +++ b/dns/transport/tls.go @@ -52,7 +52,7 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o if err != nil { return nil, err } - serverAddr := options.ServerOptions.Build() + serverAddr := options.DNSServerAddressOptions.Build() if serverAddr.Port == 0 { serverAddr.Port = 853 } diff --git a/dns/transport/udp.go b/dns/transport/udp.go index 289b1fe2e8..8c905c4c34 100644 --- a/dns/transport/udp.go +++ b/dns/transport/udp.go @@ -42,7 +42,7 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o if err != nil { return nil, err } - serverAddr := options.ServerOptions.Build() + serverAddr := options.DNSServerAddressOptions.Build() if serverAddr.Port == 0 { serverAddr.Port = 53 } diff --git a/option/dns.go b/option/dns.go index 04eb442d2f..1a42b0f43d 100644 --- a/option/dns.go +++ b/option/dns.go @@ -19,10 +19,10 @@ import ( ) type RawDNSOptions struct { - Servers []NewDNSServerOptions `json:"servers,omitempty"` - Rules []DNSRule `json:"rules,omitempty"` - Final string `json:"final,omitempty"` - ReverseMapping bool `json:"reverse_mapping,omitempty"` + Servers []DNSServerOptions `json:"servers,omitempty"` + Rules []DNSRule `json:"rules,omitempty"` + Final string `json:"final,omitempty"` + ReverseMapping bool `json:"reverse_mapping,omitempty"` DNSClientOptions } @@ -35,32 +35,47 @@ type DNSOptions struct { LegacyDNSOptions } +type contextKeyDontUpgrade struct{} + +func ContextWithDontUpgrade(ctx context.Context) context.Context { + return context.WithValue(ctx, (*contextKeyDontUpgrade)(nil), true) +} + +func dontUpgradeFromContext(ctx context.Context) bool { + return ctx.Value((*contextKeyDontUpgrade)(nil)) == true +} + func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { err := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions) if err != nil { return err } - if o.FakeIP != nil && o.FakeIP.Enabled { - deprecated.Report(ctx, deprecated.OptionLegacyDNSFakeIPOptions) - ctx = context.WithValue(ctx, (*LegacyDNSFakeIPOptions)(nil), o.FakeIP) - } + dontUpgrade := dontUpgradeFromContext(ctx) legacyOptions := o.LegacyDNSOptions - o.LegacyDNSOptions = LegacyDNSOptions{} + if !dontUpgrade { + if o.FakeIP != nil && o.FakeIP.Enabled { + deprecated.Report(ctx, deprecated.OptionLegacyDNSFakeIPOptions) + ctx = context.WithValue(ctx, (*LegacyDNSFakeIPOptions)(nil), o.FakeIP) + } + o.LegacyDNSOptions = LegacyDNSOptions{} + } err = badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions) if err != nil { return err } - rcodeMap := make(map[string]int) - o.Servers = common.Filter(o.Servers, func(it NewDNSServerOptions) bool { - if it.Type == C.DNSTypeLegacyRcode { - rcodeMap[it.Tag] = it.Options.(int) - return false - } - return true - }) - if len(rcodeMap) > 0 { - for i := 0; i < len(o.Rules); i++ { - rewriteRcode(rcodeMap, &o.Rules[i]) + if !dontUpgrade { + rcodeMap := make(map[string]int) + o.Servers = common.Filter(o.Servers, func(it DNSServerOptions) bool { + if it.Type == C.DNSTypeLegacyRcode { + rcodeMap[it.Tag] = it.Options.(int) + return false + } + return true + }) + if len(rcodeMap) > 0 { + for i := 0; i < len(o.Rules); i++ { + rewriteRcode(rcodeMap, &o.Rules[i]) + } } } return nil @@ -107,20 +122,24 @@ type DNSTransportOptionsRegistry interface { CreateOptions(transportType string) (any, bool) } -type _NewDNSServerOptions struct { +type _DNSServerOptions struct { Type string `json:"type,omitempty"` Tag string `json:"tag,omitempty"` Options any `json:"-"` } -type NewDNSServerOptions _NewDNSServerOptions +type DNSServerOptions _DNSServerOptions -func (o *NewDNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) { - return badjson.MarshallObjectsContext(ctx, (*_NewDNSServerOptions)(o), o.Options) +func (o *DNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) { + switch o.Type { + case C.DNSTypeLegacy: + o.Type = "" + } + return badjson.MarshallObjectsContext(ctx, (*_DNSServerOptions)(o), o.Options) } -func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { - err := json.UnmarshalContext(ctx, content, (*_NewDNSServerOptions)(o)) +func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_DNSServerOptions)(o)) if err != nil { return err } @@ -141,12 +160,12 @@ func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content return E.New("unknown transport type: ", o.Type) } } - err = badjson.UnmarshallExcludedContext(ctx, content, (*_NewDNSServerOptions)(o), options) + err = badjson.UnmarshallExcludedContext(ctx, content, (*_DNSServerOptions)(o), options) if err != nil { return err } o.Options = options - if o.Type == C.DNSTypeLegacy { + if o.Type == C.DNSTypeLegacy && !dontUpgradeFromContext(ctx) { err = o.Upgrade(ctx) if err != nil { return err @@ -155,7 +174,7 @@ func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content return nil } -func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { +func (o *DNSServerOptions) Upgrade(ctx context.Context) error { if o.Type != C.DNSTypeLegacy { return nil } @@ -305,6 +324,27 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { return nil } +type DNSServerAddressOptions struct { + Server string `json:"server"` + ServerPort uint16 `json:"server_port,omitempty"` +} + +func (o DNSServerAddressOptions) Build() M.Socksaddr { + return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) +} + +func (o DNSServerAddressOptions) ServerIsDomain() bool { + return M.IsDomainName(o.Server) +} + +func (o *DNSServerAddressOptions) TakeServerOptions() ServerOptions { + return ServerOptions(*o) +} + +func (o *DNSServerAddressOptions) ReplaceServerOptions(options ServerOptions) { + *o = DNSServerAddressOptions(options) +} + type LegacyDNSServerOptions struct { Address string `json:"address"` AddressResolver string `json:"address_resolver,omitempty"` @@ -329,7 +369,7 @@ type LocalDNSServerOptions struct { type RemoteDNSServerOptions struct { LocalDNSServerOptions - ServerOptions + DNSServerAddressOptions LegacyAddressResolver string `json:"-"` LegacyAddressStrategy DomainStrategy `json:"-"` LegacyAddressFallbackDelay badoption.Duration `json:"-"` diff --git a/option/outbound.go b/option/outbound.go index 09945df982..df325ffd33 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -99,7 +99,9 @@ type _DomainResolveOptions struct { type DomainResolveOptions _DomainResolveOptions func (o DomainResolveOptions) MarshalJSON() ([]byte, error) { - if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) && + if o.Server == "" { + return []byte("{}"), nil + } else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) && !o.DisableCache && o.RewriteTTL == nil && o.ClientSubnet == nil { From fa99ca275795f3c77bfa2d789b5277b5471cd0fa Mon Sep 17 00:00:00 2001 From: k9982874 Date: Sat, 15 Mar 2025 18:10:35 +0800 Subject: [PATCH 0398/1272] Add ntp protocol sniffing --- common/sniff/ntp.go | 58 ++++++++++++++++++++++++++++ common/sniff/ntp_test.go | 33 ++++++++++++++++ docs/configuration/route/sniff.md | 1 + docs/configuration/route/sniff.zh.md | 1 + route/route.go | 1 + route/rule/rule_action.go | 2 + 6 files changed, 96 insertions(+) create mode 100644 common/sniff/ntp.go create mode 100644 common/sniff/ntp_test.go diff --git a/common/sniff/ntp.go b/common/sniff/ntp.go new file mode 100644 index 0000000000..8b844c5bd6 --- /dev/null +++ b/common/sniff/ntp.go @@ -0,0 +1,58 @@ +package sniff + +import ( + "context" + "encoding/binary" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" +) + +func NTP(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { + // NTP packets must be at least 48 bytes long (standard NTP header size). + pLen := len(packet) + if pLen < 48 { + return os.ErrInvalid + } + // Check the LI (Leap Indicator) and Version Number (VN) in the first byte. + // We'll primarily focus on ensuring the version is valid for NTP. + // Many NTP versions are used, but let's check for generally accepted ones (3 & 4 for IPv4, plus potential extensions/customizations) + firstByte := packet[0] + li := (firstByte >> 6) & 0x03 // Extract LI + vn := (firstByte >> 3) & 0x07 // Extract VN + mode := firstByte & 0x07 // Extract Mode + + // Leap Indicator should be a valid value (0-3). + if li > 3 { + return os.ErrInvalid + } + + // Version Check (common NTP versions are 3 and 4) + if vn != 3 && vn != 4 { + return os.ErrInvalid + } + + // Check the Mode field for a client request (Mode 3). This validates it *is* a request. + if mode != 3 { + return os.ErrInvalid + } + + // Check Root Delay and Root Dispersion. While not strictly *required* for a request, + // we can check if they appear to be reasonable values (not excessively large). + rootDelay := binary.BigEndian.Uint32(packet[4:8]) + rootDispersion := binary.BigEndian.Uint32(packet[8:12]) + + // Check for unreasonably large root delay and dispersion. NTP RFC specifies max values of approximately 16 seconds. + // Convert to milliseconds for easy comparison. Each unit is 1/2^16 seconds. + if float64(rootDelay)/65536.0 > 16.0 { + return os.ErrInvalid + } + if float64(rootDispersion)/65536.0 > 16.0 { + return os.ErrInvalid + } + + metadata.Protocol = C.ProtocolNTP + + return nil +} diff --git a/common/sniff/ntp_test.go b/common/sniff/ntp_test.go new file mode 100644 index 0000000000..5da947853c --- /dev/null +++ b/common/sniff/ntp_test.go @@ -0,0 +1,33 @@ +package sniff_test + +import ( + "context" + "encoding/hex" + "os" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffNTP(t *testing.T) { + t.Parallel() + packet, err := hex.DecodeString("1b0006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.NTP(context.Background(), &metadata, packet) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolNTP) +} + +func TestSniffNTPFailed(t *testing.T) { + t.Parallel() + packet, err := hex.DecodeString("400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.NTP(context.Background(), &metadata, packet) + require.ErrorIs(t, err, os.ErrInvalid) +} diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index 24bc8ef602..0fb386f750 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -22,6 +22,7 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c | UDP | `dtls` | / | / | | TCP | `ssh` | / | SSH Client Name | | TCP | `rdp` | / | / | +| UDP | `ntp` | / | / | | QUIC Client | Type | |:------------------------:|:----------:| diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index cd58251733..546210c8db 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -22,6 +22,7 @@ | UDP | `dtls` | / | / | | TCP | `ssh` | / | SSH 客户端名称 | | TCP | `rdp` | / | / | +| UDP | `ntp` | / | / | | QUIC 客户端 | 类型 | |:------------------------:|:----------:| diff --git a/route/route.go b/route/route.go index df71f9efc2..85c4f88858 100644 --- a/route/route.go +++ b/route/route.go @@ -569,6 +569,7 @@ func (r *Router) actionSniff( sniff.UTP, sniff.UDPTracker, sniff.DTLSRecord, + sniff.NTP, } } for { diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 8d6391d9e3..37702d721e 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -389,6 +389,8 @@ func (r *RuleActionSniff) build() error { r.StreamSniffers = append(r.StreamSniffers, sniff.SSH) case C.ProtocolRDP: r.StreamSniffers = append(r.StreamSniffers, sniff.RDP) + case C.ProtocolNTP: + r.PacketSniffers = append(r.PacketSniffers, sniff.NTP) default: return E.New("unknown sniffer: ", name) } From bca2bd2fa1aeda7db80fb703c4d3f45c97613ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 16 Mar 2025 23:50:23 +0800 Subject: [PATCH 0399/1272] Add wildcard-sni support for shadow-tls inbound --- docs/configuration/inbound/shadowtls.md | 30 +++- docs/configuration/inbound/shadowtls.zh.md | 27 +++- go.mod | 2 +- go.sum | 4 +- option/shadowtls.go | 51 +++++++ protocol/shadowtls/inbound.go | 7 +- test/go.mod | 34 +++-- test/go.sum | 76 +++++----- test/shadowtls_test.go | 156 +++++++++++++++++++-- 9 files changed, 308 insertions(+), 79 deletions(-) diff --git a/docs/configuration/inbound/shadowtls.md b/docs/configuration/inbound/shadowtls.md index 0f6684d20e..9dbf1dd5cb 100644 --- a/docs/configuration/inbound/shadowtls.md +++ b/docs/configuration/inbound/shadowtls.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [wildcard_sni](#wildcard_sni) + ### Structure ```json @@ -29,7 +37,8 @@ ... // Dial Fields } }, - "strict_mode": false + "strict_mode": false, + "wildcard_sni": "" } ``` @@ -55,7 +64,6 @@ ShadowTLS password. Only available in the ShadowTLS protocol 2. - #### users ShadowTLS users. @@ -66,6 +74,8 @@ Only available in the ShadowTLS protocol 3. ==Required== +When `wildcard_sni` is configured to `all`, the server address is optional. + Handshake server address and [Dial Fields](/configuration/shared/dial/). #### handshake_for_server_name @@ -79,3 +89,19 @@ Only available in the ShadowTLS protocol 2/3. ShadowTLS strict mode. Only available in the ShadowTLS protocol 3. + +#### wildcard_sni + +!!! question "Since sing-box 1.12.0" + +ShadowTLS wildcard SNI mode. + +Available values are: + +* `off`: (default) Disabled. +* `authed`: Authenticated connections will have their destination overwritten to `(servername):443` +* `all`: All connections will have their destination overwritten to `(servername):443` + +Additionally, connections matching `handshake_for_server_name` are not affected. + +Only available in the ShadowTLS protocol 3. diff --git a/docs/configuration/inbound/shadowtls.zh.md b/docs/configuration/inbound/shadowtls.zh.md index 1497ac42f8..be86005131 100644 --- a/docs/configuration/inbound/shadowtls.zh.md +++ b/docs/configuration/inbound/shadowtls.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [wildcard_sni](#wildcard_sni) + ### 结构 ```json @@ -29,7 +37,8 @@ ... // 拨号字段 } }, - "strict_mode": false + "strict_mode": false, + "wildcard_sni": "" } ``` @@ -80,3 +89,19 @@ ShadowTLS 用户。 ShadowTLS 严格模式。 仅在 ShadowTLS 协议版本 3 中可用。 + +#### wildcard_sni + +!!! question "自 sing-box 1.12.0 起" + +ShadowTLS 通配符 SNI 模式。 + +可用值: + +* `off`:(默认)禁用。 +* `authed`:已认证的连接的目标将被重写为 `(servername):443`。 +* `all`:所有连接的目标将被重写为 `(servername):443`。 + +此外,匹配 `handshake_for_server_name` 的连接不受影响。 + +仅在 ShadowTLS 协议 3 中可用。 diff --git a/go.mod b/go.mod index 40fa334246..b369840c2c 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/sagernet/sing-quic v0.4.4 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 - github.com/sagernet/sing-shadowtls v0.2.0 + github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 github.com/sagernet/sing-tun v0.6.9 github.com/sagernet/sing-vmess v0.2.3 github.com/sagernet/smux v1.5.34-mod.2 diff --git a/go.sum b/go.sum index 5083d0132a..1f361bb9e7 100644 --- a/go.sum +++ b/go.sum @@ -184,8 +184,8 @@ github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVC 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.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= -github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= +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.9 h1:uP8O4Q7U9QesjWumgxd2S9fjT3c6aEPWl5RB6uBdVB8= github.com/sagernet/sing-tun v0.6.9/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44= diff --git a/option/shadowtls.go b/option/shadowtls.go index a8be77406c..7edbac4069 100644 --- a/option/shadowtls.go +++ b/option/shadowtls.go @@ -1,5 +1,11 @@ package option +import ( + "encoding/json" + + E "github.com/sagernet/sing/common/exceptions" +) + type ShadowTLSInboundOptions struct { ListenOptions Version int `json:"version,omitempty"` @@ -8,6 +14,51 @@ type ShadowTLSInboundOptions struct { Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"` HandshakeForServerName map[string]ShadowTLSHandshakeOptions `json:"handshake_for_server_name,omitempty"` StrictMode bool `json:"strict_mode,omitempty"` + WildcardSNI WildcardSNI `json:"wildcard_sni,omitempty"` +} + +type WildcardSNI int + +const ( + ShadowTLSWildcardSNIOff WildcardSNI = iota + ShadowTLSWildcardSNIAuthed + ShadowTLSWildcardSNIAll +) + +func (w WildcardSNI) MarshalJSON() ([]byte, error) { + return json.Marshal(w.String()) +} + +func (w WildcardSNI) String() string { + switch w { + case ShadowTLSWildcardSNIOff: + return "off" + case ShadowTLSWildcardSNIAuthed: + return "authed" + case ShadowTLSWildcardSNIAll: + return "all" + default: + panic("unknown wildcard SNI value") + } +} + +func (w *WildcardSNI) UnmarshalJSON(bytes []byte) error { + var valueString string + err := json.Unmarshal(bytes, &valueString) + if err != nil { + return err + } + switch valueString { + case "off", "": + *w = ShadowTLSWildcardSNIOff + case "authed": + *w = ShadowTLSWildcardSNIAuthed + case "all": + *w = ShadowTLSWildcardSNIAll + default: + return E.New("unknown wildcard SNI value: ", valueString) + } + return nil } type ShadowTLSUser struct { diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index 1db191d854..0508999bd9 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -57,7 +57,11 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo } } } - handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions, options.Handshake.ServerIsDomain()) + serverIsDomain := options.Handshake.ServerIsDomain() + if options.WildcardSNI != option.ShadowTLSWildcardSNIOff { + serverIsDomain = true + } + handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions, serverIsDomain) if err != nil { return nil, err } @@ -73,6 +77,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo }, HandshakeForServerName: handshakeForServerName, StrictMode: options.StrictMode, + WildcardSNI: shadowtls.WildcardSNI(options.WildcardSNI), Handler: (*inboundHandler)(inbound), Logger: logger, }) diff --git a/test/go.mod b/test/go.mod index 2baf3b4161..e6823537b0 100644 --- a/test/go.mod +++ b/test/go.mod @@ -13,7 +13,7 @@ require ( 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.2-0.20250210072154-8dff604468ff + 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 @@ -30,7 +30,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/anytls/sing-anytls v0.0.2 // indirect + github.com/anytls/sing-anytls v0.0.6 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/caddyserver/certmagic v0.21.7 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect @@ -46,11 +46,11 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.6.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/render v1.0.3 // indirect - github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // 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 @@ -65,13 +65,12 @@ require ( 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.2 // 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/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // 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 @@ -81,7 +80,7 @@ require ( github.com/libdns/libdns v0.2.2 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/mdlayher/genetlink v1.3.2 // indirect - github.com/mdlayher/netlink v1.7.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 @@ -109,11 +108,11 @@ require ( 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.0 // indirect - github.com/sagernet/sing-tun v0.6.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.79.0-mod.1 // 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/ws v0.0.0-20231204124109-acfe8907c854 // indirect @@ -123,10 +122,9 @@ require ( 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-20240214030740-b535050b2aa4 // indirect - github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect - github.com/tcnksm/go-httpstat v0.2.0 // indirect - github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // 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/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect @@ -138,17 +136,17 @@ require ( 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-20220726221520-4f986261bf13 // 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-20240719175910-8a7402abbf56 // 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/time v0.7.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.29.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 diff --git a/test/go.sum b/test/go.sum index d63f3057ee..ce8bfb2881 100644 --- a/test/go.sum +++ b/test/go.sum @@ -12,8 +12,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/anytls/sing-anytls v0.0.2 h1:25azSh0o/LMcIkhS4ZutgRTIGwh8O3wuOhsThVM9K9o= -github.com/anytls/sing-anytls v0.0.2/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= +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/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= @@ -53,8 +53,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 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.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +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/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= @@ -63,8 +63,8 @@ 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/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-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= +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-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= @@ -100,8 +100,8 @@ github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQN 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.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= -github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= +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= @@ -114,9 +114,6 @@ github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vy 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/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= 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= @@ -142,8 +139,8 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG 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.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +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= @@ -172,7 +169,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 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/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -205,8 +201,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W 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.2-0.20250210072154-8dff604468ff h1:5UGghwx8cI14qFa0ienrLekAYfhdKAiWvJUkY7rHmsI= -github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +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= @@ -215,16 +211,16 @@ github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegE 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.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= -github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= -github.com/sagernet/sing-tun v0.6.1 h1:4l0+gnEKcGjlWfUVTD+W0BRApqIny/lU2ZliurE+VMo= -github.com/sagernet/sing-tun v0.6.1/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +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.79.0-mod.1 h1:wIuAH7VqBYJNk0h2+bTyk4F0OlSqHvyLDCBrD3i+XNI= -github.com/sagernet/tailscale v1.79.0-mod.1/go.mod h1:RKY5WjYLj3JJ7VO/8ZCw8eAFa4+kWU6A1Ftdk84uB14= +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= @@ -251,16 +247,14 @@ github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29X 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-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +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/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/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= -github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= +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= @@ -300,8 +294,8 @@ 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/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-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= -go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= +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= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -310,10 +304,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh 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-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +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/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= @@ -339,10 +333,8 @@ 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-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/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= @@ -355,14 +347,14 @@ 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/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +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/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= diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index f64492ee61..28cd1da01f 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/tls" "net" "net/http" "net/netip" @@ -19,25 +20,43 @@ import ( func TestShadowTLS(t *testing.T) { t.Run("v1", func(t *testing.T) { - testShadowTLS(t, 1, "", false) + testShadowTLS(t, 1, "", false, option.ShadowTLSWildcardSNIOff) }) t.Run("v2", func(t *testing.T) { - testShadowTLS(t, 2, "hello", false) + testShadowTLS(t, 2, "hello", false, option.ShadowTLSWildcardSNIOff) }) t.Run("v3", func(t *testing.T) { - testShadowTLS(t, 3, "hello", false) + testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIOff) }) t.Run("v2-utls", func(t *testing.T) { - testShadowTLS(t, 2, "hello", true) + testShadowTLS(t, 2, "hello", true, option.ShadowTLSWildcardSNIOff) }) t.Run("v3-utls", func(t *testing.T) { - testShadowTLS(t, 3, "hello", true) + testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIOff) + }) + t.Run("v3-wildcard-sni-authed", func(t *testing.T) { + testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIAuthed) + }) + t.Run("v3-wildcard-sni-all", func(t *testing.T) { + testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIAll) + }) + t.Run("v3-wildcard-sni-authed-utls", func(t *testing.T) { + testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIAll) + }) + t.Run("v3-wildcard-sni-all-utls", func(t *testing.T) { + testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIAll) }) } -func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) { +func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool, wildcardSNI option.WildcardSNI) { method := shadowaead_2022.List[0] ssPassword := mkBase64(t, 16) + var clientServerName string + if wildcardSNI != option.ShadowTLSWildcardSNIOff { + clientServerName = "cloudflare.com" + } else { + clientServerName = "google.com" + } startInstance(t, option.Options{ Inbounds: []option.Inbound{ { @@ -67,9 +86,10 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) ServerPort: 443, }, }, - Version: version, - Password: password, - Users: []option.ShadowTLSUser{{Password: password}}, + Version: version, + Password: password, + Users: []option.ShadowTLSUser{{Password: password}}, + WildcardSNI: wildcardSNI, }, }, { @@ -107,7 +127,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ TLS: &option.OutboundTLSOptions{ Enabled: true, - ServerName: "google.com", + ServerName: clientServerName, UTLS: &option.OutboundUTLSOptions{ Enabled: utlsEanbled, }, @@ -157,7 +177,7 @@ func TestShadowTLSFallback(t *testing.T) { }, Handshake: option.ShadowTLSHandshakeOptions{ ServerOptions: option.ServerOptions{ - Server: "google.com", + Server: "bing.com", ServerPort: 443, }, }, @@ -177,13 +197,125 @@ func TestShadowTLSFallback(t *testing.T) { }, }, } - response, err := client.Get("https://google.com") + response, err := client.Get("https://bing.com") require.NoError(t, err) require.Equal(t, response.StatusCode, 200) response.Body.Close() client.CloseIdleConnections() } +func TestShadowTLSFallbackWildcardAll(t *testing.T) { + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeShadowTLS, + Options: &option.ShadowTLSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Version: 3, + Users: []option.ShadowTLSUser{ + {Password: "hello"}, + }, + WildcardSNI: option.ShadowTLSWildcardSNIAll, + }, + }, + }, + }) + client := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort)) + }, + }, + } + response, err := client.Get("https://www.bing.com") + require.NoError(t, err) + require.Equal(t, response.StatusCode, 200) + response.Body.Close() + client.CloseIdleConnections() +} + +func TestShadowTLSFallbackWildcardAuthedFail(t *testing.T) { + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeShadowTLS, + Options: &option.ShadowTLSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Handshake: option.ShadowTLSHandshakeOptions{ + ServerOptions: option.ServerOptions{ + Server: "bing.com", + ServerPort: 443, + }, + }, + Version: 3, + Users: []option.ShadowTLSUser{ + {Password: "hello"}, + }, + WildcardSNI: option.ShadowTLSWildcardSNIAuthed, + }, + }, + }, + }) + client := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort)) + }, + }, + } + _, err := client.Get("https://baidu.com") + expected := &tls.CertificateVerificationError{} + require.ErrorAs(t, err, &expected) + client.CloseIdleConnections() +} + +func TestShadowTLSFallbackWildcardOffFail(t *testing.T) { + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeShadowTLS, + Options: &option.ShadowTLSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Handshake: option.ShadowTLSHandshakeOptions{ + ServerOptions: option.ServerOptions{ + Server: "bing.com", + ServerPort: 443, + }, + }, + Version: 3, + Users: []option.ShadowTLSUser{ + {Password: "hello"}, + }, + WildcardSNI: option.ShadowTLSWildcardSNIOff, + }, + }, + }, + }) + client := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort)) + }, + }, + } + _, err := client.Get("https://baidu.com") + expected := &tls.CertificateVerificationError{} + require.ErrorAs(t, err, &expected) + client.CloseIdleConnections() +} + func TestShadowTLSInbound(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) From 7c923209ad19579dfb5c475a9140d0eb45aedb51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 17 Mar 2025 18:01:00 +0800 Subject: [PATCH 0400/1272] Fix unhandled DNS loop --- common/dialer/dialer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 00ebbeacc0..b93b709671 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -52,7 +52,7 @@ func NewWithOptions(options Options) (N.Dialer, error) { return nil, err } } - if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour) { + if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour || dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "") { networkManager := service.FromContext[adapter.NetworkManager](options.Context) dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context) var defaultOptions adapter.NetworkOptions From e0a6b31c03a604a877d735cdc422b7ec37f7ba19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 18 Mar 2025 09:27:53 +0800 Subject: [PATCH 0401/1272] Remove map usage in options --- option/shadowtls.go | 15 ++++++++------- protocol/shadowtls/inbound.go | 18 ++++++++++-------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/option/shadowtls.go b/option/shadowtls.go index 7edbac4069..81ef9a4370 100644 --- a/option/shadowtls.go +++ b/option/shadowtls.go @@ -4,17 +4,18 @@ import ( "encoding/json" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badjson" ) type ShadowTLSInboundOptions struct { ListenOptions - Version int `json:"version,omitempty"` - Password string `json:"password,omitempty"` - Users []ShadowTLSUser `json:"users,omitempty"` - Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"` - HandshakeForServerName map[string]ShadowTLSHandshakeOptions `json:"handshake_for_server_name,omitempty"` - StrictMode bool `json:"strict_mode,omitempty"` - WildcardSNI WildcardSNI `json:"wildcard_sni,omitempty"` + Version int `json:"version,omitempty"` + Password string `json:"password,omitempty"` + Users []ShadowTLSUser `json:"users,omitempty"` + Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"` + HandshakeForServerName *badjson.TypedMap[string, ShadowTLSHandshakeOptions] `json:"handshake_for_server_name,omitempty"` + StrictMode bool `json:"strict_mode,omitempty"` + WildcardSNI WildcardSNI `json:"wildcard_sni,omitempty"` } type WildcardSNI int diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index 0508999bd9..812df1efc6 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -46,14 +46,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo var handshakeForServerName map[string]shadowtls.HandshakeConfig if options.Version > 1 { handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) - for serverName, serverOptions := range options.HandshakeForServerName { - handshakeDialer, err := dialer.New(ctx, serverOptions.DialerOptions, serverOptions.ServerIsDomain()) - if err != nil { - return nil, err - } - handshakeForServerName[serverName] = shadowtls.HandshakeConfig{ - Server: serverOptions.ServerOptions.Build(), - Dialer: handshakeDialer, + if options.HandshakeForServerName != nil { + for _, entry := range options.HandshakeForServerName.Entries() { + handshakeDialer, err := dialer.New(ctx, entry.Value.DialerOptions, entry.Value.ServerIsDomain()) + if err != nil { + return nil, err + } + handshakeForServerName[entry.Key] = shadowtls.HandshakeConfig{ + Server: entry.Value.ServerOptions.Build(), + Dialer: handshakeDialer, + } } } } From e8499452f830bd23a28fd09515aee7e574dab8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 18 Mar 2025 19:17:11 +0800 Subject: [PATCH 0402/1272] Add wildcard name support for predefined records --- dns/router.go | 15 +-------------- route/rule/rule_action.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/dns/router.go b/dns/router.go index c09f733442..e74573108f 100644 --- a/dns/router.go +++ b/dns/router.go @@ -270,20 +270,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte return nil, tun.ErrDrop } case *R.RuleActionPredefined: - return &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Response: true, - Authoritative: true, - RecursionDesired: true, - RecursionAvailable: true, - Rcode: action.Rcode, - }, - Question: message.Question, - Answer: action.Answer, - Ns: action.Ns, - Extra: action.Extra, - }, nil + return action.Response(message), nil } } var responseCheck func(responseAddrs []netip.Addr) bool diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 37702d721e..af8c33e7ff 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -465,3 +465,32 @@ func (r *RuleActionPredefined) String() string { options = append(options, common.Map(r.Extra, dns.RR.String)...) return F.ToString("predefined(", strings.Join(options, ","), ")") } + +func (r *RuleActionPredefined) Response(request *dns.Msg) *dns.Msg { + return &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: request.Id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: r.Rcode, + }, + Question: request.Question, + Answer: rewriteRecords(r.Answer, request.Question[0]), + Ns: rewriteRecords(r.Ns, request.Question[0]), + Extra: rewriteRecords(r.Extra, request.Question[0]), + } +} + +func rewriteRecords(records []dns.RR, question dns.Question) []dns.RR { + return common.Map(records, func(it dns.RR) dns.RR { + if strings.HasPrefix(it.Header().Name, "*") { + if strings.HasSuffix(question.Name, it.Header().Name[1:]) { + it = dns.Copy(it) + it.Header().Name = question.Name + } + } + return it + }) +} From d2dc3ddf720f7946bc0773817ce81a228c49770c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 18 Mar 2025 14:21:08 +0800 Subject: [PATCH 0403/1272] Add netns support --- common/dialer/default.go | 41 ++++++++++++++---------- common/listener/listener.go | 31 ++++++++++++++++++ common/listener/listener_tcp.go | 30 ++++++++++-------- common/listener/listener_udp.go | 17 +++++++++- docs/configuration/shared/dial.md | 44 ++++++++++++++++---------- docs/configuration/shared/dial.zh.md | 38 ++++++++++++++-------- docs/configuration/shared/listen.md | 29 +++++++++++++---- docs/configuration/shared/listen.zh.md | 29 +++++++++++++---- go.mod | 2 +- option/inbound.go | 1 + option/outbound.go | 1 + option/simple.go | 8 +++-- protocol/mixed/inbound.go | 2 +- protocol/redirect/tproxy.go | 40 +++++++++++++---------- protocol/socks/inbound.go | 2 +- protocol/tor/proxy.go | 2 +- 16 files changed, 219 insertions(+), 98 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index c5111b34d1..2fa2725c41 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -10,6 +10,7 @@ import ( "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/experimental/libbox/platform" "github.com/sagernet/sing-box/option" @@ -35,6 +36,7 @@ type DefaultDialer struct { udpListener net.ListenConfig udpAddr4 string udpAddr6 string + netns string networkManager adapter.NetworkManager networkStrategy *C.NetworkStrategy defaultNetworkStrategy bool @@ -198,6 +200,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial udpListener: listener, udpAddr4: udpAddr4, udpAddr6: udpAddr6, + netns: options.NetNs, networkManager: networkManager, networkStrategy: networkStrategy, defaultNetworkStrategy: defaultNetworkStrategy, @@ -214,19 +217,21 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address return nil, E.New("domain not resolved") } if d.networkStrategy == nil { - switch N.NetworkName(network) { - case N.NetworkUDP: + return trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) { + switch N.NetworkName(network) { + case N.NetworkUDP: + if !address.IsIPv6() { + return d.udpDialer4.DialContext(ctx, network, address.String()) + } else { + return d.udpDialer6.DialContext(ctx, network, address.String()) + } + } if !address.IsIPv6() { - return trackConn(d.udpDialer4.DialContext(ctx, network, address.String())) + return DialSlowContext(&d.dialer4, ctx, network, address) } else { - return trackConn(d.udpDialer6.DialContext(ctx, network, address.String())) + return DialSlowContext(&d.dialer6, ctx, network, address) } - } - if !address.IsIPv6() { - return trackConn(DialSlowContext(&d.dialer4, ctx, network, address)) - } else { - return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) - } + })) } else { return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) } @@ -282,13 +287,15 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if d.networkStrategy == nil { - if destination.IsIPv6() { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)) - } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)) - } else { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)) - } + return 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() { + return d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4) + } else { + return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4) + } + })) } else { return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) } diff --git a/common/listener/listener.go b/common/listener/listener.go index 289f15c51b..8a4cad348b 100644 --- a/common/listener/listener.go +++ b/common/listener/listener.go @@ -4,6 +4,8 @@ import ( "context" "net" "net/netip" + "runtime" + "strings" "sync/atomic" "github.com/sagernet/sing-box/adapter" @@ -14,6 +16,8 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + + "github.com/vishvananda/netns" ) type Listener struct { @@ -135,3 +139,30 @@ func (l *Listener) UDPConn() *net.UDPConn { func (l *Listener) ListenOptions() option.ListenOptions { return l.listenOptions } + +func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (T, error) { + if nameOrPath != "" { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + currentNs, err := netns.Get() + if err != nil { + return common.DefaultValue[T](), E.Cause(err, "get current netns") + } + defer netns.Set(currentNs) + var targetNs netns.NsHandle + if strings.HasPrefix(nameOrPath, "/") { + targetNs, err = netns.GetFromPath(nameOrPath) + } else { + targetNs, err = netns.GetFromName(nameOrPath) + } + if err != nil { + return common.DefaultValue[T](), E.Cause(err, "get netns ", nameOrPath) + } + defer targetNs.Close() + err = netns.Set(targetNs) + if err != nil { + return common.DefaultValue[T](), E.Cause(err, "set netns to ", nameOrPath) + } + } + return block() +} diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index 646d40179a..c5995fad53 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -16,9 +16,12 @@ import ( ) func (l *Listener) ListenTCP() (net.Listener, error) { + //nolint:staticcheck + if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader { + return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") + } var err error bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) - var tcpListener net.Listener var listenConfig net.ListenConfig if l.listenOptions.TCPKeepAlive >= 0 { keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) @@ -37,20 +40,19 @@ func (l *Listener) ListenTCP() (net.Listener, error) { } setMultiPathTCP(&listenConfig) } - if l.listenOptions.TCPFastOpen { - var tfoConfig tfo.ListenConfig - tfoConfig.ListenConfig = listenConfig - tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } else { - tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } - if err == nil { - l.logger.Info("tcp server started at ", tcpListener.Addr()) - } - //nolint:staticcheck - if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader { - return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") + tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) { + if l.listenOptions.TCPFastOpen { + var tfoConfig tfo.ListenConfig + tfoConfig.ListenConfig = listenConfig + return tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } else { + return listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } + }) + if err != nil { + return nil, err } + l.logger.Info("tcp server started at ", tcpListener.Addr()) l.tcpListener = tcpListener return tcpListener, err } diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go index 10d6dc3868..eb5c7b5065 100644 --- a/common/listener/listener_udp.go +++ b/common/listener/listener_udp.go @@ -1,6 +1,7 @@ package listener import ( + "context" "net" "net/netip" "os" @@ -24,7 +25,9 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) { if !udpFragment { lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) } - udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) + udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { + return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) + }) if err != nil { return nil, err } @@ -34,6 +37,18 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) { return udpConn, err } +func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) { + return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) { + return dialer.DialContext(ctx, network, address) + }) +} + +func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) { + return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { + return listenConfig.ListenPacket(ctx, network, address) + }) +} + func (l *Listener) UDPAddr() M.Socksaddr { return l.udpAddr } diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 7f0bf25166..f7507531d5 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -5,7 +5,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.12.0" :material-plus: [domain_resolver](#domain_resolver) - :material-delete-clock: [domain_strategy](#domain_strategy) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-plus: [netns](#netns) !!! quote "Changes in sing-box 1.11.0" @@ -18,24 +19,25 @@ icon: material/new-box ```json { - "detour": "upstream-out", - "bind_interface": "en0", - "inet4_bind_address": "0.0.0.0", - "inet6_bind_address": "::", - "routing_mark": 1234, + "detour": "", + "bind_interface": "", + "inet4_bind_address": "", + "inet6_bind_address": "", + "routing_mark": 0, "reuse_addr": false, - "connect_timeout": "5s", + "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, + "netns": "", "domain_resolver": "", // or {} - "network_strategy": "default", + "network_strategy": "", "network_type": [], "fallback_network_type": [], - "fallback_delay": "300ms", + "fallback_delay": "", // Deprecated - "domain_strategy": "prefer_ipv6" + "domain_strategy": "" } ``` @@ -75,6 +77,15 @@ Set netfilter routing mark. Reuse listener address. +#### connect_timeout + +Connect timeout, in golang's Duration format. + +A duration string is a possibly signed sequence of +decimal numbers, each with optional fraction and a unit suffix, +such as "300ms", "-1.5h" or "2h45m". +Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + #### tcp_fast_open Enable TCP Fast Open. @@ -91,14 +102,15 @@ Enable TCP Multi Path. Enable UDP fragmentation. -#### connect_timeout +#### netns -Connect timeout, in golang's Duration format. +!!! question "Since sing-box 1.12.0" -A duration string is a possibly signed sequence of -decimal numbers, each with optional fraction and a unit suffix, -such as "300ms", "-1.5h" or "2h45m". -Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". +!!! quote "" + + Only supported on Linux. + +Set network namespace, name or path. #### domain_resolver diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 4a7d956260..5abd6ad9ec 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -5,7 +5,8 @@ icon: material/new-box !!! quote "sing-box 1.12.0 中的更改" :material-plus: [domain_resolver](#domain_resolver) - :material-delete-clock: [domain_strategy](#domain_strategy) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-plus: [netns](#netns) !!! quote "sing-box 1.11.0 中的更改" @@ -18,25 +19,26 @@ icon: material/new-box ```json { - "detour": "upstream-out", - "bind_interface": "en0", - "inet4_bind_address": "0.0.0.0", - "inet6_bind_address": "::", - "routing_mark": 1234, + "detour": "", + "bind_interface": "", + "inet4_bind_address": "", + "inet6_bind_address": "", + "routing_mark": 0, "reuse_addr": false, - "connect_timeout": "5s", + "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, + "netns": "", "domain_resolver": "", // 或 {} "network_strategy": "", "network_type": [], "fallback_network_type": [], - "fallback_delay": "300ms", + "fallback_delay": "", // 废弃的 - "domain_strategy": "prefer_ipv6" + "domain_strategy": "" } ``` @@ -76,6 +78,13 @@ icon: material/new-box 重用监听地址。 +#### connect_timeout + +连接超时,采用 golang 的 Duration 格式。 + +持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。 +有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。 + #### tcp_fast_open 启用 TCP Fast Open。 @@ -92,12 +101,15 @@ icon: material/new-box 启用 UDP 分段。 -#### connect_timeout +#### netns -连接超时,采用 golang 的 Duration 格式。 +!!! question "自 sing-box 1.12.0 起" -持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。 -有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。 +!!! quote "" + + 仅支持 Linux。 + +设置网络命名空间,名称或路径。 #### domain_resolver diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index 3e1b000f90..ab6d07ce2c 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -1,7 +1,11 @@ --- -icon: material/delete-clock +icon: material/new-box --- +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [netns](#netns) + !!! quote "Changes in sing-box 1.11.0" :material-delete-clock: [sniff](#sniff) @@ -14,17 +18,18 @@ icon: material/delete-clock ```json { - "listen": "::", - "listen_port": 5353, + "listen": "", + "listen_port": 0, "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, - "udp_timeout": "5m", - "detour": "another-in", + "udp_timeout": "", + "netns": "", + "detour": "", "sniff": false, "sniff_override_destination": false, - "sniff_timeout": "300ms", - "domain_strategy": "prefer_ipv6", + "sniff_timeout": "", + "domain_strategy": "", "udp_disable_domain_unmapping": false } ``` @@ -72,6 +77,16 @@ UDP NAT expiration time. `5m` will be used by default. +#### netns + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set network namespace, name or path. + #### detour If set, connections will be forwarded to the specified inbound. diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index 4f8ca9d6a2..bc67ac98c0 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -1,7 +1,11 @@ --- -icon: material/delete-clock +icon: material/new-box --- +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [netns](#netns) + !!! quote "sing-box 1.11.0 中的更改" :material-delete-clock: [sniff](#sniff) @@ -14,17 +18,18 @@ icon: material/delete-clock ```json { - "listen": "::", - "listen_port": 5353, + "listen": "", + "listen_port": 0, "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, - "udp_timeout": "5m", - "detour": "another-in", + "udp_timeout": "", + "netns": "", + "detour": "", "sniff": false, "sniff_override_destination": false, - "sniff_timeout": "300ms", - "domain_strategy": "prefer_ipv6", + "sniff_timeout": "", + "domain_strategy": "", "udp_disable_domain_unmapping": false } ``` @@ -73,6 +78,16 @@ UDP NAT 过期时间。 默认使用 `5m`。 +#### netns + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置网络命名空间,名称或路径。 + #### detour 如果设置,连接将被转发到指定的入站。 diff --git a/go.mod b/go.mod index b369840c2c..b8f6d36d18 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 + github.com/vishvananda/netns v0.0.4 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.33.0 @@ -121,7 +122,6 @@ 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/x448/float16 v0.8.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/option/inbound.go b/option/inbound.go index 1cf16ff6ec..b704c7e328 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -68,6 +68,7 @@ type ListenOptions struct { UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + NetNs string `json:"netns,omitempty"` // Deprecated: removed ProxyProtocol bool `json:"proxy_protocol,omitempty"` diff --git a/option/outbound.go b/option/outbound.go index df325ffd33..112572bd8f 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -77,6 +77,7 @@ type DialerOptions struct { TCPMultiPath bool `json:"tcp_multi_path,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` + NetNs string `json:"netns,omitempty"` DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` diff --git a/option/simple.go b/option/simple.go index 5fda30efe4..f244ba18b8 100644 --- a/option/simple.go +++ b/option/simple.go @@ -7,13 +7,15 @@ import ( type SocksInboundOptions struct { ListenOptions - Users []auth.User `json:"users,omitempty"` + Users []auth.User `json:"users,omitempty"` + DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` } type HTTPMixedInboundOptions struct { ListenOptions - Users []auth.User `json:"users,omitempty"` - SetSystemProxy bool `json:"set_system_proxy,omitempty"` + Users []auth.User `json:"users,omitempty"` + DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` + SetSystemProxy bool `json:"set_system_proxy,omitempty"` InboundTLSOptionsContainer } diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go index b675a5362e..fe84aa01a4 100644 --- a/protocol/mixed/inbound.go +++ b/protocol/mixed/inbound.go @@ -85,7 +85,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), metadata.Source, onClose) + return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, 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/redirect/tproxy.go b/protocol/redirect/tproxy.go index 7860e173d6..1b3e8dae12 100644 --- a/protocol/redirect/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -121,40 +121,48 @@ func (t *TProxy) NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) t.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.SocksaddrFromNetIP(destination), nil) } -type tproxyPacketWriter struct { - ctx context.Context - source netip.AddrPort - destination M.Socksaddr - conn *net.UDPConn -} - func (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { ctx := log.ContextWithNewID(t.ctx) - writer := &tproxyPacketWriter{ctx: ctx, source: source.AddrPort(), destination: destination} + writer := &tproxyPacketWriter{ + ctx: ctx, + listener: t.listener, + source: source.AddrPort(), + destination: destination, + } return true, ctx, writer, func(it error) { common.Close(common.PtrOrNil(writer.conn)) } } +type tproxyPacketWriter struct { + ctx context.Context + listener *listener.Listener + source netip.AddrPort + destination M.Socksaddr + conn *net.UDPConn +} + func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { defer buffer.Release() - conn := w.conn - if w.destination == destination && conn != nil { - _, err := conn.WriteToUDPAddrPort(buffer.Bytes(), w.source) - if err != nil { - w.conn = nil + if w.listener.ListenOptions().NetNs == "" { + conn := w.conn + if w.destination == destination && conn != nil { + _, err := conn.WriteToUDPAddrPort(buffer.Bytes(), w.source) + if err != nil { + w.conn = nil + } + return err } - return err } var listener net.ListenConfig listener.Control = control.Append(listener.Control, control.ReuseAddr()) listener.Control = control.Append(listener.Control, redir.TProxyWriteBack()) - packetConn, err := listener.ListenPacket(w.ctx, "udp", destination.String()) + packetConn, err := w.listener.ListenPacket(listener, w.ctx, "udp", destination.String()) if err != nil { return err } udpConn := packetConn.(*net.UDPConn) - if w.destination == destination { + if w.listener.ListenOptions().NetNs == "" && w.destination == destination { w.conn = udpConn } else { defer udpConn.Close() diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go index 820c7bbb5d..6b82815245 100644 --- a/protocol/socks/inbound.go +++ b/protocol/socks/inbound.go @@ -62,7 +62,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), metadata.Source, onClose) + err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, 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 feab7971c0..6b7db7c30c 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, M.SocksaddrFromNet(conn.RemoteAddr()), nil) + return socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, l, nil, 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 fb622ccbdf44b13b3009f8223c58698091b0bcaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 20 Mar 2025 20:48:23 +0800 Subject: [PATCH 0404/1272] Explicitly reject detour to empty direct outbounds --- adapter/dns.go | 2 +- common/dialer/detour.go | 36 ++++++++++++++++++++++++------ common/dialer/dialer.go | 3 ++- dns/router.go | 2 +- dns/transport/dhcp/dhcp.go | 10 ++------- dns/transport/hosts/hosts.go | 7 +++++- dns/transport/https.go | 11 ++++++++- dns/transport/local/local.go | 7 +++++- dns/transport/quic/http3.go | 8 +++++-- dns/transport/quic/quic.go | 7 +++++- dns/transport/tcp.go | 11 ++++++++- dns/transport/tls.go | 11 ++++++++- dns/transport/udp.go | 11 ++++++++- dns/transport_dialer.go | 16 ++++++++------ dns/transport_manager.go | 2 +- experimental/libbox/dns.go | 7 +++++- option/dns.go | 43 ++++++++++++++---------------------- option/rule.go | 3 +-- option/rule_dns.go | 1 - protocol/direct/outbound.go | 9 ++++++++ 20 files changed, 142 insertions(+), 65 deletions(-) diff --git a/adapter/dns.go b/adapter/dns.go index e0f381b82b..942f3566df 100644 --- a/adapter/dns.go +++ b/adapter/dns.go @@ -45,10 +45,10 @@ type RDRCStore interface { } type DNSTransport interface { + Lifecycle Type() string Tag() string Dependencies() []string - Reset() Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error) } diff --git a/common/dialer/detour.go b/common/dialer/detour.go index e4a46049f0..5c0b552ba8 100644 --- a/common/dialer/detour.go +++ b/common/dialer/detour.go @@ -6,26 +6,39 @@ import ( "sync" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) +type DirectDialer interface { + IsEmpty() bool +} + type DetourDialer struct { outboundManager adapter.OutboundManager detour string + legacyDNSDialer bool dialer N.Dialer initOnce sync.Once initErr error } -func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer { - return &DetourDialer{outboundManager: outboundManager, detour: detour} +func NewDetour(outboundManager adapter.OutboundManager, detour string, legacyDNSDialer bool) N.Dialer { + return &DetourDialer{ + outboundManager: outboundManager, + detour: detour, + legacyDNSDialer: legacyDNSDialer, + } } -func (d *DetourDialer) Start() error { - _, err := d.Dialer() - return err +func InitializeDetour(dialer N.Dialer) error { + detourDialer, isDetour := common.Cast[*DetourDialer](dialer) + if !isDetour { + return nil + } + return common.Error(detourDialer.Dialer()) } func (d *DetourDialer) Dialer() (N.Dialer, error) { @@ -34,11 +47,20 @@ func (d *DetourDialer) Dialer() (N.Dialer, error) { } func (d *DetourDialer) init() { - var loaded bool - d.dialer, loaded = d.outboundManager.Outbound(d.detour) + dialer, loaded := d.outboundManager.Outbound(d.detour) if !loaded { d.initErr = E.New("outbound detour not found: ", d.detour) + return + } + if !d.legacyDNSDialer { + if directDialer, isDirect := dialer.(DirectDialer); isDirect { + if directDialer.IsEmpty() { + d.initErr = E.New("detour to an empty direct outbound makes no sense") + return + } + } } + d.dialer = dialer } func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index b93b709671..88e167404d 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -23,6 +23,7 @@ type Options struct { DirectResolver bool ResolverOnDetour bool NewDialer bool + LegacyDNSDialer bool } // TODO: merge with NewWithOptions @@ -45,7 +46,7 @@ func NewWithOptions(options Options) (N.Dialer, error) { if outboundManager == nil { return nil, E.New("missing outbound manager") } - dialer = NewDetour(outboundManager, dialOptions.Detour) + dialer = NewDetour(outboundManager, dialOptions.Detour, options.LegacyDNSDialer) } else { dialer, err = NewDefault(options.Context, dialOptions) if err != nil { diff --git a/dns/router.go b/dns/router.go index e74573108f..18939c37f3 100644 --- a/dns/router.go +++ b/dns/router.go @@ -456,6 +456,6 @@ func (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) { func (r *Router) ResetNetwork() { r.ClearCache() for _, transport := range r.transport.Transports() { - transport.Reset() + transport.Close() } } diff --git a/dns/transport/dhcp/dhcp.go b/dns/transport/dhcp/dhcp.go index c75d736981..92dd1f8bc6 100644 --- a/dns/transport/dhcp/dhcp.go +++ b/dns/transport/dhcp/dhcp.go @@ -81,7 +81,7 @@ func (t *Transport) Start(stage adapter.StartStage) error { func (t *Transport) Close() error { for _, transport := range t.transports { - transport.Reset() + transport.Close() } if t.interfaceCallback != nil { t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) @@ -89,12 +89,6 @@ func (t *Transport) Close() error { return nil } -func (t *Transport) Reset() { - for _, transport := range t.transports { - transport.Reset() - } -} - func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { err := t.fetchServers() if err != nil { @@ -252,7 +246,7 @@ func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.So transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr)) } for _, transport := range t.transports { - transport.Reset() + transport.Close() } t.transports = transports return nil diff --git a/dns/transport/hosts/hosts.go b/dns/transport/hosts/hosts.go index 0a1dd395bd..a5eecb4027 100644 --- a/dns/transport/hosts/hosts.go +++ b/dns/transport/hosts/hosts.go @@ -51,7 +51,12 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt }, nil } -func (t *Transport) Reset() { +func (t *Transport) Start(stage adapter.StartStage) error { + return nil +} + +func (t *Transport) Close() error { + return nil } func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { diff --git a/dns/transport/https.go b/dns/transport/https.go index bd150d5f31..1750fd264f 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -10,6 +10,7 @@ import ( "strconv" "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" @@ -149,9 +150,17 @@ func NewHTTPSRaw( } } -func (t *HTTPSTransport) Reset() { +func (t *HTTPSTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) +} + +func (t *HTTPSTransport) Close() error { t.transport.CloseIdleConnections() t.transport = t.transport.Clone() + return nil } func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index 4e05ff0250..f50405a53d 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -40,7 +40,12 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt }, nil } -func (t *Transport) Reset() { +func (t *Transport) Start(stage adapter.StartStage) error { + return nil +} + +func (t *Transport) Close() error { + return nil } func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index e2a75b505d..0d871741fa 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -111,8 +111,12 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options }, nil } -func (t *HTTP3Transport) Reset() { - t.transport.Close() +func (t *HTTP3Transport) Start(stage adapter.StartStage) error { + return nil +} + +func (t *HTTP3Transport) Close() error { + return t.transport.Close() } func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index 4ae9ac161e..fc5101ee4f 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -68,13 +68,18 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options }, nil } -func (t *Transport) Reset() { +func (t *Transport) Start(stage adapter.StartStage) error { + return nil +} + +func (t *Transport) Close() error { t.access.Lock() defer t.access.Unlock() connection := t.connection if connection != nil { connection.CloseWithError(0, "") } + return nil } func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { diff --git a/dns/transport/tcp.go b/dns/transport/tcp.go index 4abeee2fa3..a814c030ea 100644 --- a/dns/transport/tcp.go +++ b/dns/transport/tcp.go @@ -6,6 +6,7 @@ import ( "io" "github.com/sagernet/sing-box/adapter" + "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" @@ -46,7 +47,15 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o }, nil } -func (t *TCPTransport) Reset() { +func (t *TCPTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) +} + +func (t *TCPTransport) Close() error { + return nil } func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { diff --git a/dns/transport/tls.go b/dns/transport/tls.go index ce88d42550..a99bf2f704 100644 --- a/dns/transport/tls.go +++ b/dns/transport/tls.go @@ -5,6 +5,7 @@ import ( "sync" "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" @@ -65,13 +66,21 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o }, nil } -func (t *TLSTransport) Reset() { +func (t *TLSTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) +} + +func (t *TLSTransport) Close() error { 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) { diff --git a/dns/transport/udp.go b/dns/transport/udp.go index 8c905c4c34..8d9f0515da 100644 --- a/dns/transport/udp.go +++ b/dns/transport/udp.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/sagernet/sing-box/adapter" + "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" @@ -64,11 +65,19 @@ func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer } } -func (t *UDPTransport) Reset() { +func (t *UDPTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + 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 } func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { diff --git a/dns/transport_dialer.go b/dns/transport_dialer.go index 0b15c7eaab..b3ee8082ab 100644 --- a/dns/transport_dialer.go +++ b/dns/transport_dialer.go @@ -20,9 +20,10 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) ( return dialer.NewDefaultOutbound(ctx), nil } else { return dialer.NewWithOptions(dialer.Options{ - Context: ctx, - Options: options.DialerOptions, - DirectResolver: true, + Context: ctx, + Options: options.DialerOptions, + DirectResolver: true, + LegacyDNSDialer: options.Legacy, }) } } @@ -43,10 +44,11 @@ func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) return transportDialer, nil } else { return dialer.NewWithOptions(dialer.Options{ - Context: ctx, - Options: options.DialerOptions, - RemoteIsDomain: options.ServerIsDomain(), - DirectResolver: true, + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain(), + DirectResolver: true, + LegacyDNSDialer: options.Legacy, }) } } diff --git a/dns/transport_manager.go b/dns/transport_manager.go index 8666a1b9f7..dff886df06 100644 --- a/dns/transport_manager.go +++ b/dns/transport_manager.go @@ -225,7 +225,7 @@ func (m *TransportManager) Remove(tag string) error { } } if started { - transport.Reset() + transport.Close() } return nil } diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index 7e143442ad..d5c97b7ee5 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -38,7 +38,12 @@ func newPlatformTransport(iif LocalDNSTransport, tag string, options option.Loca } } -func (p *platformTransport) Reset() { +func (p *platformTransport) Start(stage adapter.StartStage) error { + return nil +} + +func (p *platformTransport) Close() error { + return nil } func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { diff --git a/option/dns.go b/option/dns.go index 1a42b0f43d..f303b8945b 100644 --- a/option/dns.go +++ b/option/dns.go @@ -191,34 +191,24 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error { serverType = C.DNSTypeUDP } } - var remoteOptions RemoteDNSServerOptions - if options.Detour == "" { - remoteOptions = RemoteDNSServerOptions{ - LocalDNSServerOptions: LocalDNSServerOptions{ - LegacyStrategy: options.Strategy, - LegacyDefaultDialer: options.Detour == "", - LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), - }, - LegacyAddressResolver: options.AddressResolver, - LegacyAddressStrategy: options.AddressStrategy, - LegacyAddressFallbackDelay: options.AddressFallbackDelay, - } - } else { - remoteOptions = RemoteDNSServerOptions{ - LocalDNSServerOptions: LocalDNSServerOptions{ - DialerOptions: DialerOptions{ - Detour: options.Detour, - DomainResolver: &DomainResolveOptions{ - Server: options.AddressResolver, - Strategy: options.AddressStrategy, - }, - FallbackDelay: options.AddressFallbackDelay, + remoteOptions := RemoteDNSServerOptions{ + LocalDNSServerOptions: LocalDNSServerOptions{ + DialerOptions: DialerOptions{ + Detour: options.Detour, + DomainResolver: &DomainResolveOptions{ + Server: options.AddressResolver, + Strategy: options.AddressStrategy, }, - LegacyStrategy: options.Strategy, - LegacyDefaultDialer: options.Detour == "", - LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), + FallbackDelay: options.AddressFallbackDelay, }, - } + Legacy: true, + LegacyStrategy: options.Strategy, + LegacyDefaultDialer: options.Detour == "", + LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), + }, + LegacyAddressResolver: options.AddressResolver, + LegacyAddressStrategy: options.AddressStrategy, + LegacyAddressFallbackDelay: options.AddressFallbackDelay, } switch serverType { case C.DNSTypeLocal: @@ -362,6 +352,7 @@ type HostsDNSServerOptions struct { type LocalDNSServerOptions struct { DialerOptions + Legacy bool `json:"-"` LegacyStrategy DomainStrategy `json:"-"` LegacyDefaultDialer bool `json:"-"` LegacyClientSubnet netip.Prefix `json:"-"` diff --git a/option/rule.go b/option/rule.go index b769dab831..41bcc12604 100644 --- a/option/rule.go +++ b/option/rule.go @@ -125,10 +125,9 @@ func (r *DefaultRule) UnmarshalJSON(data []byte) error { return badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) } -func (r *DefaultRule) IsValid() bool { +func (r DefaultRule) IsValid() bool { var defaultValue DefaultRule defaultValue.Invert = r.Invert - defaultValue.Action = r.Action return !reflect.DeepEqual(r, defaultValue) } diff --git a/option/rule_dns.go b/option/rule_dns.go index 9d6fb1381a..87b1501745 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -132,7 +132,6 @@ func (r *DefaultDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) func (r DefaultDNSRule) IsValid() bool { var defaultValue DefaultDNSRule defaultValue.Invert = r.Invert - defaultValue.DNSRuleAction = r.DNSRuleAction return !reflect.DeepEqual(r, defaultValue) } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 7ad756f29f..9cd1490bec 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -4,6 +4,7 @@ import ( "context" "net" "net/netip" + "reflect" "time" "github.com/sagernet/sing-box/adapter" @@ -27,6 +28,7 @@ func RegisterOutbound(registry *outbound.Registry) { var ( _ N.ParallelDialer = (*Outbound)(nil) _ dialer.ParallelNetworkDialer = (*Outbound)(nil) + _ dialer.DirectDialer = (*Outbound)(nil) ) type Outbound struct { @@ -37,6 +39,7 @@ type Outbound struct { fallbackDelay time.Duration overrideOption int overrideDestination M.Socksaddr + isEmpty bool // loopBack *loopBackDetector } @@ -56,6 +59,8 @@ 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, // loopBack: newLoopBackDetector(router), } //nolint:staticcheck @@ -242,6 +247,10 @@ func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M. return conn, newDestination, nil } +func (h *Outbound) IsEmpty() bool { + return h.isEmpty +} + /*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { return E.New("reject loopback connection to ", metadata.Destination) From 42eb3841a114615ead694c0c3419672bc08d982c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 24 Mar 2025 20:18:39 +0800 Subject: [PATCH 0405/1272] Update gVisor to 20250319.0 --- Makefile | 9 +-------- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 3d4f78fb56..caf5ee91af 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,6 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_acme -TAGS_GO123 = with_tailscale -TAGS ?= $(TAGS_GO120),$(TAGS_GO123) +TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_acme,with_tailscale TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server GOHOSTOS = $(shell go env GOHOSTOS) @@ -20,11 +18,6 @@ build: export GOTOOLCHAIN=local && \ go build $(MAIN_PARAMS) $(MAIN) -ci_build_go120: - export GOTOOLCHAIN=local && \ - go build $(PARAMS) $(MAIN) && \ - go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN) - ci_build: export GOTOOLCHAIN=local && \ go build $(PARAMS) $(MAIN) && \ diff --git a/go.mod b/go.mod index b8f6d36d18..639beefc21 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.6 - github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff + github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b diff --git a/go.sum b/go.sum index 1f361bb9e7..34fe42260b 100644 --- a/go.sum +++ b/go.sum @@ -163,8 +163,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.6 h1:JkR1ToKOrdoiwULte4pYS5HYdPBzl2N+JNuuwVuLs0k= github.com/sagernet/gomobile v0.1.6/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= -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-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38= +github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/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= From f9999a76fedc6bf299a0f42c5a5f582f673e16fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 24 Mar 2025 20:23:55 +0800 Subject: [PATCH 0406/1272] Fail when default DNS server not found --- dns/transport_manager.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dns/transport_manager.go b/dns/transport_manager.go index dff886df06..f41c9f9eb9 100644 --- a/dns/transport_manager.go +++ b/dns/transport_manager.go @@ -59,6 +59,9 @@ func (m *TransportManager) Start(stage adapter.StartStage) error { transports := m.transports m.access.Unlock() if stage == adapter.StartStateStart { + if m.defaultTag != "" && m.defaultTransport == nil { + return E.New("default DNS server not found: ", m.defaultTag) + } return m.startTransports(m.transports) } else { for _, outbound := range transports { From af56b1a950518d5889c08d321243ffebfbf61680 Mon Sep 17 00:00:00 2001 From: Rambling2076 Date: Wed, 26 Mar 2025 05:22:43 +0000 Subject: [PATCH 0407/1272] Fix missing `with_tailscale` in Dockerfile Signed-off-by: Rambling2076 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c9b32b8e10..b2700b59f2 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_reality_server,with_acme,with_clash_api" \ + "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,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=" \ ./cmd/sing-box From d115e36ed8d9cff439a2f8ff99e6ca438c72948b Mon Sep 17 00:00:00 2001 From: anytls <198425817+anytls@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:06:37 +0800 Subject: [PATCH 0408/1272] Update anytls Co-authored-by: anytls --- docs/configuration/inbound/anytls.md | 6 +++--- docs/configuration/inbound/anytls.zh.md | 6 +++--- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/configuration/inbound/anytls.md b/docs/configuration/inbound/anytls.md index 7eca943472..55790810f6 100644 --- a/docs/configuration/inbound/anytls.md +++ b/docs/configuration/inbound/anytls.md @@ -44,10 +44,10 @@ Default padding scheme: ``` stop=8 -0=34-120 +0=30-30 1=100-400 -2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500 -3=500-1000 +2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000 +3=9-9,500-1000 4=500-1000 5=500-1000 6=500-1000 diff --git a/docs/configuration/inbound/anytls.zh.md b/docs/configuration/inbound/anytls.zh.md index 5c119b724e..099da77724 100644 --- a/docs/configuration/inbound/anytls.zh.md +++ b/docs/configuration/inbound/anytls.zh.md @@ -44,10 +44,10 @@ AnyTLS 填充方案行数组。 ``` stop=8 -0=34-120 +0=30-30 1=100-400 -2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500 -3=500-1000 +2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000 +3=9-9,500-1000 4=500-1000 5=500-1000 6=500-1000 diff --git a/go.mod b/go.mod index 639beefc21..8e82b32722 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/sagernet/sing-box go 1.23.1 require ( - github.com/anytls/sing-anytls v0.0.6 + github.com/anytls/sing-anytls v0.0.7 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 diff --git a/go.sum b/go.sum index 34fe42260b..40eec598c0 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/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.7 h1:0Q5dHNB2sqkFAWZCyK2vjQ/ckI5Iz3V/Frf3k7mBrGc= +github.com/anytls/sing-anytls v0.0.7/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.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= From 63d3e9f6e53e597b4147b80d8a7ab4a00ab0d694 Mon Sep 17 00:00:00 2001 From: dyhkwong <50692134+dyhkwong@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:19:57 +0800 Subject: [PATCH 0409/1272] Fix DNS over QUIC stream close --- dns/transport/quic/quic.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index fc5101ee4f..18f8b9febe 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -140,12 +140,12 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.C if err != nil { return nil, err } - defer stream.Close() - defer stream.CancelRead(0) err = transport.WriteMessage(stream, 0, message) if err != nil { + stream.Close() return nil, err } + stream.Close() return transport.ReadMessage(stream) } From 4e11a3585a0a190ed816584d6375cfea37816af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 28 Mar 2025 23:20:45 +0800 Subject: [PATCH 0410/1272] Fix Tailscale dialer --- common/dialer/dialer.go | 4 ++-- protocol/tailscale/endpoint.go | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 88e167404d..bfd1378485 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -102,12 +102,12 @@ func NewWithOptions(options Options) (N.Dialer, error) { } dnsQueryOptions.Transport = transport resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) - } else if options.NewDialer { - return nil, E.New("missing domain resolver for domain server address") } else { transports := dnsTransport.Transports() if len(transports) < 2 { dnsQueryOptions.Transport = dnsTransport.Default() + } else if options.NewDialer { + return nil, E.New("missing domain resolver for domain server address") } else { deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) } diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index f24f931f49..26aa6ebd05 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -2,8 +2,10 @@ package tailscale import ( "context" + "crypto/tls" "fmt" "net" + "net/http" "net/netip" "net/url" "os" @@ -147,6 +149,17 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions()) }, DNS: &dnsConfigurtor{}, + HTTPClient: &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(address)) + }, + TLSClientConfig: &tls.Config{ + RootCAs: adapter.RootPoolFromContext(ctx), + }, + }, + }, } return &Endpoint{ Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), @@ -446,6 +459,10 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } +func (t *Endpoint) Server() *tsnet.Server { + return t.server +} + func addressFromAddr(destination netip.Addr) tcpip.Address { if destination.Is6() { return tcpip.AddrFrom16(destination.As16()) From ac7bc587cbb5f110fa58759f8311326f08e51be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 1 Apr 2025 21:32:49 +0800 Subject: [PATCH 0411/1272] Allow direct outbounds without `domain_resolver` --- common/dialer/dialer.go | 3 ++- protocol/direct/outbound.go | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index bfd1378485..7bf05d1bd3 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -24,6 +24,7 @@ type Options struct { ResolverOnDetour bool NewDialer bool LegacyDNSDialer bool + DirectOutbound bool } // TODO: merge with NewWithOptions @@ -108,7 +109,7 @@ func NewWithOptions(options Options) (N.Dialer, error) { dnsQueryOptions.Transport = dnsTransport.Default() } else if options.NewDialer { return nil, E.New("missing domain resolver for domain server address") - } else { + } else if !options.DirectOutbound { deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) } } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 9cd1490bec..84838bc0a6 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -48,7 +48,12 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if options.Detour != "" { return nil, E.New("`detour` is not supported in direct context") } - outboundDialer, err := dialer.New(ctx, options.DialerOptions, true) + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: true, + DirectOutbound: true, + }) if err != nil { return nil, err } From a5e47f4e0ff5be60ce336ffc2a19bee06b05598f Mon Sep 17 00:00:00 2001 From: ReleTor <191429954+ReleTor@users.noreply.github.com> Date: Sun, 6 Apr 2025 20:33:13 +0800 Subject: [PATCH 0412/1272] Fix fetch ECH configs --- common/tls/ech.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/tls/ech.go b/common/tls/ech.go index 880ca27ca3..ddb9b5dda1 100644 --- a/common/tls/ech.go +++ b/common/tls/ech.go @@ -123,6 +123,7 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) if response.Rcode != mDNS.RcodeSuccess { return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list") } + match: for _, rr := range response.Answer { switch resource := rr.(type) { case *mDNS.HTTPS: @@ -133,11 +134,14 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) return nil, E.Cause(err, "decode ECH config") } s.config.EncryptedClientHelloConfigList = echConfigList + break match } } } } - return nil, E.New("no ECH config found in DNS records") + if len(s.config.EncryptedClientHelloConfigList) == 0 { + return nil, E.New("no ECH config found in DNS records") + } } tlsConn, err := s.Client(conn) if err != nil { From 40a6260f6e978223962bb1823c086cb047dddede Mon Sep 17 00:00:00 2001 From: iikira <2571583272@qq.com> Date: Sun, 6 Apr 2025 22:03:24 +0800 Subject: [PATCH 0413/1272] Fix UDP DNS server crash Signed-off-by: iikira --- dns/transport/udp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns/transport/udp.go b/dns/transport/udp.go index 8d9f0515da..ec17c71b7d 100644 --- a/dns/transport/udp.go +++ b/dns/transport/udp.go @@ -212,8 +212,8 @@ type dnsConnection struct { func (c *dnsConnection) Close(err error) { c.closeOnce.Do(func() { - close(c.done) c.err = err + close(c.done) }) c.Conn.Close() } From cdea3f63d48194d176da0ab3619234b05dbded9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 8 Apr 2025 16:15:15 +0800 Subject: [PATCH 0414/1272] release: Skip override version for iOS --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 322ed88d42..33ccaee8cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -549,10 +549,13 @@ jobs: MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version) echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV" + - name: Update version + if: matrix.if && matrix.name != 'iOS' + run: |- + go run -v ./cmd/internal/update_apple_version --ci - name: Build if: matrix.if run: |- - go run -v ./cmd/internal/update_apple_version --ci cd clients/apple xcodebuild archive \ -scheme "${{ matrix.scheme }}" \ From 5b363c347f9650f5eef51495206efec829c7f951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 8 Apr 2025 21:51:35 +0800 Subject: [PATCH 0415/1272] Fix DNS dialer --- common/dialer/default.go | 34 ++++++++++++++++++++-------------- route/network.go | 12 +++++++++++- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index 2fa2725c41..c4f6c16b39 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -71,18 +71,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial listener.Control = control.Append(listener.Control, bindFunc) } if options.RoutingMark > 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark))) - listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark))) - } - if networkManager != nil { - autoRedirectOutputMark := networkManager.AutoRedirectOutputMark() - if autoRedirectOutputMark > 0 { - if options.RoutingMark > 0 { - return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set") - } - dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark)) - listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark)) - } + dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false)) + listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false)) } disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil if disableDefaultBind || options.TCPFastOpen { @@ -127,8 +117,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial } } if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark)) - listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark)) + dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) + listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) } } if options.ReuseAddr { @@ -210,6 +200,22 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial }, nil } +func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefault bool) control.Func { + if networkManager == nil { + return control.RoutingMark(mark) + } + return func(network, address string, conn syscall.RawConn) error { + if networkManager.AutoRedirectOutputMark() != 0 { + if isDefault { + return E.New("`route.default_mark` is conflict with `tun.auto_redirect`") + } else { + return E.New("`routing_mark` is conflict with `tun.auto_redirect`") + } + } + return control.RoutingMark(mark)(network, address, conn) + } +} + func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) { if !address.IsValid() { return nil, E.New("invalid address") diff --git a/route/network.go b/route/network.go index a494ce0902..6c2e995f75 100644 --- a/route/network.go +++ b/route/network.go @@ -303,7 +303,7 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { if r.interfaceMonitor == nil { return nil } - return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { + bindFunc := control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { remoteAddr := M.ParseSocksaddr(address).Addr if remoteAddr.IsValid() { iif, err := r.interfaceFinder.ByAddr(remoteAddr) @@ -317,6 +317,16 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { } return defaultInterface.Name, defaultInterface.Index, nil }) + return func(network, address string, conn syscall.RawConn) error { + err := bindFunc(network, address, conn) + if err != nil { + return err + } + if r.autoRedirectOutputMark > 0 { + return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn) + } + return nil + } } } From 9b0db6ab15f2ca93d840751246ec32067a59279f Mon Sep 17 00:00:00 2001 From: anytls <198425817+anytls@users.noreply.github.com> Date: Wed, 9 Apr 2025 11:36:39 +0800 Subject: [PATCH 0416/1272] Update anytls Co-authored-by: anytls --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8e82b32722..e4f2b2c4a7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/sagernet/sing-box go 1.23.1 require ( - github.com/anytls/sing-anytls v0.0.7 + github.com/anytls/sing-anytls v0.0.8 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 diff --git a/go.sum b/go.sum index 40eec598c0..b80fbdd9a8 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/anytls/sing-anytls v0.0.7 h1:0Q5dHNB2sqkFAWZCyK2vjQ/ckI5Iz3V/Frf3k7mBrGc= -github.com/anytls/sing-anytls v0.0.7/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.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= From aadb44ebd601affc9107b54ad3ad8693c4a1fcfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 12 Apr 2025 21:26:22 +0800 Subject: [PATCH 0417/1272] Improve local DNS server --- dns/transport/local/local.go | 3 +- dns/transport/local/resolv.go | 17 ++++--- dns/transport/local/resolv_darwin_cgo.go | 3 +- dns/transport/local/resolv_unix.go | 3 +- dns/transport/local/resolv_windows.go | 57 +++++++++++++++--------- experimental/libbox/config.go | 11 ++++- experimental/libbox/monitor.go | 19 ++++++-- experimental/libbox/service.go | 1 + go.mod | 2 +- go.sum | 6 +-- 10 files changed, 82 insertions(+), 40 deletions(-) diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index f50405a53d..df473b3c47 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -35,6 +35,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt } return &Transport{ TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), + ctx: ctx, hosts: hosts.NewFile(hosts.DefaultPath), dialer: transportDialer, }, nil @@ -57,7 +58,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil } } - systemConfig := getSystemDNSConfig() + 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 { diff --git a/dns/transport/local/resolv.go b/dns/transport/local/resolv.go index 5484d0ecd2..754d453927 100644 --- a/dns/transport/local/resolv.go +++ b/dns/transport/local/resolv.go @@ -1,6 +1,7 @@ package local import ( + "context" "os" "runtime" "strings" @@ -23,19 +24,21 @@ type resolverConfig struct { var resolvConf resolverConfig -func getSystemDNSConfig() *dnsConfig { - resolvConf.tryUpdate("/etc/resolv.conf") +func getSystemDNSConfig(ctx context.Context) *dnsConfig { + resolvConf.tryUpdate(ctx, "/etc/resolv.conf") return resolvConf.dnsConfig.Load() } -func (conf *resolverConfig) init() { - conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf")) +func (conf *resolverConfig) init(ctx context.Context) { + conf.dnsConfig.Store(dnsReadConfig(ctx, "/etc/resolv.conf")) conf.lastChecked = time.Now() conf.ch = make(chan struct{}, 1) } -func (conf *resolverConfig) tryUpdate(name string) { - conf.initOnce.Do(conf.init) +func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) { + conf.initOnce.Do(func() { + conf.init(ctx) + }) if conf.dnsConfig.Load().noReload { return @@ -59,7 +62,7 @@ func (conf *resolverConfig) tryUpdate(name string) { return } } - dnsConf := dnsReadConfig(name) + dnsConf := dnsReadConfig(ctx, name) conf.dnsConfig.Store(dnsConf) } diff --git a/dns/transport/local/resolv_darwin_cgo.go b/dns/transport/local/resolv_darwin_cgo.go index dfcb8a8abf..f9d82a36af 100644 --- a/dns/transport/local/resolv_darwin_cgo.go +++ b/dns/transport/local/resolv_darwin_cgo.go @@ -11,6 +11,7 @@ package local import "C" import ( + "context" "time" E "github.com/sagernet/sing/common/exceptions" @@ -18,7 +19,7 @@ import ( "github.com/miekg/dns" ) -func dnsReadConfig(_ string) *dnsConfig { +func dnsReadConfig(_ context.Context, _ string) *dnsConfig { if C.res_init() != 0 { return &dnsConfig{ servers: defaultNS, diff --git a/dns/transport/local/resolv_unix.go b/dns/transport/local/resolv_unix.go index c395314539..f77f35536c 100644 --- a/dns/transport/local/resolv_unix.go +++ b/dns/transport/local/resolv_unix.go @@ -4,6 +4,7 @@ package local import ( "bufio" + "context" "net" "net/netip" "os" @@ -13,7 +14,7 @@ import ( "github.com/miekg/dns" ) -func dnsReadConfig(name string) *dnsConfig { +func dnsReadConfig(_ context.Context, name string) *dnsConfig { conf := &dnsConfig{ ndots: 1, timeout: 5 * time.Second, diff --git a/dns/transport/local/resolv_windows.go b/dns/transport/local/resolv_windows.go index f6b810901f..76f758c62f 100644 --- a/dns/transport/local/resolv_windows.go +++ b/dns/transport/local/resolv_windows.go @@ -1,6 +1,7 @@ package local import ( + "context" "net" "net/netip" "os" @@ -8,10 +9,13 @@ import ( "time" "unsafe" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/service" + "golang.org/x/sys/windows" ) -func dnsReadConfig(_ string) *dnsConfig { +func dnsReadConfig(ctx context.Context, _ string) *dnsConfig { conf := &dnsConfig{ ndots: 1, timeout: 5 * time.Second, @@ -22,35 +26,35 @@ func dnsReadConfig(_ string) *dnsConfig { conf.servers = defaultNS } }() - aas, err := adapterAddresses() + addresses, err := adapterAddresses() if err != nil { return nil } - - for _, aa := range aas { - // Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs. - if aa.OperStatus != windows.IfOperStatusUp { + var dnsAddresses []struct { + ifName string + netip.Addr + } + for _, address := range addresses { + if address.OperStatus != windows.IfOperStatusUp { continue } - - // Only take interfaces which have at least one gateway - if aa.FirstGatewayAddress == nil { + if address.IfType == windows.IF_TYPE_TUNNEL { continue } - - for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next { - sa, err := dns.Address.Sockaddr.Sockaddr() + if address.FirstGatewayAddress == nil { + continue + } + for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next { + rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr() if err != nil { continue } - var ip netip.Addr - switch sa := sa.(type) { + var dnsServerAddr netip.Addr + switch sockaddr := rawSockaddr.(type) { case *syscall.SockaddrInet4: - ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]}) + dnsServerAddr = netip.AddrFrom4(sockaddr.Addr) case *syscall.SockaddrInet6: - var addr16 [16]byte - copy(addr16[:], sa.Addr[:]) - if addr16[0] == 0xfe && addr16[1] == 0xc0 { + if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 { // fec0/10 IPv6 addresses are site local anycast DNS // addresses Microsoft sets by default if no other // IPv6 DNS address is set. Site local anycast is @@ -58,13 +62,26 @@ func dnsReadConfig(_ string) *dnsConfig { // https://datatracker.ietf.org/doc/html/rfc3879 continue } - ip = netip.AddrFrom16(addr16) + dnsServerAddr = netip.AddrFrom16(sockaddr.Addr) default: // Unexpected type. continue } - conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53")) + dnsAddresses = append(dnsAddresses, struct { + ifName string + netip.Addr + }{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr}) + } + } + var myInterface string + if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil { + myInterface = networkManager.InterfaceMonitor().MyInterface() + } + for _, address := range dnsAddresses { + if address.ifName == myInterface { + continue } + conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53")) } return conf } diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index d3149dc27f..8fc1b66c7a 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -116,6 +116,10 @@ func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network str return nil, os.ErrInvalid } +func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error { + return nil +} + type interfaceMonitorStub struct{} func (s *interfaceMonitorStub) Start() error { @@ -145,8 +149,11 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { } -func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error { - return nil +func (s *interfaceMonitorStub) RegisterMyInterface(interfaceName string) { +} + +func (s *interfaceMonitorStub) MyInterface() string { + return "" } func FormatConfig(configContent string) (*StringBox, error) { diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 05973ec6ab..00f63abd80 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -15,9 +15,10 @@ var ( type platformDefaultInterfaceMonitor struct { *platformInterfaceWrapper - element *list.Element[tun.NetworkUpdateCallback] - callbacks list.List[tun.DefaultInterfaceUpdateCallback] - logger logger.Logger + logger logger.Logger + element *list.Element[tun.NetworkUpdateCallback] + callbacks list.List[tun.DefaultInterfaceUpdateCallback] + myInterface string } func (m *platformDefaultInterfaceMonitor) Start() error { @@ -102,3 +103,15 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s callback(newInterface, 0) } } + +func (m *platformDefaultInterfaceMonitor) RegisterMyInterface(interfaceName string) { + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() + m.myInterface = interfaceName +} + +func (m *platformDefaultInterfaceMonitor) MyInterface() string { + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() + return m.myInterface +} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 48d614ff0d..7f80e6fef6 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -164,6 +164,7 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions if err != nil { return nil, E.Cause(err, "query tun name") } + options.InterfaceMonitor.RegisterMyInterface(options.Name) dupFd, err := dup(int(tunFd)) if err != nil { return nil, E.Cause(err, "dup tun file descriptor") diff --git a/go.mod b/go.mod index e4f2b2c4a7..089261bb1b 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,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.20250316154757-6f9e732e5056 - github.com/sagernet/sing-tun v0.6.9 + github.com/sagernet/sing-tun v0.6.10-0.20250620051458-5e343c4b66b2 github.com/sagernet/sing-vmess v0.2.3 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-mod.5 diff --git a/go.sum b/go.sum index b80fbdd9a8..5d6625507e 100644 --- a/go.sum +++ b/go.sum @@ -186,14 +186,12 @@ 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.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.9 h1:uP8O4Q7U9QesjWumgxd2S9fjT3c6aEPWl5RB6uBdVB8= -github.com/sagernet/sing-tun v0.6.9/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-tun v0.6.10-0.20250620051458-5e343c4b66b2 h1:ykbqGFHDNVvp0jhgLime/XBAtQpcOcFpT8Rs5Hcc5n4= +github.com/sagernet/sing-tun v0.6.10-0.20250620051458-5e343c4b66b2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44= github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= 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-mod.4.0.20250512093633-e1bc1888c814 h1:B6ejgOuM1BrX4TzWvm1h/LQAOZW1T1jP4PSZe8b/49o= -github.com/sagernet/tailscale v1.80.3-mod.4.0.20250512093633-e1bc1888c814/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A= github.com/sagernet/tailscale v1.80.3-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= From 6a211f6ed6f587a06838e9cf8b27dd0cdddd05e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 17 Apr 2025 15:49:36 +0800 Subject: [PATCH 0418/1272] Fix missing handling of legacy `domain_strategy` options --- common/dialer/dialer.go | 40 +++++--- docs/configuration/shared/dial.md | 2 +- docs/configuration/shared/dial.zh.md | 4 + docs/migration.md | 55 +++++++++- docs/migration.zh.md | 146 ++++++++++++++++++--------- experimental/deprecated/constants.go | 14 +++ 6 files changed, 195 insertions(+), 66 deletions(-) diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 7bf05d1bd3..7bf8ff02c8 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -83,6 +83,7 @@ func NewWithOptions(options Options) (N.Dialer, error) { dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) { //nolint:staticcheck strategy = C.DomainStrategy(dialOptions.DomainStrategy) + deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions) } server = dialOptions.DomainResolver.Server dnsQueryOptions = adapter.DNSQueryOptions{ @@ -95,22 +96,31 @@ func NewWithOptions(options Options) (N.Dialer, error) { resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) } else if options.DirectResolver { return nil, E.New("missing domain resolver for domain server address") - } else if defaultOptions.DomainResolver != "" { - dnsQueryOptions = defaultOptions.DomainResolveOptions - transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver) - if !loaded { - return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver) - } - dnsQueryOptions.Transport = transport - resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) } else { - transports := dnsTransport.Transports() - if len(transports) < 2 { - dnsQueryOptions.Transport = dnsTransport.Default() - } else if options.NewDialer { - return nil, E.New("missing domain resolver for domain server address") - } else if !options.DirectOutbound { - deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) + if defaultOptions.DomainResolver != "" { + dnsQueryOptions = defaultOptions.DomainResolveOptions + transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver) + if !loaded { + return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver) + } + dnsQueryOptions.Transport = transport + resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) + } else { + transports := dnsTransport.Transports() + if len(transports) < 2 { + dnsQueryOptions.Transport = dnsTransport.Default() + } else if options.NewDialer { + return nil, E.New("missing domain resolver for domain server address") + } else if !options.DirectOutbound { + deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) + } + } + if + //nolint:staticcheck + dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) { + //nolint:staticcheck + dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy) + deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions) } } dialer = NewResolveDialer( diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index f7507531d5..97fbfce327 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -206,7 +206,7 @@ Only take effect when `domain_strategy` or `network_strategy` is set. !!! failure "Deprecated in sing-box 1.12.0" - `domain_strategy` is merged to [domain_resolver](#domain_resolver) in sing-box 1.12.0. + `domain_strategy` is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver). Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 5abd6ad9ec..292cdc7c5c 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -194,6 +194,10 @@ icon: material/new-box #### domain_strategy +!!! failure "已在 sing-box 1.12.0 废弃" + + `domain_strategy` 已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver)。 + 可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 如果设置,域名将在请求发出之前解析为 IP。 diff --git a/docs/migration.md b/docs/migration.md index 895f48f1b6..ca50a70a18 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -516,13 +516,13 @@ DNS servers are refactored for better performance and scalability. The legacy outbound DNS rules are deprecated and can be replaced by new domain resolver options. !!! info "References" - + [DNS rule](/configuration/dns/rule/#outbound) / [Dial Fields](/configuration/shared/dial/#domain_resolver) / [Route](/configuration/route/#domain_resolver) === ":material-card-remove: Deprecated" - + ```json { "dns": { @@ -586,6 +586,57 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r } ``` +### Migrate outbound domain strategy option to domain resolver + +!!! info "References" + + [Dial Fields](/configuration/shared/dial/#domain_strategy) + +The `domain_strategy` option in Dial Fields has been deprecated and can be replaced with the new domain resolver option. + +Note that due to the use of Dial Fields by some of the new DNS servers introduced in sing-box 1.12, +some people mistakenly believe that `domain_strategy` is the same feature as in the legacy DNS servers. + +=== ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_strategy": "prefer_ipv4", + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_resolver": { + "server": "local", + "strategy": "prefer_ipv4" + } + } + ] + } + ``` + ## 1.11.0 ### Migrate legacy special outbounds to rule actions diff --git a/docs/migration.zh.md b/docs/migration.zh.md index f82f252169..a2779e2360 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -16,7 +16,7 @@ DNS 服务器已经重构。 === "Local" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -28,9 +28,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -46,7 +46,7 @@ DNS 服务器已经重构。 === "TCP" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -58,9 +58,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -77,7 +77,7 @@ DNS 服务器已经重构。 === "UDP" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -89,9 +89,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -108,7 +108,7 @@ DNS 服务器已经重构。 === "TLS" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -120,9 +120,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -139,7 +139,7 @@ DNS 服务器已经重构。 === "HTTPS" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -151,9 +151,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -170,7 +170,7 @@ DNS 服务器已经重构。 === "QUIC" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -182,9 +182,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -201,7 +201,7 @@ DNS 服务器已经重构。 === "HTTP3" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -213,9 +213,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -232,7 +232,7 @@ DNS 服务器已经重构。 === "DHCP" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -247,9 +247,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -269,7 +269,7 @@ DNS 服务器已经重构。 === "FakeIP" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -299,9 +299,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -333,7 +333,7 @@ DNS 服务器已经重构。 === "RCode" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -345,9 +345,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -368,7 +368,7 @@ DNS 服务器已经重构。 === "带有域名地址的服务器" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -385,9 +385,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -410,7 +410,7 @@ DNS 服务器已经重构。 === "带有域策略的服务器" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -434,9 +434,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -466,7 +466,7 @@ DNS 服务器已经重构。 === "带有客户端子网的服务器" === ":material-card-remove: 弃用的" - + ```json { "dns": { @@ -483,9 +483,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "dns": { @@ -586,6 +586,56 @@ DNS 服务器已经重构。 } ``` +### 迁移出站域名策略选项到域名解析器 + +拨号字段中的 `domain_strategy` 选项已被弃用,可以用新的域名解析器选项替代。 + +请注意,由于 sing-box 1.12 中引入的一些新 DNS 服务器使用了拨号字段,一些人错误地认为 `domain_strategy` 与旧 DNS 服务器中的功能相同。 + +!!! info "参考" + + [拨号字段](/configuration/shared/dial/#domain_strategy) + +=== ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_strategy": "prefer_ipv4", + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_resolver": { + "server": "local", + "strategy": "prefer_ipv4" + } + } + ] + } + ``` + ## 1.11.0 ### 迁移旧的特殊出站到规则动作 @@ -601,7 +651,7 @@ DNS 服务器已经重构。 === "Block" === ":material-card-remove: 弃用的" - + ```json { "outbounds": [ @@ -614,7 +664,7 @@ DNS 服务器已经重构。 "rules": [ { ..., - + "outbound": "block" } ] @@ -623,14 +673,14 @@ DNS 服务器已经重构。 ``` === ":material-card-multiple: 新的" - + ```json { "route": { "rules": [ { ..., - + "action": "reject" } ] @@ -641,13 +691,13 @@ DNS 服务器已经重构。 === "DNS" === ":material-card-remove: 弃用的" - + ```json { "inbound": [ { ..., - + "sniff": true } ], @@ -667,9 +717,9 @@ DNS 服务器已经重构。 } } ``` - + === ":material-card-multiple: 新的" - + ```json { "route": { @@ -1133,4 +1183,4 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold } } } - ``` \ No newline at end of file + ``` diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 16216716a9..5dfdfd4755 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -161,6 +161,7 @@ var OptionLegacyDNSFakeIPOptions = Note{ Description: "legacy DNS fakeip options", DeprecatedVersion: "1.12.0", ScheduledVersion: "1.14.0", + EnvName: "LEGACY_DNS_FAKEIP_OPTIONS", MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats", } @@ -169,6 +170,7 @@ var OptionOutboundDNSRuleItem = Note{ Description: "outbound DNS rule item", DeprecatedVersion: "1.12.0", ScheduledVersion: "1.14.0", + EnvName: "OUTBOUND_DNS_RULE_ITEM", MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", } @@ -177,6 +179,7 @@ var OptionMissingDomainResolver = Note{ Description: "missing `route.default_domain_resolver` or `domain_resolver` in dial fields", DeprecatedVersion: "1.12.0", ScheduledVersion: "1.14.0", + EnvName: "MISSING_DOMAIN_RESOLVER", MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", } @@ -185,9 +188,19 @@ var OptionLegacyECHOptions = Note{ 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", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", + EnvName: "LEGACY_DOMAIN_STRATEGY_OPTIONS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-domain-strategy-options", +} + var Options = []Note{ OptionBadMatchSource, OptionGEOIP, @@ -204,4 +217,5 @@ var Options = []Note{ OptionOutboundDNSRuleItem, OptionMissingDomainResolver, OptionLegacyECHOptions, + OptionLegacyDomainStrategyOptions, } From 54a0004de61351ac153cf1c5b91284231c530e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 17 Apr 2025 17:54:19 +0800 Subject: [PATCH 0419/1272] documentation: Try to make the play review happy --- docs/clients/privacy.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/clients/privacy.md b/docs/clients/privacy.md index b1ecd2aa34..61df710259 100644 --- a/docs/clients/privacy.md +++ b/docs/clients/privacy.md @@ -9,6 +9,10 @@ and the data generated by the software is always on your device. ## Android +The broad package (App) visibility (QUERY_ALL_PACKAGES) permission +is used to provide per-application proxy features for VPN, +sing-box will not collect your app list. + If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules, sing-box uses the location permission in the background to get information about the connected Wi-Fi network to make them work. From 245273e6c1a1ad8c3b1b1f95d93145a49400a8a4 Mon Sep 17 00:00:00 2001 From: caelansar <31852257+caelansar@users.noreply.github.com> Date: Sat, 19 Apr 2025 01:20:17 -0500 Subject: [PATCH 0420/1272] Fix callback deletion in UDP transport --- dns/transport/udp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns/transport/udp.go b/dns/transport/udp.go index ec17c71b7d..e2ad9db7f0 100644 --- a/dns/transport/udp.go +++ b/dns/transport/udp.go @@ -117,7 +117,7 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M conn.access.Unlock() defer func() { conn.access.Lock() - delete(conn.callbacks, messageId) + delete(conn.callbacks, exMessage.Id) conn.access.Unlock() }() rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes()) From 0b4b5e6f0f51671fa2ef8db1cb6237dc2eb07b08 Mon Sep 17 00:00:00 2001 From: reletor <191429954+reletor@users.noreply.github.com> Date: Sat, 19 Apr 2025 15:39:39 +0800 Subject: [PATCH 0421/1272] documentation: Minor fixes --- docs/configuration/dns/server/index.md | 27 ++++++++++++----------- docs/configuration/dns/server/index.zh.md | 27 ++++++++++++----------- docs/configuration/route/sniff.zh.md | 4 ++-- docs/migration.md | 8 ++++--- docs/migration.zh.md | 8 ++++--- 5 files changed, 40 insertions(+), 34 deletions(-) diff --git a/docs/configuration/dns/server/index.md b/docs/configuration/dns/server/index.md index 0dae05c311..706eb54f69 100644 --- a/docs/configuration/dns/server/index.md +++ b/docs/configuration/dns/server/index.md @@ -27,19 +27,20 @@ icon: material/alert-decagram The type of the DNS server. -| Type | Format | -|-----------------|-----------------------------| -| empty (default) | [Legacy](./legacy/) | -| `tcp` | [TCP](./tcp/) | -| `udp` | [UDP](./udp/) | -| `tls` | [TLS](./tls/) | -| `https` | [HTTPS](./https/) | -| `quic` | [QUIC](./quic/) | -| `h3` | [HTTP/3](./http3/) | -| `predefined` | [Predefined](./predefined/) | -| `dhcp` | [DHCP](./dhcp/) | -| `fakeip` | [Fake IP](./fakeip/) | -| `tailscale` | [Tailscale](./tailscale/) | +| Type | Format | +|-----------------|---------------------------| +| 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/) | #### tag diff --git a/docs/configuration/dns/server/index.zh.md b/docs/configuration/dns/server/index.zh.md index 2ad9950210..61977c67f8 100644 --- a/docs/configuration/dns/server/index.zh.md +++ b/docs/configuration/dns/server/index.zh.md @@ -27,19 +27,20 @@ icon: material/alert-decagram DNS 服务器的类型。 -| 类型 | 格式 | -|-----------------|-----------------------------| -| empty (default) | [Legacy](./legacy/) | -| `tcp` | [TCP](./tcp/) | -| `udp` | [UDP](./udp/) | -| `tls` | [TLS](./tls/) | -| `https` | [HTTPS](./https/) | -| `quic` | [QUIC](./quic/) | -| `h3` | [HTTP/3](./http3/) | -| `predefined` | [Predefined](./predefined/) | -| `dhcp` | [DHCP](./dhcp/) | -| `fakeip` | [Fake IP](./fakeip/) | -| `tailscale` | [Tailscale](./tailscale/) | +| 类型 | 格式 | +|-----------------|---------------------------| +| 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/) | #### tag diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index 546210c8db..8cb9488f9f 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -26,7 +26,7 @@ | QUIC 客户端 | 类型 | |:------------------------:|:----------:| -| Chromium/Cronet | `chrimium` | +| Chromium/Cronet | `chromium` | | Safari/Apple Network API | `safari` | | Firefox / uquic firefox | `firefox` | -| quic-go / uquic chrome | `quic-go` | \ No newline at end of file +| quic-go / uquic chrome | `quic-go` | diff --git a/docs/migration.md b/docs/migration.md index ca50a70a18..50a0655fbc 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -292,7 +292,7 @@ DNS servers are refactored for better performance and scalability. } ], "fakeip": { - "enable": true, + "enabled": true, "inet4_range": "198.18.0.0/15", "inet6_range": "fc00::/18" } @@ -556,7 +556,8 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r "dns": { "servers": [ { - "type": "local" + "type": "local", + "tag": "local" } ] }, @@ -619,7 +620,8 @@ some people mistakenly believe that `domain_strategy` is the same feature as in "dns": { "servers": [ { - "type": "local" + "type": "local", + "tag": "local" } ] }, diff --git a/docs/migration.zh.md b/docs/migration.zh.md index a2779e2360..255b70699b 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -292,7 +292,7 @@ DNS 服务器已经重构。 } ], "fakeip": { - "enable": true, + "enabled": true, "inet4_range": "198.18.0.0/15", "inet6_range": "fc00::/18" } @@ -556,7 +556,8 @@ DNS 服务器已经重构。 "dns": { "servers": [ { - "type": "local" + "type": "local", + "tag": "local" } ] }, @@ -618,7 +619,8 @@ DNS 服务器已经重构。 "dns": { "servers": [ { - "type": "local" + "type": "local", + "tag": "local" } ] }, From 1f3097da007c83921fb09edce18dd6703ff6d7ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 22 Apr 2025 15:08:30 +0800 Subject: [PATCH 0422/1272] Fix fetch ECH configs --- common/tls/ech.go | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/common/tls/ech.go b/common/tls/ech.go index ddb9b5dda1..de911126e5 100644 --- a/common/tls/ech.go +++ b/common/tls/ech.go @@ -10,6 +10,8 @@ import ( "net" "os" "strings" + "sync" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/dns" @@ -46,7 +48,10 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions tlsConfig.EncryptedClientHelloConfigList = block.Bytes return &STDClientConfig{tlsConfig}, nil } else { - return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil + return &STDECHClientConfig{ + STDClientConfig: STDClientConfig{tlsConfig}, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + }, nil } } @@ -99,11 +104,28 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error { type STDECHClientConfig struct { STDClientConfig - dnsRouter adapter.DNSRouter + access sync.Mutex + dnsRouter adapter.DNSRouter + lastTTL time.Duration + lastUpdate time.Time } func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { - if len(s.config.EncryptedClientHelloConfigList) == 0 { + tlsConn, err := s.fetchAndHandshake(ctx, conn) + if err != nil { + return nil, err + } + err = tlsConn.HandshakeContext(ctx) + if err != nil { + return nil, err + } + return tlsConn, nil +} + +func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { + s.access.Lock() + defer s.access.Unlock() + if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL { message := &mDNS.Msg{ MsgHdr: mDNS.MsgHdr{ RecursionDesired: true, @@ -133,6 +155,8 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) if err != nil { return nil, E.Cause(err, "decode ECH config") } + s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second + s.lastUpdate = time.Now() s.config.EncryptedClientHelloConfigList = echConfigList break match } @@ -143,19 +167,11 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) return nil, E.New("no ECH config found in DNS records") } } - tlsConn, err := s.Client(conn) - if err != nil { - return nil, err - } - err = tlsConn.HandshakeContext(ctx) - if err != nil { - return nil, err - } - return tlsConn, nil + return s.Client(conn) } func (s *STDECHClientConfig) Clone() Config { - return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter} + return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate} } func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) { From 349db7baecb0bd2f63b20014b51c1d40787085e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 16:27:56 +0800 Subject: [PATCH 0423/1272] Fix DNS lookup --- dns/client.go | 2 +- dns/router.go | 11 +++++++---- dns/transport/local/local.go | 6 ++++-- route/conn.go | 33 ++++++++++++++++++++++++++++++--- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/dns/client.go b/dns/client.go index f456f878b5..90c950b474 100644 --- a/dns/client.go +++ b/dns/client.go @@ -483,7 +483,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp } func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) { - if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError { + if response.Rcode != dns.RcodeSuccess { return nil, RcodeError(response.Rcode) } addresses := make([]netip.Addr, 0, len(response.Answer)) diff --git a/dns/router.go b/dns/router.go index 18939c37f3..2dab57cc2d 100644 --- a/dns/router.go +++ b/dns/router.go @@ -330,6 +330,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ err error ) printResult := func() { + if err == nil && len(responseAddrs) == 0 { + err = E.New("empty result") + } if err != nil { if errors.Is(err, ErrResponseRejectedCached) { r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)") @@ -338,15 +341,15 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ } else { r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) } - } else if len(responseAddrs) == 0 { - r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") - err = RcodeNameError + } + if err != nil { + err = E.Cause(err, "lookup ", domain) } } responseAddrs, cached = r.client.LookupCache(domain, options.Strategy) if cached { if len(responseAddrs) == 0 { - return nil, RcodeNameError + return nil, E.New("lookup ", domain, ": empty result (cached)") } return responseAddrs, nil } diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index df473b3c47..15be54c3d0 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -3,6 +3,7 @@ package local import ( "context" "math/rand" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" @@ -90,8 +91,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi startRacer := func(ctx context.Context, fqdn string) { response, err := t.tryOneName(ctx, systemConfig, fqdn, message) if err == nil { - addresses, _ := dns.MessageToAddresses(response) - if len(addresses) == 0 { + var addresses []netip.Addr + addresses, err = dns.MessageToAddresses(response) + if err == nil && len(addresses) == 0 { err = E.New(fqdn, ": empty result") } } diff --git a/route/conn.go b/route/conn.go index 582562eb8d..f46283ad9b 100644 --- a/route/conn.go +++ b/route/conn.go @@ -7,6 +7,7 @@ import ( "net" "net/netip" "os" + "strings" "sync" "sync/atomic" "time" @@ -66,7 +67,17 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co remoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) } if err != nil { - err = E.Cause(err, "open outbound connection") + var remoteString string + if len(metadata.DestinationAddresses) > 0 { + remoteString = "[" + strings.Join(common.Map(metadata.DestinationAddresses, netip.Addr.String), ",") + "]" + } else { + remoteString = metadata.Destination.String() + } + var dialerString string + if outbound, isOutbound := this.(adapter.Outbound); isOutbound { + dialerString = " using outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + } + err = E.Cause(err, "open connection to ", remoteString, dialerString) N.CloseOnHandshakeFailure(conn, onClose, err) m.logger.ErrorContext(ctx, err) return @@ -133,8 +144,19 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial remoteConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) } if err != nil { + var remoteString string + if len(metadata.DestinationAddresses) > 0 { + remoteString = "[" + strings.Join(common.Map(metadata.DestinationAddresses, netip.Addr.String), ",") + "]" + } else { + remoteString = metadata.Destination.String() + } + var dialerString string + if outbound, isOutbound := this.(adapter.Outbound); isOutbound { + dialerString = " using outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + } + err = E.Cause(err, "open packet connection to ", remoteString, dialerString) N.CloseOnHandshakeFailure(conn, onClose, err) - m.logger.ErrorContext(ctx, "open outbound packet connection: ", err) + m.logger.ErrorContext(ctx, err) return } remotePacketConn = bufio.NewUnbindPacketConn(remoteConn) @@ -149,8 +171,13 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial remotePacketConn, err = this.ListenPacket(ctx, metadata.Destination) } if err != nil { + var dialerString string + if outbound, isOutbound := this.(adapter.Outbound); isOutbound { + dialerString = " using outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + } + err = E.Cause(err, "listen packet connection using ", dialerString) N.CloseOnHandshakeFailure(conn, onClose, err) - m.logger.ErrorContext(ctx, "listen outbound packet connection: ", err) + m.logger.ErrorContext(ctx, err) return } } From 6c377f16e761384d461533744dbe7913ab697f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 25 Apr 2025 11:22:55 +0800 Subject: [PATCH 0424/1272] clash-api: Add more meta api --- experimental/clashapi/api_meta.go | 10 +++++++ experimental/clashapi/api_meta_upgrade.go | 36 +++++++++++++++++++++++ experimental/clashapi/server.go | 3 ++ 3 files changed, 49 insertions(+) create mode 100644 experimental/clashapi/api_meta_upgrade.go diff --git a/experimental/clashapi/api_meta.go b/experimental/clashapi/api_meta.go index 29add8ae94..77dad7979b 100644 --- a/experimental/clashapi/api_meta.go +++ b/experimental/clashapi/api_meta.go @@ -4,6 +4,7 @@ import ( "bytes" "net" "net/http" + "runtime/debug" "time" "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" @@ -12,14 +13,23 @@ import ( "github.com/sagernet/ws/wsutil" "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/render" ) // API created by Clash.Meta func (s *Server) setupMetaAPI(r chi.Router) { + if s.logDebug { + r := chi.NewRouter() + r.Put("/gc", func(w http.ResponseWriter, r *http.Request) { + debug.FreeOSMemory() + }) + r.Mount("/", middleware.Profiler()) + } r.Get("/memory", memory(s.trafficManager)) r.Mount("/group", groupRouter(s)) + r.Mount("/upgrade", upgradeRouter(s)) } type Memory struct { diff --git a/experimental/clashapi/api_meta_upgrade.go b/experimental/clashapi/api_meta_upgrade.go new file mode 100644 index 0000000000..df70088edf --- /dev/null +++ b/experimental/clashapi/api_meta_upgrade.go @@ -0,0 +1,36 @@ +package clashapi + +import ( + "net/http" + + E "github.com/sagernet/sing/common/exceptions" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +func upgradeRouter(server *Server) http.Handler { + r := chi.NewRouter() + r.Post("/ui", updateExternalUI(server)) + return r +} + +func updateExternalUI(server *Server) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if server.externalUI == "" { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, newError("external UI not enabled")) + return + } + server.logger.Info("upgrading external UI") + err := server.downloadExternalUI() + if err != nil { + server.logger.Error(E.Cause(err, "upgrade external ui")) + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError(err.Error())) + return + } + server.logger.Info("updated external UI") + render.JSON(w, r, render.M{"status": "ok"}) + } +} diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index e6d8c4cfad..3a2d48274f 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -49,6 +49,8 @@ type Server struct { httpServer *http.Server trafficManager *trafficontrol.Manager urlTestHistory adapter.URLTestHistoryStorage + logDebug bool + mode string modeList []string modeUpdateHook chan<- struct{} @@ -74,6 +76,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op Handler: chiRouter, }, trafficManager: trafficManager, + logDebug: logFactory.Level() >= log.LevelDebug, modeList: options.ModeList, externalController: options.ExternalController != "", externalUIDownloadURL: options.ExternalUIDownloadURL, From d4fd43cf6ff5d79e5b708e3deeb430e3487079ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 26 Apr 2025 17:25:36 +0800 Subject: [PATCH 0425/1272] Fix wireguard `listen_port` --- option/outbound.go | 1 - protocol/wireguard/endpoint.go | 4 ++-- protocol/wireguard/outbound.go | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/option/outbound.go b/option/outbound.go index 112572bd8f..9b04de93cf 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -83,7 +83,6 @@ type DialerOptions struct { NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"` FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` // Deprecated: migrated to domain resolver DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 29acbf1258..4165d12640 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -45,8 +45,8 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL logger: logger, localAddresses: options.Address, } - if options.Detour == "" { - options.IsWireGuardListener = true + if options.Detour != "" && options.ListenPort != 0 { + return nil, E.New("`listen_port` is conflict with `detour`") } outboundDialer, err := dialer.NewWithOptions(dialer.Options{ Context: ctx, diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 1e7a32c70b..129e69b829 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -46,9 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL logger: logger, localAddresses: options.LocalAddress, } - if options.Detour == "" { - options.IsWireGuardListener = true - } else if options.GSO { + if options.Detour != "" && options.GSO { return nil, E.New("gso is conflict with detour") } outboundDialer, err := dialer.NewWithOptions(dialer.Options{ From 596b66f3977e945f91c08014a609b66b1ae5ad38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E5=AE=B9?= Date: Sat, 26 Apr 2025 14:03:51 +0800 Subject: [PATCH 0426/1272] Report invalid DNS address early --- dns/transport/https.go | 3 +++ dns/transport/quic/http3.go | 3 +++ dns/transport/quic/quic.go | 4 ++++ dns/transport/tcp.go | 4 ++++ dns/transport/tls.go | 3 +++ dns/transport/udp.go | 4 ++++ 6 files changed, 21 insertions(+) diff --git a/dns/transport/https.go b/dns/transport/https.go index 1750fd264f..a13d911630 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -96,6 +96,9 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options if serverAddr.Port == 0 { serverAddr.Port = 443 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return NewHTTPSRaw( dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions), logger, diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index 0d871741fa..fd1591a379 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -92,6 +92,9 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options if serverAddr.Port == 0 { serverAddr.Port = 443 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return &HTTP3Transport{ TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions), logger: logger, diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index 18f8b9febe..515aff58e3 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -16,6 +16,7 @@ import ( sQUIC "github.com/sagernet/sing-quic" "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" @@ -58,6 +59,9 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options if serverAddr.Port == 0 { serverAddr.Port = 853 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return &Transport{ TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions), ctx: ctx, diff --git a/dns/transport/tcp.go b/dns/transport/tcp.go index a814c030ea..3039c57422 100644 --- a/dns/transport/tcp.go +++ b/dns/transport/tcp.go @@ -13,6 +13,7 @@ import ( "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" @@ -40,6 +41,9 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o if serverAddr.Port == 0 { serverAddr.Port = 53 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return &TCPTransport{ TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options), dialer: transportDialer, diff --git a/dns/transport/tls.go b/dns/transport/tls.go index a99bf2f704..b6ac294aa8 100644 --- a/dns/transport/tls.go +++ b/dns/transport/tls.go @@ -57,6 +57,9 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o if serverAddr.Port == 0 { serverAddr.Port = 853 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return &TLSTransport{ TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), logger: logger, diff --git a/dns/transport/udp.go b/dns/transport/udp.go index e2ad9db7f0..a9c1d4d98d 100644 --- a/dns/transport/udp.go +++ b/dns/transport/udp.go @@ -13,6 +13,7 @@ import ( "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" @@ -47,6 +48,9 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o if serverAddr.Port == 0 { serverAddr.Port = 53 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil } From ce32d1c2c3f45b3b68ec6aadf91635c6efe7d685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 28 Apr 2025 10:31:34 +0800 Subject: [PATCH 0427/1272] documentation: Fix anytls padding scheme description --- docs/configuration/inbound/anytls.md | 22 ++++++++++++---------- docs/configuration/inbound/anytls.zh.md | 22 ++++++++++++---------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/configuration/inbound/anytls.md b/docs/configuration/inbound/anytls.md index 55790810f6..f3780119f6 100644 --- a/docs/configuration/inbound/anytls.md +++ b/docs/configuration/inbound/anytls.md @@ -42,16 +42,18 @@ AnyTLS padding scheme line array. Default padding scheme: -``` -stop=8 -0=30-30 -1=100-400 -2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000 -3=9-9,500-1000 -4=500-1000 -5=500-1000 -6=500-1000 -7=500-1000 +```json +[ + "stop=8", + "0=30-30", + "1=100-400", + "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000", + "3=9-9,500-1000", + "4=500-1000", + "5=500-1000", + "6=500-1000", + "7=500-1000" +] ``` #### tls diff --git a/docs/configuration/inbound/anytls.zh.md b/docs/configuration/inbound/anytls.zh.md index 099da77724..55b6749ed1 100644 --- a/docs/configuration/inbound/anytls.zh.md +++ b/docs/configuration/inbound/anytls.zh.md @@ -42,16 +42,18 @@ AnyTLS 填充方案行数组。 默认填充方案: -``` -stop=8 -0=30-30 -1=100-400 -2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000 -3=9-9,500-1000 -4=500-1000 -5=500-1000 -6=500-1000 -7=500-1000 +```json +[ + "stop=8", + "0=30-30", + "1=100-400", + "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000", + "3=9-9,500-1000", + "4=500-1000", + "5=500-1000", + "6=500-1000", + "7=500-1000" +] ``` #### tls From e7ef1b2368ba371c17f5e4768f9412da6e5987e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 30 Apr 2025 20:00:32 +0800 Subject: [PATCH 0428/1272] Handle EDNS version downgrade --- dns/client.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dns/client.go b/dns/client.go index 90c950b474..5d7f109d66 100644 --- a/dns/client.go +++ b/dns/client.go @@ -232,10 +232,20 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m record.Header().Ttl = timeToLive } } - response.Id = messageId if !disableCache { c.storeCache(transport, question, response, timeToLive) } + response.Id = messageId + requestEDNSOpt := message.IsEdns0() + responseEDNSOpt := response.IsEdns0() + if responseEDNSOpt != nil && (requestEDNSOpt == nil || requestEDNSOpt.Version() < responseEDNSOpt.Version()) { + response.Extra = common.Filter(response.Extra, func(it dns.RR) bool { + return it.Header().Rrtype != dns.TypeOPT + }) + if requestEDNSOpt != nil { + response.SetEdns0(responseEDNSOpt.UDPSize(), responseEDNSOpt.Do()) + } + } logExchangedResponse(c.logger, ctx, response, timeToLive) return response, err } From dbdcce20a8aa30dc66b1e229125e09bfcf8a335b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 3 May 2025 11:35:53 +0800 Subject: [PATCH 0429/1272] Update utls to v1.7.2 --- .github/workflows/build.yml | 2 +- .github/workflows/linux.yml | 5 +- .golangci.yml | 1 - .goreleaser.fury.yaml | 1 - .goreleaser.yaml | 2 - Dockerfile | 2 +- Makefile | 4 +- common/badtls/read_wait_utls.go | 7 +- common/tls/config.go | 1 + common/tls/ech_tag_stub.go | 5 + common/tls/reality_client.go | 31 +++++-- common/tls/reality_server.go | 24 ++--- common/tls/reality_stub.go | 14 +-- common/tls/utls_client.go | 2 +- common/tls/utls_stub.go | 7 +- docs/installation/build-from-source.md | 1 - docs/installation/build-from-source.zh.md | 1 - go.mod | 7 +- go.sum | 14 ++- test/reality_test.go | 108 ++++++++++++++++++++++ 20 files changed, 178 insertions(+), 61 deletions(-) create mode 100644 common/tls/ech_tag_stub.go create mode 100644 test/reality_test.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 33ccaee8cc..023f9db12f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -140,7 +140,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,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' 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 7393e65410..79b4d645bd 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -80,10 +80,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api' - if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then - TAGS="${TAGS},with_ech" - fi + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build run: | diff --git a/.golangci.yml b/.golangci.yml index 28b45337f0..9cb20ee801 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -28,7 +28,6 @@ run: - with_dhcp - with_wireguard - with_utls - - with_reality_server - with_acme - with_clash_api diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml index c149b1f440..4237b075dd 100644 --- a/.goreleaser.fury.yaml +++ b/.goreleaser.fury.yaml @@ -15,7 +15,6 @@ builds: - with_dhcp - with_wireguard - with_utls - - with_reality_server - with_acme - with_clash_api - with_tailscale diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 23e0771f88..4c333d3bb1 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -17,7 +17,6 @@ builds: - with_dhcp - with_wireguard - with_utls - - with_reality_server - with_acme - with_clash_api - with_tailscale @@ -47,7 +46,6 @@ builds: - with_dhcp - with_wireguard - with_utls - - with_reality_server - with_acme - with_clash_api - with_tailscale diff --git a/Dockerfile b/Dockerfile index b2700b59f2..fecd98a6f2 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_reality_server,with_acme,with_clash_api,with_tailscale" \ + "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=" \ ./cmd/sing-box diff --git a/Makefile b/Makefile index caf5ee91af..3a9f53d943 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_acme,with_tailscale -TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server +TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_clash_api,with_quic,with_utls,with_acme,with_tailscale +TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) diff --git a/common/badtls/read_wait_utls.go b/common/badtls/read_wait_utls.go index ebdb2251a9..bba016e487 100644 --- a/common/badtls/read_wait_utls.go +++ b/common/badtls/read_wait_utls.go @@ -7,7 +7,8 @@ import ( _ "unsafe" "github.com/sagernet/sing/common" - "github.com/sagernet/utls" + + "github.com/metacubex/utls" ) func init() { @@ -24,8 +25,8 @@ func init() { }) } -//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord +//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord func utlsReadRecord(c *tls.Conn) error -//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage +//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage func utlsHandlePostHandshakeMessage(c *tls.Conn) error diff --git a/common/tls/config.go b/common/tls/config.go index 52d88af0d1..72bbd19419 100644 --- a/common/tls/config.go +++ b/common/tls/config.go @@ -18,6 +18,7 @@ type ( STDConfig = tls.Config STDConn = tls.Conn ConnectionState = tls.ConnectionState + CurveID = tls.CurveID ) func ParseTLSVersion(version string) (uint16, error) { diff --git a/common/tls/ech_tag_stub.go b/common/tls/ech_tag_stub.go new file mode 100644 index 0000000000..1a9cbd56eb --- /dev/null +++ b/common/tls/ech_tag_stub.go @@ -0,0 +1,5 @@ +//go:build with_ech + +package tls + +var _ int = "Due to the migration to stdlib, the separate `with_ech` build tag has been deprecated and is no longer needed, please update your build configuration." diff --git a/common/tls/reality_client.go b/common/tls/reality_client.go index 748567b5d3..823e82857d 100644 --- a/common/tls/reality_client.go +++ b/common/tls/reality_client.go @@ -29,12 +29,13 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/debug" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" aTLS "github.com/sagernet/sing/common/tls" - utls "github.com/sagernet/utls" + utls "github.com/metacubex/utls" "golang.org/x/crypto/hkdf" "golang.org/x/net/http2" ) @@ -114,6 +115,22 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn if err != nil { return nil, err } + for _, extension := range uConn.Extensions { + if ce, ok := extension.(*utls.SupportedCurvesExtension); ok { + ce.Curves = common.Filter(ce.Curves, func(curveID utls.CurveID) bool { + return curveID != utls.X25519MLKEM768 + }) + } + if ks, ok := extension.(*utls.KeyShareExtension); ok { + ks.KeyShares = common.Filter(ks.KeyShares, func(share utls.KeyShare) bool { + return share.Group != utls.X25519MLKEM768 + }) + } + } + err = uConn.BuildHandshakeState() + if err != nil { + return nil, err + } if len(uConfig.NextProtos) > 0 { for _, extension := range uConn.Extensions { @@ -148,9 +165,13 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn if err != nil { return nil, err } - ecdheKey := uConn.HandshakeState.State13.EcdheKey + keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys + if keyShareKeys == nil { + return nil, E.New("nil KeyShareKeys") + } + ecdheKey := keyShareKeys.Ecdhe if ecdheKey == nil { - return nil, E.New("nil ecdhe_key") + return nil, E.New("nil ecdheKey") } authKey, err := ecdheKey.ECDH(publicKey) if err != nil { @@ -214,10 +235,6 @@ func realityClientFallback(ctx context.Context, uConn net.Conn, serverName strin response.Body.Close() } -func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) { - e.uClient.config.SessionIDGenerator = generator -} - func (e *RealityClientConfig) Clone() Config { return &RealityClientConfig{ e.ctx, diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index 84b0979da5..2742024877 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -1,4 +1,4 @@ -//go:build with_reality_server +//go:build with_utls package tls @@ -7,28 +7,29 @@ import ( "crypto/tls" "encoding/base64" "encoding/hex" + "fmt" "net" "time" - "github.com/sagernet/reality" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/debug" 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" + + utls "github.com/metacubex/utls" ) var _ ServerConfigCompat = (*RealityServerConfig)(nil) type RealityServerConfig struct { - config *reality.Config + config *utls.RealityConfig } func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) { - var tlsConfig reality.Config + var tlsConfig utls.RealityConfig if options.ACME != nil && len(options.ACME.Domain) > 0 { return nil, E.New("acme is unavailable in reality") @@ -74,6 +75,11 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb } tlsConfig.SessionTicketsDisabled = true + tlsConfig.Log = func(format string, v ...any) { + if logger != nil { + logger.Trace(fmt.Sprintf(format, v...)) + } + } tlsConfig.Type = N.NetworkTCP tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String() @@ -113,10 +119,6 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) } - if debug.Enabled { - tlsConfig.Show = true - } - return &RealityServerConfig{&tlsConfig}, nil } @@ -157,7 +159,7 @@ func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) { } func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) { - tlsConn, err := reality.Server(ctx, conn, c.config) + tlsConn, err := utls.RealityServer(ctx, conn, c.config) if err != nil { return nil, err } @@ -173,7 +175,7 @@ func (c *RealityServerConfig) Clone() Config { var _ Conn = (*realityConnWrapper)(nil) type realityConnWrapper struct { - *reality.Conn + *utls.Conn } func (c *realityConnWrapper) ConnectionState() ConnectionState { diff --git a/common/tls/reality_stub.go b/common/tls/reality_stub.go index 8d394f7b01..0feb2aac0a 100644 --- a/common/tls/reality_stub.go +++ b/common/tls/reality_stub.go @@ -1,15 +1,5 @@ -//go:build !with_reality_server +//go:build with_reality_server package tls -import ( - "context" - - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { - return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`) -} +var _ int = "The separate `with_reality_server` build tag has been merged into `with_utls` and is no longer needed, please update your build configuration." diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index fe8e4296d9..5c0de6ad48 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -16,8 +16,8 @@ import ( "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" - utls "github.com/sagernet/utls" + utls "github.com/metacubex/utls" "golang.org/x/net/http2" ) diff --git a/common/tls/utls_stub.go b/common/tls/utls_stub.go index d015611aa4..ea5da4a7ab 100644 --- a/common/tls/utls_stub.go +++ b/common/tls/utls_stub.go @@ -5,6 +5,7 @@ package tls import ( "context" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" ) @@ -14,5 +15,9 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out } func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { - return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`) + return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`) +} + +func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, 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/docs/installation/build-from-source.md b/docs/installation/build-from-source.md index c286c36626..1f13e8145f 100644 --- a/docs/installation/build-from-source.md +++ b/docs/installation/build-from-source.md @@ -52,7 +52,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box | `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). | | `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). | | `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). | -| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). | | `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | | `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). | | `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | diff --git a/docs/installation/build-from-source.zh.md b/docs/installation/build-from-source.zh.md index c022292954..512d2e24e6 100644 --- a/docs/installation/build-from-source.zh.md +++ b/docs/installation/build-from-source.zh.md @@ -56,7 +56,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box | `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). | | `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). | | `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). | -| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). | | `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | | `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). | | `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | diff --git a/go.mod b/go.mod index 089261bb1b..e296ca35e5 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/libdns/cloudflare v0.1.1 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 + github.com/metacubex/utls v1.7.0-alpha.3 github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.63 github.com/oschwald/maxminddb-golang v1.12.0 @@ -25,18 +26,16 @@ require ( github.com/sagernet/gomobile v0.1.6 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.49.0-beta.1 - github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.4.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.20250316154757-6f9e732e5056 + github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 github.com/sagernet/sing-tun v0.6.10-0.20250620051458-5e343c4b66b2 - github.com/sagernet/sing-vmess v0.2.3 + github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-mod.5 - github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index 5d6625507e..7452680b85 100644 --- a/go.sum +++ b/go.sum @@ -125,6 +125,8 @@ github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= 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/metacubex/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA= +github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= @@ -171,8 +173,6 @@ 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.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.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo= github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= @@ -184,18 +184,16 @@ github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVC 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.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-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.6.10-0.20250620051458-5e343c4b66b2 h1:ykbqGFHDNVvp0jhgLime/XBAtQpcOcFpT8Rs5Hcc5n4= github.com/sagernet/sing-tun v0.6.10-0.20250620051458-5e343c4b66b2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= -github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44= -github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= +github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88 h1:0pVm8sPOel+BoiCddW3pV3cKDKEaSioVTYDdTSKjyFI= +github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w= 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-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A= github.com/sagernet/tailscale v1.80.3-mod.5/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.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= diff --git a/test/reality_test.go b/test/reality_test.go new file mode 100644 index 0000000000..220ccb6c18 --- /dev/null +++ b/test/reality_test.go @@ -0,0 +1,108 @@ +package main + +import ( + "net/netip" + "testing" + + 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 TestReality(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", + 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", + 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) +} From 3b6ddcae3765ae1ad866a2eadddfe5e249819a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 5 May 2025 13:29:15 +0800 Subject: [PATCH 0430/1272] Update quic-go to v0.52.0 --- go.mod | 20 ++++++++------------ go.sum | 44 ++++++++++++++++---------------------------- 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index e296ca35e5..9b2a08a631 100644 --- a/go.mod +++ b/go.mod @@ -25,10 +25,10 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.6 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb - github.com/sagernet/quic-go v0.49.0-beta.1 + github.com/sagernet/quic-go v0.52.0-beta.1 github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b github.com/sagernet/sing-mux v0.3.2 - github.com/sagernet/sing-quic v0.4.4 + github.com/sagernet/sing-quic v0.5.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 @@ -43,11 +43,11 @@ require ( github.com/vishvananda/netns v0.0.4 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.33.0 + golang.org/x/crypto v0.37.0 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 golang.org/x/mod v0.22.0 golang.org/x/net v0.35.0 - golang.org/x/sys v0.30.0 + golang.org/x/sys v0.32.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.35.1 @@ -75,7 +75,6 @@ require ( github.com/gaissmai/bart v0.11.1 // indirect github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // 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 @@ -84,7 +83,6 @@ require ( github.com/google/go-cmp v0.6.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/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 @@ -102,12 +100,10 @@ require ( github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/socket v0.5.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect - github.com/onsi/ginkgo/v2 v2.17.2 // indirect 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.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/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect @@ -125,9 +121,9 @@ require ( github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/term v0.31.0 // indirect + golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.29.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect diff --git a/go.sum b/go.sum index 7452680b85..664a14b0fc 100644 --- a/go.sum +++ b/go.sum @@ -51,12 +51,8 @@ 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-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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= @@ -78,8 +74,6 @@ 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= @@ -135,10 +129,6 @@ 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/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/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -148,10 +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.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/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= @@ -171,15 +159,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.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/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.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo= github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.4.4 h1:qqOCLnzHbqKkj/wBcXEI3rhSyqoGlqDdv2S6mz2d/JA= -github.com/sagernet/sing-quic v0.4.4/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= +github.com/sagernet/sing-quic v0.5.0-beta.3 h1:X/acRNsqQNfDlmwE7SorHfaZiny5e67hqIzM/592ric= +github.com/sagernet/sing-quic v0.5.0-beta.3/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0= 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= @@ -255,8 +243,8 @@ 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.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 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/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= @@ -268,8 +256,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 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.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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= @@ -279,15 +267,15 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.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.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 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= From c3403c5413f92222d83c2fb475eef795c1329e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 5 May 2025 08:50:22 +0800 Subject: [PATCH 0431/1272] Add control options for listeners --- common/listener/listener_tcp.go | 11 +++++ common/listener/listener_udp.go | 35 ++++++++++++-- docs/configuration/shared/dial.md | 26 +++++----- docs/configuration/shared/dial.zh.md | 24 ++++----- docs/configuration/shared/listen.md | 66 +++++++++++++++++-------- docs/configuration/shared/listen.zh.md | 67 +++++++++++++++++--------- option/inbound.go | 5 +- option/outbound.go | 2 +- 8 files changed, 166 insertions(+), 70 deletions(-) diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index c5995fad53..b8ecc1dbe5 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -8,9 +8,11 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" "github.com/metacubex/tfo-go" ) @@ -23,6 +25,15 @@ func (l *Listener) ListenTCP() (net.Listener, error) { var err error bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) var listenConfig net.ListenConfig + if l.listenOptions.BindInterface != "" { + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + } if l.listenOptions.TCPKeepAlive >= 0 { keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) if keepIdle == 0 { diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go index eb5c7b5065..d189bf13e9 100644 --- a/common/listener/listener_udp.go +++ b/common/listener/listener_udp.go @@ -6,16 +6,27 @@ import ( "net/netip" "os" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) func (l *Listener) ListenUDP() (net.PacketConn, error) { bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) - var lc net.ListenConfig + var listenConfig net.ListenConfig + if l.listenOptions.BindInterface != "" { + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + } var udpFragment bool if l.listenOptions.UDPFragment != nil { udpFragment = *l.listenOptions.UDPFragment @@ -23,10 +34,10 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) { udpFragment = l.listenOptions.UDPFragmentDefault } if !udpFragment { - lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) + listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment()) } udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { - return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) + return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) }) if err != nil { return nil, err @@ -39,12 +50,30 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) { func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) { return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) { + if l.listenOptions.BindInterface != "" { + dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + dialer.Control = control.Append(dialer.Control, control.ReuseAddr()) + } return dialer.DialContext(ctx, network, address) }) } func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) { return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { + if l.listenOptions.BindInterface != "" { + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + } return listenConfig.ListenPacket(ctx, network, address) }) } diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 97fbfce327..f48f355d9b 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -25,11 +25,12 @@ icon: material/new-box "inet6_bind_address": "", "routing_mark": 0, "reuse_addr": false, + "netns": "", "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, - "netns": "", + "domain_resolver": "", // or {} "network_strategy": "", "network_type": [], @@ -37,6 +38,7 @@ icon: material/new-box "fallback_delay": "", // Deprecated + "domain_strategy": "" } ``` @@ -73,10 +75,22 @@ The IPv6 address to bind to. Set netfilter routing mark. +Integers (e.g. `1234`) and string hexadecimals (e.g. `"0x1234"`) are supported. + #### reuse_addr Reuse listener address. +#### netns + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set network namespace, name or path. + #### connect_timeout Connect timeout, in golang's Duration format. @@ -102,16 +116,6 @@ Enable TCP Multi Path. Enable UDP fragmentation. -#### netns - -!!! question "Since sing-box 1.12.0" - -!!! quote "" - - Only supported on Linux. - -Set network namespace, name or path. - #### domain_resolver !!! warning "" diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 292cdc7c5c..babb43e983 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -25,11 +25,11 @@ icon: material/new-box "inet6_bind_address": "", "routing_mark": 0, "reuse_addr": false, + "netns": "", "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, - "netns": "", "domain_resolver": "", // 或 {} "network_strategy": "", "network_type": [], @@ -74,10 +74,22 @@ icon: material/new-box 设置 netfilter 路由标记。 +支持数字 (如 `1234`) 和十六进制字符串 (如 `"0x1234"`)。 + #### reuse_addr 重用监听地址。 +#### netns + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置网络命名空间,名称或路径。 + #### connect_timeout 连接超时,采用 golang 的 Duration 格式。 @@ -101,16 +113,6 @@ icon: material/new-box 启用 UDP 分段。 -#### netns - -!!! question "自 sing-box 1.12.0 起" - -!!! quote "" - - 仅支持 Linux。 - -设置网络命名空间,名称或路径。 - #### domain_resolver !!! warning "" diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index ab6d07ce2c..4040e42f07 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -4,7 +4,10 @@ icon: material/new-box !!! quote "Changes in sing-box 1.12.0" - :material-plus: [netns](#netns) + :material-plus: [netns](#netns) + :material-plus: [bind_interface](#bind_interface) + :material-plus: [routing_mark](#routing_mark) + :material-plus: [reuse_addr](#reuse_addr) !!! quote "Changes in sing-box 1.11.0" @@ -20,12 +23,18 @@ icon: material/new-box { "listen": "", "listen_port": 0, + "bind_interface": "", + "routing_mark": 0, + "reuse_addr": false, + "netns": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, "udp_timeout": "", - "netns": "", "detour": "", + + // Deprecated + "sniff": false, "sniff_override_destination": false, "sniff_timeout": "", @@ -36,15 +45,6 @@ icon: material/new-box ### Fields -| Field | Available Context | -|--------------------------------|---------------------------------------------------------| -| `listen` | Needs to listen on TCP or UDP. | -| `listen_port` | Needs to listen on TCP or UDP. | -| `tcp_fast_open` | Needs to listen on TCP. | -| `tcp_multi_path` | Needs to listen on TCP. | -| `udp_timeout` | Needs to assemble UDP connections. | -| `udp_disable_domain_unmapping` | Needs to listen on UDP and accept domain UDP addresses. | - #### listen ==Required== @@ -55,6 +55,40 @@ Listen address. Listen port. +#### bind_interface + +!!! question "Since sing-box 1.12.0" + +The network interface to bind to. + +#### routing_mark + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set netfilter routing mark. + +Integers (e.g. `1234`) and string hexadecimals (e.g. `"0x1234"`) are supported. + +#### reuse_addr + +!!! question "Since sing-box 1.12.0" + +Reuse listener address. + +#### netns + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set network namespace, name or path. + #### tcp_fast_open Enable TCP Fast Open. @@ -77,16 +111,6 @@ UDP NAT expiration time. `5m` will be used by default. -#### netns - -!!! question "Since sing-box 1.12.0" - -!!! quote "" - - Only supported on Linux. - -Set network namespace, name or path. - #### detour If set, connections will be forwarded to the specified inbound. diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index bc67ac98c0..cd12036ca3 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -4,7 +4,10 @@ icon: material/new-box !!! quote "Changes in sing-box 1.12.0" - :material-plus: [netns](#netns) + :material-plus: [netns](#netns) + :material-plus: [bind_interface](#bind_interface) + :material-plus: [routing_mark](#routing_mark) + :material-plus: [reuse_addr](#reuse_addr) !!! quote "sing-box 1.11.0 中的更改" @@ -20,12 +23,18 @@ icon: material/new-box { "listen": "", "listen_port": 0, + "bind_interface": "", + "routing_mark": 0, + "reuse_addr": false, + "netns": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, "udp_timeout": "", - "netns": "", "detour": "", + + // 废弃的 + "sniff": false, "sniff_override_destination": false, "sniff_timeout": "", @@ -34,16 +43,6 @@ icon: material/new-box } ``` - -| 字段 | 可用上下文 | -|------------------|-----------------| -| `listen` | 需要监听 TCP 或 UDP。 | -| `listen_port` | 需要监听 TCP 或 UDP。 | -| `tcp_fast_open` | 需要监听 TCP。 | -| `tcp_multi_path` | 需要监听 TCP。 | -| `udp_timeout` | 需要组装 UDP 连接。 | -| - ### 字段 #### listen @@ -56,6 +55,40 @@ icon: material/new-box 监听端口。 +#### bind_interface + +!!! question "自 sing-box 1.12.0 起" + +要绑定到的网络接口。 + +#### routing_mark + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置 netfilter 路由标记。 + +支持数字 (如 `1234`) 和十六进制字符串 (如 `"0x1234"`)。 + +#### reuse_addr + +!!! question "自 sing-box 1.12.0 起" + +重用监听地址。 + +#### netns + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置网络命名空间,名称或路径。 + #### tcp_fast_open 启用 TCP Fast Open。 @@ -78,16 +111,6 @@ UDP NAT 过期时间。 默认使用 `5m`。 -#### netns - -!!! question "自 sing-box 1.12.0 起" - -!!! quote "" - - 仅支持 Linux。 - -设置网络命名空间,名称或路径。 - #### detour 如果设置,连接将被转发到指定的入站。 diff --git a/option/inbound.go b/option/inbound.go index b704c7e328..99c5bc91a4 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -61,6 +61,10 @@ type InboundOptions struct { type ListenOptions struct { Listen *badoption.Addr `json:"listen,omitempty"` ListenPort uint16 `json:"listen_port,omitempty"` + BindInterface string `json:"bind_interface,omitempty"` + RoutingMark FwMark `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,omitempty"` + NetNs string `json:"netns,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"` @@ -68,7 +72,6 @@ type ListenOptions struct { UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - NetNs string `json:"netns,omitempty"` // Deprecated: removed ProxyProtocol bool `json:"proxy_protocol,omitempty"` diff --git a/option/outbound.go b/option/outbound.go index 9b04de93cf..2520d000f8 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -72,12 +72,12 @@ type DialerOptions struct { 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:"-"` - NetNs string `json:"netns,omitempty"` DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` From 26ec73c71b523335ab7788fa29b7bcf5003b7ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 5 May 2025 13:33:35 +0800 Subject: [PATCH 0432/1272] Update protobuf and grpc --- experimental/v2rayapi/stats.pb.go | 352 ++++++++----------------- experimental/v2rayapi/stats_grpc.pb.go | 29 +- go.mod | 6 +- go.sum | 30 ++- transport/v2raygrpc/client.go | 2 +- transport/v2raygrpc/conn.go | 1 + transport/v2raygrpc/custom_name.go | 2 +- transport/v2raygrpc/stream.pb.go | 65 ++--- transport/v2raygrpc/stream_grpc.pb.go | 81 ++---- 9 files changed, 206 insertions(+), 362 deletions(-) diff --git a/experimental/v2rayapi/stats.pb.go b/experimental/v2rayapi/stats.pb.go index 45cde82441..586b9a7fdf 100644 --- a/experimental/v2rayapi/stats.pb.go +++ b/experimental/v2rayapi/stats.pb.go @@ -3,6 +3,7 @@ package v2rayapi import ( reflect "reflect" sync "sync" + unsafe "unsafe" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -16,23 +17,20 @@ const ( ) type GetStatsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Name of the stat counter. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Whether or not to reset the counter to fetching its value. - Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetStatsRequest) Reset() { *x = GetStatsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetStatsRequest) String() string { @@ -43,7 +41,7 @@ func (*GetStatsRequest) ProtoMessage() {} func (x *GetStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -73,21 +71,18 @@ func (x *GetStatsRequest) GetReset_() bool { } type Stat struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Stat) Reset() { *x = Stat{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Stat) String() string { @@ -98,7 +93,7 @@ func (*Stat) ProtoMessage() {} func (x *Stat) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -128,20 +123,17 @@ func (x *Stat) GetValue() int64 { } type GetStatsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"` unknownFields protoimpl.UnknownFields - - Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"` + sizeCache protoimpl.SizeCache } func (x *GetStatsResponse) Reset() { *x = GetStatsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetStatsResponse) String() string { @@ -152,7 +144,7 @@ func (*GetStatsResponse) ProtoMessage() {} func (x *GetStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -175,24 +167,21 @@ func (x *GetStatsResponse) GetStat() *Stat { } type QueryStatsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Deprecated, use Patterns instead - Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` - Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` - Patterns []string `protobuf:"bytes,3,rep,name=patterns,proto3" json:"patterns,omitempty"` - Regexp bool `protobuf:"varint,4,opt,name=regexp,proto3" json:"regexp,omitempty"` + Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + Patterns []string `protobuf:"bytes,3,rep,name=patterns,proto3" json:"patterns,omitempty"` + Regexp bool `protobuf:"varint,4,opt,name=regexp,proto3" json:"regexp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *QueryStatsRequest) Reset() { *x = QueryStatsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *QueryStatsRequest) String() string { @@ -203,7 +192,7 @@ func (*QueryStatsRequest) ProtoMessage() {} func (x *QueryStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -247,20 +236,17 @@ func (x *QueryStatsRequest) GetRegexp() bool { } type QueryStatsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"` unknownFields protoimpl.UnknownFields - - Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"` + sizeCache protoimpl.SizeCache } func (x *QueryStatsResponse) Reset() { *x = QueryStatsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *QueryStatsResponse) String() string { @@ -271,7 +257,7 @@ func (*QueryStatsResponse) ProtoMessage() {} func (x *QueryStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -294,18 +280,16 @@ func (x *QueryStatsResponse) GetStat() []*Stat { } type SysStatsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SysStatsRequest) Reset() { *x = SysStatsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SysStatsRequest) String() string { @@ -316,7 +300,7 @@ func (*SysStatsRequest) ProtoMessage() {} func (x *SysStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -332,29 +316,26 @@ func (*SysStatsRequest) Descriptor() ([]byte, []int) { } type SysStatsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"` + NumGC uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"` + Alloc uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"` + TotalAlloc uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"` + Sys uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"` + Mallocs uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"` + Frees uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"` + LiveObjects uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"` + PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"` + Uptime uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"` unknownFields protoimpl.UnknownFields - - NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"` - NumGC uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"` - Alloc uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"` - TotalAlloc uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"` - Sys uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"` - Mallocs uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"` - Frees uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"` - LiveObjects uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"` - PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"` - Uptime uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"` + sizeCache protoimpl.SizeCache } func (x *SysStatsResponse) Reset() { *x = SysStatsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SysStatsResponse) String() string { @@ -365,7 +346,7 @@ func (*SysStatsResponse) ProtoMessage() {} func (x *SysStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -452,94 +433,60 @@ func (x *SysStatsResponse) GetUptime() uint32 { var File_experimental_v2rayapi_stats_proto protoreflect.FileDescriptor -var file_experimental_v2rayapi_stats_proto_rawDesc = []byte{ - 0x0a, 0x21, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2f, 0x76, - 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, - 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x22, 0x3b, 0x0a, 0x0f, 0x47, 0x65, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x22, 0x30, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x43, 0x0a, 0x10, 0x47, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, - 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x78, - 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x77, - 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x14, 0x0a, - 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, - 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x12, - 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x22, 0x45, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, - 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x78, - 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x11, - 0x0a, 0x0f, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xa2, 0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, - 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x4e, 0x75, - 0x6d, 0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x75, - 0x6d, 0x47, 0x43, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, - 0x12, 0x14, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, - 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54, 0x6f, 0x74, 0x61, - 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x61, 0x6c, 0x6c, - 0x6f, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, - 0x63, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x69, 0x76, 0x65, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4c, - 0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x61, - 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, - 0x0a, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, - 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x32, 0xb4, 0x02, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, - 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x65, 0x78, - 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, - 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, - 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, - 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x0b, 0x47, - 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x65, 0x78, 0x70, - 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, - 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, - 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x34, 0x5a, - 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x61, 0x67, 0x65, - 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x78, 0x2f, 0x65, 0x78, - 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_experimental_v2rayapi_stats_proto_rawDesc = "" + + "\n" + + "!experimental/v2rayapi/stats.proto\x12\x15experimental.v2rayapi\";\n" + + "\x0fGetStatsRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05reset\x18\x02 \x01(\bR\x05reset\"0\n" + + "\x04Stat\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05value\x18\x02 \x01(\x03R\x05value\"C\n" + + "\x10GetStatsResponse\x12/\n" + + "\x04stat\x18\x01 \x01(\v2\x1b.experimental.v2rayapi.StatR\x04stat\"w\n" + + "\x11QueryStatsRequest\x12\x18\n" + + "\apattern\x18\x01 \x01(\tR\apattern\x12\x14\n" + + "\x05reset\x18\x02 \x01(\bR\x05reset\x12\x1a\n" + + "\bpatterns\x18\x03 \x03(\tR\bpatterns\x12\x16\n" + + "\x06regexp\x18\x04 \x01(\bR\x06regexp\"E\n" + + "\x12QueryStatsResponse\x12/\n" + + "\x04stat\x18\x01 \x03(\v2\x1b.experimental.v2rayapi.StatR\x04stat\"\x11\n" + + "\x0fSysStatsRequest\"\xa2\x02\n" + + "\x10SysStatsResponse\x12\"\n" + + "\fNumGoroutine\x18\x01 \x01(\rR\fNumGoroutine\x12\x14\n" + + "\x05NumGC\x18\x02 \x01(\rR\x05NumGC\x12\x14\n" + + "\x05Alloc\x18\x03 \x01(\x04R\x05Alloc\x12\x1e\n" + + "\n" + + "TotalAlloc\x18\x04 \x01(\x04R\n" + + "TotalAlloc\x12\x10\n" + + "\x03Sys\x18\x05 \x01(\x04R\x03Sys\x12\x18\n" + + "\aMallocs\x18\x06 \x01(\x04R\aMallocs\x12\x14\n" + + "\x05Frees\x18\a \x01(\x04R\x05Frees\x12 \n" + + "\vLiveObjects\x18\b \x01(\x04R\vLiveObjects\x12\"\n" + + "\fPauseTotalNs\x18\t \x01(\x04R\fPauseTotalNs\x12\x16\n" + + "\x06Uptime\x18\n" + + " \x01(\rR\x06Uptime2\xb4\x02\n" + + "\fStatsService\x12]\n" + + "\bGetStats\x12&.experimental.v2rayapi.GetStatsRequest\x1a'.experimental.v2rayapi.GetStatsResponse\"\x00\x12c\n" + + "\n" + + "QueryStats\x12(.experimental.v2rayapi.QueryStatsRequest\x1a).experimental.v2rayapi.QueryStatsResponse\"\x00\x12`\n" + + "\vGetSysStats\x12&.experimental.v2rayapi.SysStatsRequest\x1a'.experimental.v2rayapi.SysStatsResponse\"\x00B4Z2github.com/sagernet/sing-box/experimental/v2rayapib\x06proto3" var ( file_experimental_v2rayapi_stats_proto_rawDescOnce sync.Once - file_experimental_v2rayapi_stats_proto_rawDescData = file_experimental_v2rayapi_stats_proto_rawDesc + file_experimental_v2rayapi_stats_proto_rawDescData []byte ) func file_experimental_v2rayapi_stats_proto_rawDescGZIP() []byte { file_experimental_v2rayapi_stats_proto_rawDescOnce.Do(func() { - file_experimental_v2rayapi_stats_proto_rawDescData = protoimpl.X.CompressGZIP(file_experimental_v2rayapi_stats_proto_rawDescData) + file_experimental_v2rayapi_stats_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_experimental_v2rayapi_stats_proto_rawDesc), len(file_experimental_v2rayapi_stats_proto_rawDesc))) }) return file_experimental_v2rayapi_stats_proto_rawDescData } var ( file_experimental_v2rayapi_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 7) - file_experimental_v2rayapi_stats_proto_goTypes = []interface{}{ + file_experimental_v2rayapi_stats_proto_goTypes = []any{ (*GetStatsRequest)(nil), // 0: experimental.v2rayapi.GetStatsRequest (*Stat)(nil), // 1: experimental.v2rayapi.Stat (*GetStatsResponse)(nil), // 2: experimental.v2rayapi.GetStatsResponse @@ -571,97 +518,11 @@ func file_experimental_v2rayapi_stats_proto_init() { if File_experimental_v2rayapi_stats_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_experimental_v2rayapi_stats_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Stat); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryStatsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryStatsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SysStatsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SysStatsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_experimental_v2rayapi_stats_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_experimental_v2rayapi_stats_proto_rawDesc), len(file_experimental_v2rayapi_stats_proto_rawDesc)), NumEnums: 0, NumMessages: 7, NumExtensions: 0, @@ -672,7 +533,6 @@ func file_experimental_v2rayapi_stats_proto_init() { MessageInfos: file_experimental_v2rayapi_stats_proto_msgTypes, }.Build() File_experimental_v2rayapi_stats_proto = out.File - file_experimental_v2rayapi_stats_proto_rawDesc = nil file_experimental_v2rayapi_stats_proto_goTypes = nil file_experimental_v2rayapi_stats_proto_depIdxs = nil } diff --git a/experimental/v2rayapi/stats_grpc.pb.go b/experimental/v2rayapi/stats_grpc.pb.go index ee95028747..3788f5205e 100644 --- a/experimental/v2rayapi/stats_grpc.pb.go +++ b/experimental/v2rayapi/stats_grpc.pb.go @@ -10,8 +10,8 @@ import ( // 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.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 const ( StatsService_GetStats_FullMethodName = "/experimental.v2rayapi.StatsService/GetStats" @@ -37,8 +37,9 @@ func NewStatsServiceClient(cc grpc.ClientConnInterface) StatsServiceClient { } func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetStatsResponse) - err := c.cc.Invoke(ctx, StatsService_GetStats_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, StatsService_GetStats_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -46,8 +47,9 @@ func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, } func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(QueryStatsResponse) - err := c.cc.Invoke(ctx, StatsService_QueryStats_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, StatsService_QueryStats_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -55,8 +57,9 @@ func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsReque } func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SysStatsResponse) - err := c.cc.Invoke(ctx, StatsService_GetSysStats_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, StatsService_GetSysStats_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -65,7 +68,7 @@ func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsReques // StatsServiceServer is the server API for StatsService service. // All implementations must embed UnimplementedStatsServiceServer -// for forward compatibility +// for forward compatibility. type StatsServiceServer interface { GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) @@ -73,7 +76,11 @@ type StatsServiceServer interface { mustEmbedUnimplementedStatsServiceServer() } -// UnimplementedStatsServiceServer must be embedded to have forward compatible implementations. +// UnimplementedStatsServiceServer 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 UnimplementedStatsServiceServer struct{} func (UnimplementedStatsServiceServer) GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) { @@ -88,6 +95,7 @@ func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsReq return nil, status.Errorf(codes.Unimplemented, "method GetSysStats not implemented") } func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {} +func (UnimplementedStatsServiceServer) testEmbeddedByValue() {} // UnsafeStatsServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to StatsServiceServer will @@ -97,6 +105,13 @@ type UnsafeStatsServiceServer interface { } func RegisterStatsServiceServer(s grpc.ServiceRegistrar, srv StatsServiceServer) { + // If the following call pancis, 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. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&StatsService_ServiceDesc, srv) } diff --git a/go.mod b/go.mod index 9b2a08a631..dda9d21eed 100644 --- a/go.mod +++ b/go.mod @@ -49,8 +49,8 @@ require ( golang.org/x/net v0.35.0 golang.org/x/sys v0.32.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 - google.golang.org/grpc v1.63.2 - google.golang.org/protobuf v1.35.1 + google.golang.org/grpc v1.72.0 + google.golang.org/protobuf v1.36.6 howett.net/plist v1.0.1 ) @@ -128,7 +128,7 @@ require ( golang.org/x/tools v0.29.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-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // 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 664a14b0fc..0f3a45c4f6 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,10 @@ 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-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= +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/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -63,6 +67,8 @@ 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/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= +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.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -232,6 +238,18 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +go.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.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 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= @@ -290,12 +308,12 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvY golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= 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-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +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 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/transport/v2raygrpc/client.go b/transport/v2raygrpc/client.go index af922b45c2..e649aa8f26 100644 --- a/transport/v2raygrpc/client.go +++ b/transport/v2raygrpc/client.go @@ -62,6 +62,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt dialOptions = append(dialOptions, grpc.WithContextDialer(func(ctx context.Context, server string) (net.Conn, error) { return dialer.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(server)) })) + //nolint:staticcheck dialOptions = append(dialOptions, grpc.WithReturnConnectionError()) return &Client{ ctx: ctx, @@ -84,7 +85,6 @@ func (c *Client) connect() (*grpc.ClientConn, error) { return conn, nil } //nolint:staticcheck - //goland:noinspection GoDeprecation conn, err := grpc.DialContext(c.ctx, c.serverAddr, c.dialOptions...) if err != nil { return nil, err diff --git a/transport/v2raygrpc/conn.go b/transport/v2raygrpc/conn.go index 0a0a627f73..c29da4f98e 100644 --- a/transport/v2raygrpc/conn.go +++ b/transport/v2raygrpc/conn.go @@ -18,6 +18,7 @@ type GRPCConn struct { } func NewGRPCConn(service GunService) *GRPCConn { + //nolint:staticcheck if client, isClient := service.(GunService_TunClient); isClient { service = &clientConnWrapper{client} } diff --git a/transport/v2raygrpc/custom_name.go b/transport/v2raygrpc/custom_name.go index 1fe7d8cc4f..ce970dc6ba 100644 --- a/transport/v2raygrpc/custom_name.go +++ b/transport/v2raygrpc/custom_name.go @@ -34,7 +34,7 @@ func (c *gunServiceClient) TunCustomName(ctx context.Context, name string, opts if err != nil { return nil, err } - x := &gunServiceTunClient{stream} + x := &grpc.GenericClientStream[Hunk, Hunk]{ClientStream: stream} return x, nil } diff --git a/transport/v2raygrpc/stream.pb.go b/transport/v2raygrpc/stream.pb.go index 26c091e1fa..9576c7397b 100644 --- a/transport/v2raygrpc/stream.pb.go +++ b/transport/v2raygrpc/stream.pb.go @@ -3,6 +3,7 @@ package v2raygrpc import ( reflect "reflect" sync "sync" + unsafe "unsafe" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -16,20 +17,17 @@ const ( ) type Hunk struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` unknownFields protoimpl.UnknownFields - - Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Hunk) Reset() { *x = Hunk{} - if protoimpl.UnsafeEnabled { - mi := &file_transport_v2raygrpc_stream_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_transport_v2raygrpc_stream_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Hunk) String() string { @@ -40,7 +38,7 @@ func (*Hunk) ProtoMessage() {} func (x *Hunk) ProtoReflect() protoreflect.Message { mi := &file_transport_v2raygrpc_stream_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -64,38 +62,30 @@ func (x *Hunk) GetData() []byte { var File_transport_v2raygrpc_stream_proto protoreflect.FileDescriptor -var file_transport_v2raygrpc_stream_proto_rawDesc = []byte{ - 0x0a, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x76, 0x32, 0x72, 0x61, - 0x79, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x12, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x76, 0x32, - 0x72, 0x61, 0x79, 0x67, 0x72, 0x70, 0x63, 0x22, 0x1a, 0x0a, 0x04, 0x48, 0x75, 0x6e, 0x6b, 0x12, - 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x32, 0x4d, 0x0a, 0x0a, 0x47, 0x75, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x3f, 0x0a, 0x03, 0x54, 0x75, 0x6e, 0x12, 0x19, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x48, - 0x75, 0x6e, 0x6b, 0x1a, 0x19, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x76, 0x32, 0x72, 0x61, 0x79, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x75, 0x6e, 0x6b, 0x28, 0x01, - 0x30, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x73, 0x61, 0x67, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x69, 0x6e, 0x67, 0x2d, 0x62, - 0x6f, 0x78, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x76, 0x32, 0x72, - 0x61, 0x79, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_transport_v2raygrpc_stream_proto_rawDesc = "" + + "\n" + + " transport/v2raygrpc/stream.proto\x12\x13transport.v2raygrpc\"\x1a\n" + + "\x04Hunk\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data2M\n" + + "\n" + + "GunService\x12?\n" + + "\x03Tun\x12\x19.transport.v2raygrpc.Hunk\x1a\x19.transport.v2raygrpc.Hunk(\x010\x01B2Z0github.com/sagernet/sing-box/transport/v2raygrpcb\x06proto3" var ( file_transport_v2raygrpc_stream_proto_rawDescOnce sync.Once - file_transport_v2raygrpc_stream_proto_rawDescData = file_transport_v2raygrpc_stream_proto_rawDesc + file_transport_v2raygrpc_stream_proto_rawDescData []byte ) func file_transport_v2raygrpc_stream_proto_rawDescGZIP() []byte { file_transport_v2raygrpc_stream_proto_rawDescOnce.Do(func() { - file_transport_v2raygrpc_stream_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_v2raygrpc_stream_proto_rawDescData) + file_transport_v2raygrpc_stream_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_v2raygrpc_stream_proto_rawDesc), len(file_transport_v2raygrpc_stream_proto_rawDesc))) }) return file_transport_v2raygrpc_stream_proto_rawDescData } var ( file_transport_v2raygrpc_stream_proto_msgTypes = make([]protoimpl.MessageInfo, 1) - file_transport_v2raygrpc_stream_proto_goTypes = []interface{}{ + file_transport_v2raygrpc_stream_proto_goTypes = []any{ (*Hunk)(nil), // 0: transport.v2raygrpc.Hunk } ) @@ -115,25 +105,11 @@ func file_transport_v2raygrpc_stream_proto_init() { if File_transport_v2raygrpc_stream_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_transport_v2raygrpc_stream_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Hunk); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_transport_v2raygrpc_stream_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_v2raygrpc_stream_proto_rawDesc), len(file_transport_v2raygrpc_stream_proto_rawDesc)), NumEnums: 0, NumMessages: 1, NumExtensions: 0, @@ -144,7 +120,6 @@ func file_transport_v2raygrpc_stream_proto_init() { MessageInfos: file_transport_v2raygrpc_stream_proto_msgTypes, }.Build() File_transport_v2raygrpc_stream_proto = out.File - file_transport_v2raygrpc_stream_proto_rawDesc = nil file_transport_v2raygrpc_stream_proto_goTypes = nil file_transport_v2raygrpc_stream_proto_depIdxs = nil } diff --git a/transport/v2raygrpc/stream_grpc.pb.go b/transport/v2raygrpc/stream_grpc.pb.go index ea634849d4..d602ec452b 100644 --- a/transport/v2raygrpc/stream_grpc.pb.go +++ b/transport/v2raygrpc/stream_grpc.pb.go @@ -10,8 +10,8 @@ import ( // 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.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 const ( GunService_Tun_FullMethodName = "/transport.v2raygrpc.GunService/Tun" @@ -21,7 +21,7 @@ const ( // // 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 GunServiceClient interface { - Tun(ctx context.Context, opts ...grpc.CallOption) (GunService_TunClient, error) + Tun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Hunk, Hunk], error) } type gunServiceClient struct { @@ -32,52 +32,39 @@ func NewGunServiceClient(cc grpc.ClientConnInterface) GunServiceClient { return &gunServiceClient{cc} } -func (c *gunServiceClient) Tun(ctx context.Context, opts ...grpc.CallOption) (GunService_TunClient, error) { - stream, err := c.cc.NewStream(ctx, &GunService_ServiceDesc.Streams[0], GunService_Tun_FullMethodName, opts...) +func (c *gunServiceClient) Tun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Hunk, Hunk], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &GunService_ServiceDesc.Streams[0], GunService_Tun_FullMethodName, cOpts...) if err != nil { return nil, err } - x := &gunServiceTunClient{stream} + x := &grpc.GenericClientStream[Hunk, Hunk]{ClientStream: stream} return x, nil } -type GunService_TunClient interface { - Send(*Hunk) error - Recv() (*Hunk, error) - grpc.ClientStream -} - -type gunServiceTunClient struct { - grpc.ClientStream -} - -func (x *gunServiceTunClient) Send(m *Hunk) error { - return x.ClientStream.SendMsg(m) -} - -func (x *gunServiceTunClient) Recv() (*Hunk, error) { - m := new(Hunk) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type GunService_TunClient = grpc.BidiStreamingClient[Hunk, Hunk] // GunServiceServer is the server API for GunService service. // All implementations must embed UnimplementedGunServiceServer -// for forward compatibility +// for forward compatibility. type GunServiceServer interface { - Tun(GunService_TunServer) error + Tun(grpc.BidiStreamingServer[Hunk, Hunk]) error mustEmbedUnimplementedGunServiceServer() } -// UnimplementedGunServiceServer must be embedded to have forward compatible implementations. +// UnimplementedGunServiceServer 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 UnimplementedGunServiceServer struct{} -func (UnimplementedGunServiceServer) Tun(GunService_TunServer) error { +func (UnimplementedGunServiceServer) Tun(grpc.BidiStreamingServer[Hunk, Hunk]) error { return status.Errorf(codes.Unimplemented, "method Tun not implemented") } func (UnimplementedGunServiceServer) mustEmbedUnimplementedGunServiceServer() {} +func (UnimplementedGunServiceServer) testEmbeddedByValue() {} // UnsafeGunServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to GunServiceServer will @@ -87,34 +74,22 @@ type UnsafeGunServiceServer interface { } func RegisterGunServiceServer(s grpc.ServiceRegistrar, srv GunServiceServer) { + // If the following call pancis, 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. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&GunService_ServiceDesc, srv) } func _GunService_Tun_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(GunServiceServer).Tun(&gunServiceTunServer{stream}) -} - -type GunService_TunServer interface { - Send(*Hunk) error - Recv() (*Hunk, error) - grpc.ServerStream -} - -type gunServiceTunServer struct { - grpc.ServerStream + return srv.(GunServiceServer).Tun(&grpc.GenericServerStream[Hunk, Hunk]{ServerStream: stream}) } -func (x *gunServiceTunServer) Send(m *Hunk) error { - return x.ServerStream.SendMsg(m) -} - -func (x *gunServiceTunServer) Recv() (*Hunk, error) { - m := new(Hunk) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type GunService_TunServer = grpc.BidiStreamingServer[Hunk, Hunk] // GunService_ServiceDesc is the grpc.ServiceDesc for GunService service. // It's only intended for direct use with grpc.RegisterService, From b3866bcea0bb8180c0c6c054acf09408eb1d7077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 5 May 2025 13:35:04 +0800 Subject: [PATCH 0433/1272] Update certmagic and providers --- common/tls/acme.go | 2 +- go.mod | 22 ++++++++++++---------- go.sum | 42 ++++++++++++++++++++++++------------------ 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/common/tls/acme.go b/common/tls/acme.go index 52172c9cf6..989df7f050 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -16,7 +16,7 @@ import ( "github.com/caddyserver/certmagic" "github.com/libdns/alidns" "github.com/libdns/cloudflare" - "github.com/mholt/acmez/acme" + "github.com/mholt/acmez/v3/acme" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) diff --git a/go.mod b/go.mod index dda9d21eed..a37710ca6e 100644 --- a/go.mod +++ b/go.mod @@ -4,19 +4,19 @@ go 1.23.1 require ( github.com/anytls/sing-anytls v0.0.8 - github.com/caddyserver/certmagic v0.20.0 + github.com/caddyserver/certmagic v0.23.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.3.2 github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 - github.com/libdns/alidns v1.0.3 - github.com/libdns/cloudflare v0.1.1 + github.com/libdns/alidns v1.0.4-libdns.v1.beta1 + github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 github.com/metacubex/utls v1.7.0-alpha.3 - github.com/mholt/acmez v1.2.0 + github.com/mholt/acmez/v3 v3.1.2 github.com/miekg/dns v1.1.63 github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 @@ -45,8 +45,8 @@ require ( go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.37.0 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 - golang.org/x/mod v0.22.0 - golang.org/x/net v0.35.0 + golang.org/x/mod v0.24.0 + golang.org/x/net v0.37.0 golang.org/x/sys v0.32.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.72.0 @@ -63,6 +63,7 @@ require ( 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/coder/websocket v1.8.12 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect @@ -92,9 +93,9 @@ 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.5 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect - github.com/libdns/libdns v0.2.2 // indirect + github.com/libdns/libdns v1.0.0-beta.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 @@ -118,14 +119,15 @@ require ( 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/x448/float16 v0.8.4 // indirect - github.com/zeebo/blake3 v0.2.3 // 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.13.0 // indirect golang.org/x/term v0.31.0 // indirect golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.31.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-20250218202821-56aae31c358a // indirect diff --git a/go.sum b/go.sum index 0f3a45c4f6..5d079d3320 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,10 @@ github.com/anytls/sing-anytls v0.0.8 h1:1u/fnH1HoeeMV5mX7/eUOjLBvPdkd1UJRmXiRi6V 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.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= -github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= +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= @@ -101,18 +103,21 @@ github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUF github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= 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.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/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.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= +github.com/libdns/alidns v1.0.4-libdns.v1.beta1 h1:ods22gD4PcT0g4qRX77ucykjz7Rppnkz3vQoxDbbKTM= +github.com/libdns/alidns v1.0.4-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g= github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= +github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6 h1:0dlpPjNr8TaYZbkpwCiee4udBNrYrWG8EZPYEbjHEn8= +github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6/go.mod h1:Aq4IXdjalB6mD0ELvKqJiIGim8zSC6mlIshRPMOAb5w= 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/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= +github.com/libdns/libdns v1.0.0-beta.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= @@ -127,8 +132,8 @@ github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVr github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA= github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= -github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= -github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= +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.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= @@ -234,8 +239,8 @@ 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/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/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= @@ -256,6 +261,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/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= 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= @@ -267,12 +274,12 @@ golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 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.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= @@ -282,7 +289,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.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.5.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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= @@ -297,8 +303,8 @@ golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 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.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= 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 d33614d6a0a780556f1182805964c6daccdffc3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 5 May 2025 13:43:15 +0800 Subject: [PATCH 0434/1272] Update minor dependencies --- go.mod | 34 +++++++++++++------------- go.sum | 75 +++++++++++++++++++++++++++------------------------------- 2 files changed, 52 insertions(+), 57 deletions(-) diff --git a/go.mod b/go.mod index a37710ca6e..ebcc9f47b7 100644 --- a/go.mod +++ b/go.mod @@ -5,20 +5,20 @@ go 1.23.1 require ( github.com/anytls/sing-anytls v0.0.8 github.com/caddyserver/certmagic v0.23.0 - github.com/cloudflare/circl v1.3.7 + github.com/cloudflare/circl v1.6.1 github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.3.2 - github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 + github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f github.com/libdns/alidns v1.0.4-libdns.v1.beta1 github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 github.com/metacubex/utls v1.7.0-alpha.3 github.com/mholt/acmez/v3 v3.1.2 - github.com/miekg/dns v1.1.63 - github.com/oschwald/maxminddb-golang v1.12.0 + github.com/miekg/dns v1.1.66 + 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 github.com/sagernet/cors v1.2.1 @@ -38,17 +38,17 @@ require ( github.com/sagernet/tailscale v1.80.3-mod.5 github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 - github.com/vishvananda/netns v0.0.4 + 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.37.0 - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 + golang.org/x/crypto v0.38.0 + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 golang.org/x/mod v0.24.0 - golang.org/x/net v0.37.0 - golang.org/x/sys v0.32.0 - golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 + golang.org/x/net v0.40.0 + golang.org/x/sys v0.33.0 + golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/grpc v1.72.0 google.golang.org/protobuf v1.36.6 howett.net/plist v1.0.1 @@ -99,7 +99,7 @@ require ( 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.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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -108,7 +108,7 @@ require ( 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 - github.com/spf13/pflag v1.0.5 // indirect + 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 @@ -123,11 +123,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.13.0 // indirect - golang.org/x/term v0.31.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.31.0 // indirect + golang.org/x/tools v0.33.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-20250218202821-56aae31c358a // indirect diff --git a/go.sum b/go.sum index 5d079d3320..d8fd1fe871 100644 --- a/go.sum +++ b/go.sum @@ -20,13 +20,13 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 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.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/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/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.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -96,8 +96,8 @@ github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vy github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= 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-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/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= @@ -107,15 +107,10 @@ github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2 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.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= -github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= github.com/libdns/alidns v1.0.4-libdns.v1.beta1 h1:ods22gD4PcT0g4qRX77ucykjz7Rppnkz3vQoxDbbKTM= github.com/libdns/alidns v1.0.4-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g= -github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= -github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6 h1:0dlpPjNr8TaYZbkpwCiee4udBNrYrWG8EZPYEbjHEn8= github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6/go.mod h1:Aq4IXdjalB6mD0ELvKqJiIGim8zSC6mlIshRPMOAb5w= -github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= @@ -126,22 +121,22 @@ github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0 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.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= -github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= +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/metacubex/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA= github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= 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.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= -github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= +github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= 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/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= -github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= +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= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -197,10 +192,10 @@ github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKH 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/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/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/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= @@ -233,8 +228,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/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= @@ -268,21 +263,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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 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.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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= @@ -291,27 +286,27 @@ 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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 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.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= 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= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= +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-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= From f990630cccf6ad85e2990227a588431a950de66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 7 May 2025 13:27:53 +0800 Subject: [PATCH 0435/1272] Fix set edns0 client subnet --- dns/client.go | 2 +- dns/extension_edns0_subnet.go | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/dns/client.go b/dns/client.go index 5d7f109d66..0c5490f923 100644 --- a/dns/client.go +++ b/dns/client.go @@ -105,7 +105,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m } question := message.Question[0] if options.ClientSubnet.IsValid() { - message = SetClientSubnet(message, options.ClientSubnet, true) + message = SetClientSubnet(message, options.ClientSubnet) } isSimpleRequest := len(message.Question) == 1 && len(message.Ns) == 0 && diff --git a/dns/extension_edns0_subnet.go b/dns/extension_edns0_subnet.go index 1c4033d36e..e804fb6cd1 100644 --- a/dns/extension_edns0_subnet.go +++ b/dns/extension_edns0_subnet.go @@ -6,7 +6,11 @@ import ( "github.com/miekg/dns" ) -func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg { +func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg { + return setClientSubnet(message, clientSubnet, true) +} + +func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg { var ( optRecord *dns.OPT subnetOption *dns.EDNS0_SUBNET @@ -19,9 +23,6 @@ findExists: var isEDNS0Subnet bool subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET) if isEDNS0Subnet { - if !override { - return message - } break findExists } } @@ -37,14 +38,14 @@ findExists: }, } message.Extra = append(message.Extra, optRecord) - } else { - message = message.Copy() + } else if clone { + return setClientSubnet(message.Copy(), clientSubnet, false) } if subnetOption == nil { subnetOption = new(dns.EDNS0_SUBNET) + subnetOption.Code = dns.EDNS0SUBNET optRecord.Option = append(optRecord.Option, subnetOption) } - subnetOption.Code = dns.EDNS0SUBNET if clientSubnet.Addr().Is4() { subnetOption.Family = 1 } else { From 3b480de38a8305d006f81580aa98a966c0141eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 12 May 2025 12:03:34 +0800 Subject: [PATCH 0436/1272] Add TLS record fragment support --- adapter/inbound.go | 1 + common/tlsfragment/conn.go | 40 +++++++++++++++++----- common/tlsfragment/conn_test.go | 32 +++++++++++++++++ docs/configuration/route/rule_action.md | 29 +++++++++++++--- docs/configuration/route/rule_action.zh.md | 23 +++++++++++-- option/rule_action.go | 4 +++ route/conn.go | 12 ++----- route/rule/rule_action.go | 28 +++++++++------ 8 files changed, 133 insertions(+), 36 deletions(-) create mode 100644 common/tlsfragment/conn_test.go diff --git a/adapter/inbound.go b/adapter/inbound.go index f45ddb24c1..0de4c79905 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -74,6 +74,7 @@ type InboundContext struct { UDPTimeout time.Duration TLSFragment bool TLSFragmentFallbackDelay time.Duration + TLSRecordFragment bool NetworkStrategy *C.NetworkStrategy NetworkType []C.InterfaceType diff --git a/common/tlsfragment/conn.go b/common/tlsfragment/conn.go index 6f2a3dad6a..c224b689a1 100644 --- a/common/tlsfragment/conn.go +++ b/common/tlsfragment/conn.go @@ -1,7 +1,9 @@ package tf import ( + "bytes" "context" + "encoding/binary" "math/rand" "net" "strings" @@ -17,17 +19,19 @@ type Conn struct { tcpConn *net.TCPConn ctx context.Context firstPacketWritten bool + splitRecord bool fallbackDelay time.Duration } -func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) { +func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn { tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn) return &Conn{ Conn: conn, tcpConn: tcpConn, ctx: ctx, + splitRecord: splitRecord, fallbackDelay: fallbackDelay, - }, nil + } } func (c *Conn) Write(b []byte) (n int, err error) { @@ -37,10 +41,12 @@ func (c *Conn) Write(b []byte) (n int, err error) { }() serverName := indexTLSServerName(b) if serverName != nil { - if c.tcpConn != nil { - err = c.tcpConn.SetNoDelay(true) - if err != nil { - return + if !c.splitRecord { + if c.tcpConn != nil { + err = c.tcpConn.SetNoDelay(true) + if err != nil { + return + } } } splits := strings.Split(serverName.ServerName, ".") @@ -61,16 +67,25 @@ func (c *Conn) Write(b []byte) (n int, err error) { currentIndex++ } } + var buffer bytes.Buffer for i := 0; i <= len(splitIndexes); i++ { var payload []byte if i == 0 { payload = b[:splitIndexes[i]] + if c.splitRecord { + payload = payload[recordLayerHeaderLen:] + } } else if i == len(splitIndexes) { payload = b[splitIndexes[i-1]:] } else { payload = b[splitIndexes[i-1]:splitIndexes[i]] } - if c.tcpConn != nil && i != len(splitIndexes) { + if c.splitRecord { + payloadLen := uint16(len(payload)) + buffer.Write(b[:3]) + binary.Write(&buffer, binary.BigEndian, payloadLen) + buffer.Write(payload) + } else if c.tcpConn != nil && i != len(splitIndexes) { err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay) if err != nil { return @@ -82,11 +97,18 @@ func (c *Conn) Write(b []byte) (n int, err error) { } } } - if c.tcpConn != nil { - err = c.tcpConn.SetNoDelay(false) + if c.splitRecord { + _, err = c.tcpConn.Write(buffer.Bytes()) if err != nil { return } + } else { + if c.tcpConn != nil { + err = c.tcpConn.SetNoDelay(false) + if err != nil { + return + } + } } return len(b), nil } diff --git a/common/tlsfragment/conn_test.go b/common/tlsfragment/conn_test.go new file mode 100644 index 0000000000..21e2fcb2ae --- /dev/null +++ b/common/tlsfragment/conn_test.go @@ -0,0 +1,32 @@ +package tf_test + +import ( + "context" + "crypto/tls" + "net" + "testing" + + tf "github.com/sagernet/sing-box/common/tlsfragment" + + "github.com/stretchr/testify/require" +) + +func TestTLSFragment(t *testing.T) { + t.Parallel() + tcpConn, err := net.Dial("tcp", "1.1.1.1:443") + require.NoError(t, err) + tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{ + ServerName: "www.cloudflare.com", + }) + require.NoError(t, tlsConn.Handshake()) +} + +func TestTLSRecordFragment(t *testing.T) { + t.Parallel() + tcpConn, err := net.Dial("tcp", "1.1.1.1:443") + require.NoError(t, err) + tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{ + ServerName: "www.cloudflare.com", + }) + require.NoError(t, tlsConn.Handshake()) +} diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 0f04b1659d..1bba954253 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -6,6 +6,7 @@ icon: material/new-box :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-plus: [resolve.disable_cache](#disable_cache) :material-plus: [resolve.rewrite_ttl](#rewrite_ttl) :material-plus: [resolve.client_subnet](#client_subnet) @@ -91,7 +92,8 @@ Not available when `method` is set to drop. "udp_connect": false, "udp_timeout": "", "tls_fragment": false, - "tls_fragment_fallback_delay": "" + "tls_fragment_fallback_delay": "", + "tls_record_fragment": "" } ``` @@ -164,13 +166,19 @@ If no protocol is sniffed, the following ports will be recognized as protocols b Fragment TLS handshakes to bypass firewalls. -This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship. +This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, +and should not be used to circumvent real censorship. -Since it is not designed for performance, it should not be applied to all connections, but only to server names that are known to be blocked. +Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked. -On Linux, Apple platforms, (administrator privileges required) Windows, the wait time can be automatically detected, otherwise it will fall back to waiting for a fixed time specified by `tls_fragment_fallback_delay`. +On Linux, Apple platforms, (administrator privileges required) Windows, +the wait time can be automatically detected, otherwise it will fall back to +waiting for a fixed time specified by `tls_fragment_fallback_delay`. -In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, because the target is considered to be local or behind a transparent proxy. +In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, +because the target is considered to be local or behind a transparent proxy. + +Conflict with `tls_record_fragment`. #### tls_fragment_fallback_delay @@ -180,6 +188,17 @@ The fallback value used when TLS segmentation cannot automatically determine the `500ms` is used by default. +#### tls_record_fragment + +!!! question "Since sing-box 1.12.0" + +Fragment TLS handshake into multiple TLS records to bypass firewalls. + +This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, +and should not be used to circumvent real censorship. + +Conflict with `tls_fragment`. + ### sniff ```json diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index f0c9161029..e1626963fb 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -5,7 +5,11 @@ icon: material/new-box !!! 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_fragment_fallback_delay](#tls_fragment_fallback_delay) + :material-plus: [tls_record_fragment](#tls_record_fragment) + :material-plus: [resolve.disable_cache](#disable_cache) + :material-plus: [resolve.rewrite_ttl](#rewrite_ttl) + :material-plus: [resolve.client_subnet](#client_subnet) ## 最终动作 @@ -159,12 +163,15 @@ UDP 连接超时时间。 此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。 -由于它不是为性能设计的,不应被应用于所有连接,而仅应用于已知被阻止的服务器名称。 +由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。 -在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。 +在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。 +若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。 此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。 +与 `tls_record_fragment` 冲突。 + #### tls_fragment_fallback_delay !!! question "自 sing-box 1.12.0 起" @@ -173,6 +180,16 @@ UDP 连接超时时间。 默认使用 `500ms`。 +#### tls_record_fragment + +!!! question "自 sing-box 1.12.0 起" + +通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。 + +此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。 + +与 `tls_fragment` 冲突。 + ### sniff ```json diff --git a/option/rule_action.go b/option/rule_action.go index 7c05dce68a..914edb846b 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -158,6 +158,7 @@ type RawRouteOptionsActionOptions struct { TLSFragment bool `json:"tls_fragment,omitempty"` TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"` + TLSRecordFragment bool `json:"tls_record_fragment,omitempty"` } type RouteOptionsActionOptions RawRouteOptionsActionOptions @@ -170,6 +171,9 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { if *r == (RouteOptionsActionOptions{}) { return E.New("empty route option action") } + if r.TLSFragment && r.TLSRecordFragment { + return E.New("`tls_fragment` and `tls_record_fragment` are mutually exclusive") + } return nil } diff --git a/route/conn.go b/route/conn.go index f46283ad9b..d5f914b824 100644 --- a/route/conn.go +++ b/route/conn.go @@ -95,15 +95,9 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co if fallbackDelay == 0 { fallbackDelay = C.TLSFragmentFallbackDelay } - var newConn *tf.Conn - newConn, err = tf.NewConn(remoteConn, ctx, fallbackDelay) - if err != nil { - conn.Close() - remoteConn.Close() - m.logger.ErrorContext(ctx, err) - return - } - remoteConn = newConn + remoteConn = tf.NewConn(remoteConn, ctx, false, fallbackDelay) + } else if metadata.TLSRecordFragment { + remoteConn = tf.NewConn(remoteConn, ctx, true, 0) } m.access.Lock() element := m.connections.PushBack(conn) diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index af8c33e7ff..ce50dad9fd 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -41,6 +41,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti UDPConnect: action.RouteOptions.UDPConnect, TLSFragment: action.RouteOptions.TLSFragment, TLSFragmentFallbackDelay: time.Duration(action.RouteOptions.TLSFragmentFallbackDelay), + TLSRecordFragment: action.RouteOptions.TLSRecordFragment, }, }, nil case C.RuleActionTypeRouteOptions: @@ -54,6 +55,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout), TLSFragment: action.RouteOptionsOptions.TLSFragment, TLSFragmentFallbackDelay: time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay), + TLSRecordFragment: action.RouteOptionsOptions.TLSRecordFragment, }, nil case C.RuleActionTypeDirect: directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false) @@ -153,15 +155,7 @@ func (r *RuleActionRoute) Type() string { func (r *RuleActionRoute) String() string { var descriptions []string descriptions = append(descriptions, r.Outbound) - if r.UDPDisableDomainUnmapping { - descriptions = append(descriptions, "udp-disable-domain-unmapping") - } - if r.UDPConnect { - descriptions = append(descriptions, "udp-connect") - } - if r.TLSFragment { - descriptions = append(descriptions, "tls-fragment") - } + descriptions = append(descriptions, r.Descriptions()...) return F.ToString("route(", strings.Join(descriptions, ","), ")") } @@ -177,6 +171,7 @@ type RuleActionRouteOptions struct { UDPTimeout time.Duration TLSFragment bool TLSFragmentFallbackDelay time.Duration + TLSRecordFragment bool } func (r *RuleActionRouteOptions) Type() string { @@ -184,6 +179,10 @@ func (r *RuleActionRouteOptions) Type() string { } func (r *RuleActionRouteOptions) String() string { + return F.ToString("route-options(", strings.Join(r.Descriptions(), ","), ")") +} + +func (r *RuleActionRouteOptions) Descriptions() []string { var descriptions []string if r.OverrideAddress.IsValid() { descriptions = append(descriptions, F.ToString("override-address=", r.OverrideAddress.AddrString())) @@ -212,7 +211,16 @@ func (r *RuleActionRouteOptions) String() string { if r.UDPTimeout > 0 { descriptions = append(descriptions, "udp-timeout") } - return F.ToString("route-options(", strings.Join(descriptions, ","), ")") + if r.TLSFragment { + descriptions = append(descriptions, "tls-fragment") + } + if r.TLSFragmentFallbackDelay > 0 { + descriptions = append(descriptions, F.ToString("tls-fragment-fallback-delay=", r.TLSFragmentFallbackDelay.String())) + } + if r.TLSRecordFragment { + descriptions = append(descriptions, "tls-record-fragment") + } + return descriptions } type RuleActionDNSRoute struct { From ed5b2f29971496a3b92e4bdb11151bcf2c6fe624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 12 May 2025 18:06:58 +0800 Subject: [PATCH 0437/1272] Add missing `accept_routes` option for Tailscale --- docs/configuration/endpoint/tailscale.md | 5 +++++ option/tailscale.go | 1 + protocol/tailscale/endpoint.go | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/docs/configuration/endpoint/tailscale.md b/docs/configuration/endpoint/tailscale.md index 298b68cebf..612a86e636 100644 --- a/docs/configuration/endpoint/tailscale.md +++ b/docs/configuration/endpoint/tailscale.md @@ -15,6 +15,7 @@ icon: material/new-box "control_url": "", "ephemeral": false, "hostname": "", + "accept_routes": false, "exit_node": "", "exit_node_allow_lan_access": false, "advertise_routes": [], @@ -62,6 +63,10 @@ System hostname is used by default. Example: `localhost` +#### accept_routes + +Indicates whether the node should accept routes advertised by other nodes. + #### exit_node The exit node name or IP address to use. diff --git a/option/tailscale.go b/option/tailscale.go index 30579fc77e..7b901770b9 100644 --- a/option/tailscale.go +++ b/option/tailscale.go @@ -11,6 +11,7 @@ type TailscaleEndpointOptions struct { 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"` diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 26aa6ebd05..92f405629f 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -72,6 +72,7 @@ type Endpoint struct { filter *atomic.Pointer[filter.Filter] onReconfig wgengine.ReconfigListener + acceptRoutes bool exitNode string exitNodeAllowLANAccess bool advertiseRoutes []netip.Prefix @@ -170,6 +171,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL network: service.FromContext[adapter.NetworkManager](ctx), platformInterface: service.FromContext[platform.Interface](ctx), server: server, + acceptRoutes: options.AcceptRoutes, exitNode: options.ExitNode, exitNodeAllowLANAccess: options.ExitNodeAllowLANAccess, advertiseRoutes: options.AdvertiseRoutes, @@ -226,6 +228,10 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { localBackend := t.server.ExportLocalBackend() perfs := &ipn.MaskedPrefs{ + Prefs: ipn.Prefs{ + RouteAll: t.acceptRoutes, + }, + RouteAllSet: true, ExitNodeIPSet: true, AdvertiseRoutesSet: true, } From 7cee76f9a60541fec894bd129d96bfdbb7ba13b4 Mon Sep 17 00:00:00 2001 From: Restia-Ashbell <107416976+Restia-Ashbell@users.noreply.github.com> Date: Tue, 13 May 2025 11:56:48 +0800 Subject: [PATCH 0438/1272] Fix TLS record fragment --- common/tlsfragment/conn.go | 2 +- route/route.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/common/tlsfragment/conn.go b/common/tlsfragment/conn.go index c224b689a1..50baa6baae 100644 --- a/common/tlsfragment/conn.go +++ b/common/tlsfragment/conn.go @@ -98,7 +98,7 @@ func (c *Conn) Write(b []byte) (n int, err error) { } } if c.splitRecord { - _, err = c.tcpConn.Write(buffer.Bytes()) + _, err = c.Conn.Write(buffer.Bytes()) if err != nil { return } diff --git a/route/route.go b/route/route.go index 85c4f88858..08441bf3b9 100644 --- a/route/route.go +++ b/route/route.go @@ -450,6 +450,9 @@ match: metadata.TLSFragment = true metadata.TLSFragmentFallbackDelay = routeOptions.TLSFragmentFallbackDelay } + if routeOptions.TLSRecordFragment { + metadata.TLSRecordFragment = true + } } switch action := currentRule.Action().(type) { case *R.RuleActionSniff: From 42064fe7ece2bca14eb45154414463bcbcfb5f0d Mon Sep 17 00:00:00 2001 From: PuerNya Date: Fri, 16 May 2025 20:26:13 +0800 Subject: [PATCH 0439/1272] documentation: Fix description of reject DNS action behavior --- docs/configuration/dns/rule_action.md | 2 +- docs/configuration/dns/rule_action.zh.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index 62c81c326f..db9033f8b7 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -81,7 +81,7 @@ Will overrides `dns.client_subnet`. #### method -- `default`: Reply with NXDOMAIN. +- `default`: Reply with REFUSED. - `drop`: Drop the request. `default` will be used by default. diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index c2921ace26..9e59c6bd2b 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -81,7 +81,7 @@ icon: material/new-box #### method -- `default`: 返回 NXDOMAIN。 +- `default`: 返回 REFUSED。 - `drop`: 丢弃请求。 默认使用 `defualt`。 From d5432b4c2728be30b58cb6014a25516c0b45f8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 16 May 2025 20:29:21 +0800 Subject: [PATCH 0440/1272] prevent creation of bind and mark controls on unsupported platforms --- common/dialer/default.go | 6 ++++++ route/network.go | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/common/dialer/default.go b/common/dialer/default.go index c4f6c16b39..4b9cf71c65 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -66,11 +66,17 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial interfaceFinder = control.NewDefaultInterfaceFinder() } if options.BindInterface != "" { + if !(C.IsLinux || C.IsDarwin || C.IsWindows) { + return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows") + } bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) } if options.RoutingMark > 0 { + if !C.IsLinux { + return nil, E.New("`routing_mark` is only supported on Linux") + } dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false)) listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false)) } diff --git a/route/network.go b/route/network.go index 6c2e995f75..090e4c0ded 100644 --- a/route/network.go +++ b/route/network.go @@ -57,6 +57,15 @@ type NetworkManager struct { func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*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") + } else if routeOptions.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) { + return nil, E.New("`default_interface` is only supported on Linux, Windows and macOS") + } else if routeOptions.DefaultMark != 0 && !C.IsLinux { + return nil, E.New("`default_mark` is only supported on linux") + } nm := &NetworkManager{ logger: logger, interfaceFinder: control.NewDefaultInterfaceFinder(), From 886d4273374cf6e9e4d873fe0975cd66e365fa4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=84=9A=E8=80=85?= <11926619+FansChou@users.noreply.github.com> Date: Sat, 17 May 2025 20:14:04 +0800 Subject: [PATCH 0441/1272] release: Fix build tags for android MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 愚者 <11926619+FansChou@users.noreply.github.com> --- cmd/internal/build_libbox/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 8f82ff36a0..7dcc9608b5 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -59,8 +59,8 @@ func init() { 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") - iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack") + sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack") + iosTags = append(iosTags, "with_dhcp", "with_low_memory") memcTags = append(memcTags, "with_tailscale") debugTags = append(debugTags, "debug") } From e3c8c0705ffbc8e3ccc2d88b97d44d0dd5f70f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 18 May 2025 13:45:50 +0800 Subject: [PATCH 0442/1272] Fix tproxy tcp control --- common/listener/listener.go | 3 +++ common/listener/listener_tcp.go | 9 +++++++++ common/listener/listener_udp.go | 9 +++++++++ common/redir/tproxy_linux.go | 14 ++++++++------ common/redir/tproxy_other.go | 2 +- protocol/redirect/tproxy.go | 33 ++++++--------------------------- 6 files changed, 36 insertions(+), 34 deletions(-) diff --git a/common/listener/listener.go b/common/listener/listener.go index 8a4cad348b..7d49d6642d 100644 --- a/common/listener/listener.go +++ b/common/listener/listener.go @@ -32,6 +32,7 @@ type Listener struct { disablePacketOutput bool setSystemProxy bool systemProxySOCKS bool + tproxy bool tcpListener net.Listener systemProxy settings.SystemProxy @@ -54,6 +55,7 @@ type Options struct { DisablePacketOutput bool SetSystemProxy bool SystemProxySOCKS bool + TProxy bool } func New( @@ -71,6 +73,7 @@ func New( disablePacketOutput: options.DisablePacketOutput, setSystemProxy: options.SetSystemProxy, systemProxySOCKS: options.SystemProxySOCKS, + tproxy: options.TProxy, } } diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index b8ecc1dbe5..8b2f7e71de 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -3,9 +3,11 @@ package listener import ( "net" "net/netip" + "syscall" "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/redir" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/control" @@ -51,6 +53,13 @@ func (l *Listener) ListenTCP() (net.Listener, error) { } setMultiPathTCP(&listenConfig) } + if l.tproxy { + listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), false) + }) + }) + } tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) { if l.listenOptions.TCPFastOpen { var tfoConfig tfo.ListenConfig diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go index d189bf13e9..61db69ce21 100644 --- a/common/listener/listener_udp.go +++ b/common/listener/listener_udp.go @@ -5,8 +5,10 @@ import ( "net" "net/netip" "os" + "syscall" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/redir" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" @@ -36,6 +38,13 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) { if !udpFragment { listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment()) } + if l.tproxy { + listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), true) + }) + }) + } udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) }) diff --git a/common/redir/tproxy_linux.go b/common/redir/tproxy_linux.go index ccf940376e..a3ccade08d 100644 --- a/common/redir/tproxy_linux.go +++ b/common/redir/tproxy_linux.go @@ -12,7 +12,7 @@ import ( "golang.org/x/sys/unix" ) -func TProxy(fd uintptr, isIPv6 bool) error { +func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error { err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) if err == nil { err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) @@ -20,11 +20,13 @@ func TProxy(fd uintptr, isIPv6 bool) error { if err == nil && isIPv6 { err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1) } - if err == nil { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) - } - if err == nil && isIPv6 { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1) + if isUDP { + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) + } + if err == nil && isIPv6 { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1) + } } return err } diff --git a/common/redir/tproxy_other.go b/common/redir/tproxy_other.go index 5869a195e8..42e31fee34 100644 --- a/common/redir/tproxy_other.go +++ b/common/redir/tproxy_other.go @@ -9,7 +9,7 @@ import ( "github.com/sagernet/sing/common/control" ) -func TProxy(fd uintptr, isIPv6 bool) error { +func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error { return os.ErrInvalid } diff --git a/protocol/redirect/tproxy.go b/protocol/redirect/tproxy.go index 1b3e8dae12..f0b82bb132 100644 --- a/protocol/redirect/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -4,7 +4,6 @@ import ( "context" "net" "net/netip" - "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -17,7 +16,6 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" - 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/udpnat2" @@ -57,6 +55,7 @@ func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLog Listen: options.ListenOptions, ConnectionHandler: tproxy, OOBPacketHandler: tproxy, + TProxy: true, }) return tproxy, nil } @@ -65,27 +64,7 @@ func (t *TProxy) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } - err := t.listener.Start() - if err != nil { - return err - } - if listener := t.listener.TCPListener(); listener != nil { - err = control.Conn(common.MustCast[syscall.Conn](listener), func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(listener.Addr()).Addr.Is6()) - }) - if err != nil { - return E.Cause(err, "configure tproxy TCP listener") - } - } - if conn := t.listener.UDPConn(); conn != nil { - err = control.Conn(conn, func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(conn.LocalAddr()).Addr.Is6()) - }) - if err != nil { - return E.Cause(err, "configure tproxy UDP listener") - } - } - return nil + return t.listener.Start() } func (t *TProxy) Close() error { @@ -154,10 +133,10 @@ func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socks return err } } - var listener net.ListenConfig - listener.Control = control.Append(listener.Control, control.ReuseAddr()) - listener.Control = control.Append(listener.Control, redir.TProxyWriteBack()) - packetConn, err := w.listener.ListenPacket(listener, w.ctx, "udp", destination.String()) + var listenConfig net.ListenConfig + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + listenConfig.Control = control.Append(listenConfig.Control, redir.TProxyWriteBack()) + packetConn, err := w.listener.ListenPacket(listenConfig, w.ctx, "udp", destination.String()) if err != nil { return err } From 7a1eee78df6bf1b856107bc373d08fc8336956cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 29 Mar 2025 17:24:34 +0800 Subject: [PATCH 0443/1272] Add service component type --- adapter/dns.go | 20 +++++ adapter/fakeip.go | 2 +- adapter/lifecycle.go | 5 ++ adapter/lifecycle_legacy.go | 12 +-- adapter/rule.go | 2 +- adapter/service.go | 25 +++++- adapter/service/adapter.go | 21 +++++ adapter/service/manager.go | 143 ++++++++++++++++++++++++++++++++++ adapter/service/registry.go | 72 +++++++++++++++++ adapter/time.go | 2 +- box.go | 117 ++++++++++++++++++---------- cmd/sing-box/cmd.go | 2 +- common/tls/acme.go | 2 +- common/tls/acme_stub.go | 2 +- common/tls/std_server.go | 4 +- experimental/libbox/config.go | 2 +- include/registry.go | 5 ++ option/dns.go | 1 - option/endpoint.go | 4 +- option/inbound.go | 2 +- option/options.go | 1 + option/service.go | 47 +++++++++++ 22 files changed, 431 insertions(+), 62 deletions(-) create mode 100644 adapter/service/adapter.go create mode 100644 adapter/service/manager.go create mode 100644 adapter/service/registry.go create mode 100644 option/service.go diff --git a/adapter/dns.go b/adapter/dns.go index 942f3566df..73b7760170 100644 --- a/adapter/dns.go +++ b/adapter/dns.go @@ -7,7 +7,9 @@ import ( 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" + "github.com/sagernet/sing/service" "github.com/miekg/dns" ) @@ -38,6 +40,24 @@ type DNSQueryOptions struct { ClientSubnet netip.Prefix } +func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) { + if options == nil { + return &DNSQueryOptions{}, nil + } + transportManager := service.FromContext[DNSTransportManager](ctx) + transport, loaded := transportManager.Transport(options.Server) + if !loaded { + return nil, E.New("domain resolver not found: " + options.Server) + } + return &DNSQueryOptions{ + Transport: transport, + Strategy: C.DomainStrategy(options.Strategy), + DisableCache: options.DisableCache, + RewriteTTL: options.RewriteTTL, + ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), + }, nil +} + type RDRCStore interface { LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) SaveRDRC(transportName string, qName string, qType uint16) error diff --git a/adapter/fakeip.go b/adapter/fakeip.go index 97d1c3c070..0787c14642 100644 --- a/adapter/fakeip.go +++ b/adapter/fakeip.go @@ -7,7 +7,7 @@ import ( ) type FakeIPStore interface { - Service + SimpleLifecycle Contains(address netip.Addr) bool Create(domain string, isIPv6 bool) (netip.Addr, error) Lookup(address netip.Addr) (string, bool) diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go index aff9fadbac..face00b76d 100644 --- a/adapter/lifecycle.go +++ b/adapter/lifecycle.go @@ -2,6 +2,11 @@ package adapter import E "github.com/sagernet/sing/common/exceptions" +type SimpleLifecycle interface { + Start() error + Close() error +} + type StartStage uint8 const ( diff --git a/adapter/lifecycle_legacy.go b/adapter/lifecycle_legacy.go index 94a5cf8c8d..f8b25db6ec 100644 --- a/adapter/lifecycle_legacy.go +++ b/adapter/lifecycle_legacy.go @@ -28,14 +28,14 @@ func LegacyStart(starter any, stage StartStage) error { } type lifecycleServiceWrapper struct { - Service + SimpleLifecycle name string } -func NewLifecycleService(service Service, name string) LifecycleService { +func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService { return &lifecycleServiceWrapper{ - Service: service, - name: name, + SimpleLifecycle: service, + name: name, } } @@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string { } func (l *lifecycleServiceWrapper) Start(stage StartStage) error { - return LegacyStart(l.Service, stage) + return LegacyStart(l.SimpleLifecycle, stage) } func (l *lifecycleServiceWrapper) Close() error { - return l.Service.Close() + return l.SimpleLifecycle.Close() } diff --git a/adapter/rule.go b/adapter/rule.go index 2512a77bf4..f8ee797d44 100644 --- a/adapter/rule.go +++ b/adapter/rule.go @@ -11,7 +11,7 @@ type HeadlessRule interface { type Rule interface { HeadlessRule - Service + SimpleLifecycle Type() string Action() RuleAction } diff --git a/adapter/service.go b/adapter/service.go index 5ed0798dd2..534bd7ebaf 100644 --- a/adapter/service.go +++ b/adapter/service.go @@ -1,6 +1,27 @@ package adapter +import ( + "context" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" +) + type Service interface { - Start() error - Close() error + Lifecycle + Type() string + Tag() string +} + +type ServiceRegistry interface { + option.ServiceOptionsRegistry + Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error) +} + +type ServiceManager interface { + Lifecycle + Services() []Service + Get(tag string) (Service, bool) + Remove(tag string) error + Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error } diff --git a/adapter/service/adapter.go b/adapter/service/adapter.go new file mode 100644 index 0000000000..6c6242ea09 --- /dev/null +++ b/adapter/service/adapter.go @@ -0,0 +1,21 @@ +package service + +type Adapter struct { + serviceType string + serviceTag string +} + +func NewAdapter(serviceType string, serviceTag string) Adapter { + return Adapter{ + serviceType: serviceType, + serviceTag: serviceTag, + } +} + +func (a *Adapter) Type() string { + return a.serviceType +} + +func (a *Adapter) Tag() string { + return a.serviceTag +} diff --git a/adapter/service/manager.go b/adapter/service/manager.go new file mode 100644 index 0000000000..3ef787cbcd --- /dev/null +++ b/adapter/service/manager.go @@ -0,0 +1,143 @@ +package service + +import ( + "context" + "os" + "sync" + + "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/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +var _ adapter.ServiceManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.ServiceRegistry + access sync.Mutex + started bool + stage adapter.StartStage + services []adapter.Service + serviceByTag map[string]adapter.Service +} + +func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager { + return &Manager{ + logger: logger, + registry: registry, + serviceByTag: make(map[string]adapter.Service), + } +} + +func (m *Manager) Start(stage adapter.StartStage) error { + m.access.Lock() + defer m.access.Unlock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + for _, service := range m.services { + err := adapter.LegacyStart(service, stage) + if err != nil { + return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]") + } + } + return nil +} + +func (m *Manager) Close() error { + m.access.Lock() + defer m.access.Unlock() + if !m.started { + return nil + } + m.started = false + services := m.services + m.services = nil + monitor := taskmonitor.New(m.logger, C.StopTimeout) + var err error + for _, service := range services { + monitor.Start("close service/", service.Type(), "[", service.Tag(), "]") + err = E.Append(err, service.Close(), func(err error) error { + return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]") + }) + monitor.Finish() + } + return nil +} + +func (m *Manager) Services() []adapter.Service { + m.access.Lock() + defer m.access.Unlock() + return m.services +} + +func (m *Manager) Get(tag string) (adapter.Service, bool) { + m.access.Lock() + service, found := m.serviceByTag[tag] + m.access.Unlock() + return service, found +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + service, found := m.serviceByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.serviceByTag, tag) + index := common.Index(m.services, func(it adapter.Service) bool { + return it == service + }) + if index == -1 { + panic("invalid service index") + } + m.services = append(m.services[:index], m.services[index+1:]...) + started := m.started + m.access.Unlock() + if started { + return service.Close() + } + return nil +} + +func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error { + service, err := m.registry.Create(ctx, logger, tag, serviceType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + for _, stage := range adapter.ListStartStages { + err = adapter.LegacyStart(service, stage) + if err != nil { + return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]") + } + } + } + if existsService, loaded := m.serviceByTag[tag]; loaded { + if m.started { + err = existsService.Close() + if err != nil { + return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]") + } + } + existsIndex := common.Index(m.services, func(it adapter.Service) bool { + return it == existsService + }) + if existsIndex == -1 { + panic("invalid service index") + } + m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...) + } + m.services = append(m.services, service) + m.serviceByTag[tag] = service + return nil +} diff --git a/adapter/service/registry.go b/adapter/service/registry.go new file mode 100644 index 0000000000..42fec82fc2 --- /dev/null +++ b/adapter/service/registry.go @@ -0,0 +1,72 @@ +package service + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, logger, tag, common.PtrValueOrDefault(options)) + }) +} + +var _ adapter.ServiceRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructor map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructor: make(map[string]constructorFunc), + } +} + +func (m *Registry) CreateOptions(outboundType string) (any, bool) { + m.access.Lock() + defer m.access.Unlock() + optionsConstructor, loaded := m.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) { + m.access.Lock() + defer m.access.Unlock() + constructor, loaded := m.constructor[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, logger, tag, options) +} + +func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + m.access.Lock() + defer m.access.Unlock() + m.optionsType[outboundType] = optionsConstructor + m.constructor[outboundType] = constructor +} diff --git a/adapter/time.go b/adapter/time.go index 3cb13fc1c8..be2631d855 100644 --- a/adapter/time.go +++ b/adapter/time.go @@ -3,6 +3,6 @@ package adapter import "time" type TimeService interface { - Service + SimpleLifecycle TimeFunc() func() time.Time } diff --git a/box.go b/box.go index db900a7a46..184deb8e1d 100644 --- a/box.go +++ b/box.go @@ -12,6 +12,7 @@ import ( "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" + boxService "github.com/sagernet/sing-box/adapter/service" "github.com/sagernet/sing-box/common/certificate" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/taskmonitor" @@ -34,22 +35,23 @@ import ( "github.com/sagernet/sing/service/pause" ) -var _ adapter.Service = (*Box)(nil) +var _ adapter.SimpleLifecycle = (*Box)(nil) type Box struct { - createdAt time.Time - logFactory log.Factory - logger log.ContextLogger - network *route.NetworkManager - endpoint *endpoint.Manager - inbound *inbound.Manager - outbound *outbound.Manager - dnsTransport *dns.TransportManager - dnsRouter *dns.Router - connection *route.ConnectionManager - router *route.Router - services []adapter.LifecycleService - done chan struct{} + createdAt time.Time + logFactory log.Factory + logger log.ContextLogger + network *route.NetworkManager + endpoint *endpoint.Manager + inbound *inbound.Manager + outbound *outbound.Manager + service *boxService.Manager + dnsTransport *dns.TransportManager + dnsRouter *dns.Router + connection *route.ConnectionManager + router *route.Router + internalService []adapter.LifecycleService + done chan struct{} } type Options struct { @@ -64,6 +66,7 @@ func Context( outboundRegistry adapter.OutboundRegistry, endpointRegistry adapter.EndpointRegistry, dnsTransportRegistry adapter.DNSTransportRegistry, + serviceRegistry adapter.ServiceRegistry, ) context.Context { if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || service.FromContext[adapter.InboundRegistry](ctx) == nil { @@ -84,6 +87,10 @@ func Context( ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry) ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry) } + if service.FromContext[adapter.ServiceRegistry](ctx) == nil { + ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry) + ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry) + } return ctx } @@ -99,6 +106,7 @@ func New(options Options) (*Box, error) { inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx) + serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx) if endpointRegistry == nil { return nil, E.New("missing endpoint registry in context") @@ -109,6 +117,12 @@ func New(options Options) (*Box, error) { if outboundRegistry == nil { return nil, E.New("missing outbound registry in context") } + if dnsTransportRegistry == nil { + return nil, E.New("missing DNS transport registry in context") + } + if serviceRegistry == nil { + return nil, E.New("missing service registry in context") + } ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) @@ -142,7 +156,7 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "create log factory") } - var services []adapter.LifecycleService + var internalServices []adapter.LifecycleService certificateOptions := common.PtrValueOrDefault(options.Certificate) if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem || len(certificateOptions.Certificate) > 0 || @@ -153,7 +167,7 @@ func New(options Options) (*Box, error) { return nil, err } service.MustRegister[adapter.CertificateStore](ctx, certificateStore) - services = append(services, certificateStore) + internalServices = append(internalServices, certificateStore) } routeOptions := common.PtrValueOrDefault(options.Route) @@ -162,10 +176,12 @@ func New(options Options) (*Box, error) { inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager) outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final) dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final) + serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry) service.MustRegister[adapter.EndpointManager](ctx, endpointManager) service.MustRegister[adapter.InboundManager](ctx, inboundManager) service.MustRegister[adapter.OutboundManager](ctx, outboundManager) service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager) + 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) @@ -280,6 +296,24 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "initialize outbound[", i, "]") } } + for i, serviceOptions := range options.Services { + var tag string + if serviceOptions.Tag != "" { + tag = serviceOptions.Tag + } else { + tag = F.ToString(i) + } + err = serviceManager.Create( + ctx, + logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")), + tag, + serviceOptions.Type, + serviceOptions.Options, + ) + if err != nil { + return nil, E.Cause(err, "initialize service[", i, "]") + } + } outboundManager.Initialize(common.Must1( direct.NewOutbound( ctx, @@ -305,7 +339,7 @@ func New(options Options) (*Box, error) { if needCacheFile { cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile)) service.MustRegister[adapter.CacheFile](ctx, cacheFile) - services = append(services, cacheFile) + internalServices = append(internalServices, cacheFile) } if needClashAPI { clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) @@ -316,7 +350,7 @@ func New(options Options) (*Box, error) { } router.AppendTracker(clashServer) service.MustRegister[adapter.ClashServer](ctx, clashServer) - services = append(services, clashServer) + internalServices = append(internalServices, clashServer) } if needV2RayAPI { v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) @@ -325,7 +359,7 @@ func New(options Options) (*Box, error) { } if v2rayServer.StatsService() != nil { router.AppendTracker(v2rayServer.StatsService()) - services = append(services, v2rayServer) + internalServices = append(internalServices, v2rayServer) service.MustRegister[adapter.V2RayServer](ctx, v2rayServer) } } @@ -343,22 +377,23 @@ func New(options Options) (*Box, error) { WriteToSystem: ntpOptions.WriteToSystem, }) timeService.TimeService = ntpService - services = append(services, adapter.NewLifecycleService(ntpService, "ntp service")) + internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service")) } return &Box{ - network: networkManager, - endpoint: endpointManager, - inbound: inboundManager, - outbound: outboundManager, - dnsTransport: dnsTransportManager, - dnsRouter: dnsRouter, - connection: connectionManager, - router: router, - createdAt: createdAt, - logFactory: logFactory, - logger: logFactory.Logger(), - services: services, - done: make(chan struct{}), + network: networkManager, + endpoint: endpointManager, + inbound: inboundManager, + outbound: outboundManager, + dnsTransport: dnsTransportManager, + service: serviceManager, + dnsRouter: dnsRouter, + connection: connectionManager, + router: router, + createdAt: createdAt, + logFactory: logFactory, + logger: logFactory.Logger(), + internalService: internalServices, + done: make(chan struct{}), }, nil } @@ -408,11 +443,11 @@ func (s *Box) preStart() error { if err != nil { return E.Cause(err, "start logger") } - err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api + err = adapter.StartNamed(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) + err = adapter.Start(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 } @@ -428,7 +463,7 @@ func (s *Box) start() error { if err != nil { return err } - err = adapter.StartNamed(adapter.StartStateStart, s.services) + err = adapter.StartNamed(adapter.StartStateStart, s.internalService) if err != nil { return err } @@ -440,19 +475,19 @@ func (s *Box) start() error { 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) + err = adapter.Start(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.services) + err = adapter.StartNamed(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) + err = adapter.Start(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.services) + err = adapter.StartNamed(adapter.StartStateStarted, s.internalService) if err != nil { return err } @@ -469,7 +504,7 @@ func (s *Box) Close() error { err := common.Close( s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network, ) - for _, lifecycleService := range s.services { + for _, lifecycleService := range s.internalService { err = E.Append(err, lifecycleService.Close(), func(err error) error { return E.Cause(err, "close ", lifecycleService.Name()) }) diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go index 55fe1179e2..78b55a6f67 100644 --- a/cmd/sing-box/cmd.go +++ b/cmd/sing-box/cmd.go @@ -69,5 +69,5 @@ func preRun(cmd *cobra.Command, args []string) { configPaths = append(configPaths, "config.json") } globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())) - globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry()) + globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry(), include.ServiceRegistry()) } diff --git a/common/tls/acme.go b/common/tls/acme.go index 989df7f050..4a79c56c29 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -68,7 +68,7 @@ func encoderConfig() zapcore.EncoderConfig { return config } -func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) { +func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) { var acmeServer string switch options.Provider { case "", "letsencrypt": diff --git a/common/tls/acme_stub.go b/common/tls/acme_stub.go index f32f9e8ddf..53ced06d11 100644 --- a/common/tls/acme_stub.go +++ b/common/tls/acme_stub.go @@ -12,6 +12,6 @@ import ( "github.com/sagernet/sing/common/logger" ) -func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) { +func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) { return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`) } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 9c0ef1efcf..82ba71edce 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -22,7 +22,7 @@ var errInsecureUnused = E.New("tls: insecure unused") type STDServerConfig struct { config *tls.Config logger log.Logger - acmeService adapter.Service + acmeService adapter.SimpleLifecycle certificate []byte key []byte certificatePath string @@ -165,7 +165,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound return nil, nil } var tlsConfig *tls.Config - var acmeService adapter.Service + var acmeService adapter.SimpleLifecycle var err error if options.ACME != nil && len(options.ACME.Domain) > 0 { //nolint:staticcheck diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 8fc1b66c7a..7dc84b83f8 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -33,7 +33,7 @@ func BaseContext(platformInterface PlatformInterface) context.Context { }) } } - return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry) + return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry()) } func parseConfig(ctx context.Context, configContent string) (option.Options, error) { diff --git a/include/registry.go b/include/registry.go index 9be1f2b4f6..a326e586d3 100644 --- a/include/registry.go +++ b/include/registry.go @@ -118,6 +118,11 @@ func DNSTransportRegistry() *dns.TransportRegistry { return registry } +func ServiceRegistry() *service.Registry { + registry := service.NewRegistry() + return registry +} + func registerStubForRemovedInbounds(registry *inbound.Registry) { inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") diff --git a/option/dns.go b/option/dns.go index f303b8945b..3744578256 100644 --- a/option/dns.go +++ b/option/dns.go @@ -121,7 +121,6 @@ type LegacyDNSFakeIPOptions struct { type DNSTransportOptionsRegistry interface { CreateOptions(transportType string) (any, bool) } - type _DNSServerOptions struct { Type string `json:"type,omitempty"` Tag string `json:"tag,omitempty"` diff --git a/option/endpoint.go b/option/endpoint.go index 909fb89618..45c4f8311f 100644 --- a/option/endpoint.go +++ b/option/endpoint.go @@ -32,11 +32,11 @@ func (h *Endpoint) UnmarshalJSONContext(ctx context.Context, content []byte) err } registry := service.FromContext[EndpointOptionsRegistry](ctx) if registry == nil { - return E.New("missing Endpoint fields registry in context") + return E.New("missing endpoint fields registry in context") } options, loaded := registry.CreateOptions(h.Type) if !loaded { - return E.New("unknown inbound type: ", h.Type) + return E.New("unknown endpoint type: ", h.Type) } err = badjson.UnmarshallExcludedContext(ctx, content, (*_Endpoint)(h), options) if err != nil { diff --git a/option/inbound.go b/option/inbound.go index 99c5bc91a4..64ded9b1c4 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -34,7 +34,7 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro } registry := service.FromContext[InboundOptionsRegistry](ctx) if registry == nil { - return E.New("missing Inbound fields registry in context") + return E.New("missing inbound fields registry in context") } options, loaded := registry.CreateOptions(h.Type) if !loaded { diff --git a/option/options.go b/option/options.go index c0e579ccb5..c964998b23 100644 --- a/option/options.go +++ b/option/options.go @@ -19,6 +19,7 @@ type _Options struct { Inbounds []Inbound `json:"inbounds,omitempty"` Outbounds []Outbound `json:"outbounds,omitempty"` Route *RouteOptions `json:"route,omitempty"` + Services []Service `json:"services,omitempty"` Experimental *ExperimentalOptions `json:"experimental,omitempty"` } diff --git a/option/service.go b/option/service.go new file mode 100644 index 0000000000..7d45bc1433 --- /dev/null +++ b/option/service.go @@ -0,0 +1,47 @@ +package option + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/service" +) + +type ServiceOptionsRegistry interface { + CreateOptions(serviceType string) (any, bool) +} + +type _Service struct { + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` +} + +type Service _Service + +func (h *Service) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Service)(h), h.Options) +} + +func (h *Service) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_Service)(h)) + if err != nil { + return err + } + registry := service.FromContext[ServiceOptionsRegistry](ctx) + if registry == nil { + return E.New("missing service fields registry in context") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown inbound type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Service)(h), options) + if err != nil { + return err + } + h.Options = options + return nil +} From e2440a569e9eecac3b8c43d9050ec2122a0e871b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 29 Mar 2025 17:24:34 +0800 Subject: [PATCH 0444/1272] Add DERP service --- constant/proxy.go | 1 + docs/configuration/endpoint/index.md | 2 +- docs/configuration/index.md | 2 + docs/configuration/index.zh.md | 2 + docs/configuration/service/derp.md | 130 +++++++ docs/configuration/service/index.md | 30 ++ go.mod | 2 +- include/registry.go | 4 + include/tailscale.go | 6 + include/tailscale_stub.go | 7 + mkdocs.yml | 3 + option/tailscale.go | 90 +++++ service/derp/service.go | 518 +++++++++++++++++++++++++++ test/box_test.go | 2 +- 14 files changed, 796 insertions(+), 3 deletions(-) create mode 100644 docs/configuration/service/derp.md create mode 100644 docs/configuration/service/index.md create mode 100644 service/derp/service.go diff --git a/constant/proxy.go b/constant/proxy.go index 1044428b76..743babbcc8 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -25,6 +25,7 @@ const ( TypeTUIC = "tuic" TypeHysteria2 = "hysteria2" TypeTailscale = "tailscale" + TypeDERP = "derp" ) const ( diff --git a/docs/configuration/endpoint/index.md b/docs/configuration/endpoint/index.md index 5e98c4cc21..59101e759b 100644 --- a/docs/configuration/endpoint/index.md +++ b/docs/configuration/endpoint/index.md @@ -6,7 +6,7 @@ icon: material/new-box # Endpoint -Endpoint is protocols that has both inbound and outbound behavior. +An endpoint is a protocol with inbound and outbound behavior. ### Structure diff --git a/docs/configuration/index.md b/docs/configuration/index.md index dc5d063776..1f6eec1375 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -14,6 +14,7 @@ sing-box uses JSON for configuration files. "inbounds": [], "outbounds": [], "route": {}, + "services": [], "experimental": {} } ``` @@ -30,6 +31,7 @@ sing-box uses JSON for configuration files. | `inbounds` | [Inbound](./inbound/) | | `outbounds` | [Outbound](./outbound/) | | `route` | [Route](./route/) | +| `services` | [Service](./service/) | | `experimental` | [Experimental](./experimental/) | ### Check diff --git a/docs/configuration/index.zh.md b/docs/configuration/index.zh.md index a18cefd82e..3bdc352187 100644 --- a/docs/configuration/index.zh.md +++ b/docs/configuration/index.zh.md @@ -14,6 +14,7 @@ sing-box 使用 JSON 作为配置文件格式。 "inbounds": [], "outbounds": [], "route": {}, + "services": [], "experimental": {} } ``` @@ -30,6 +31,7 @@ sing-box 使用 JSON 作为配置文件格式。 | `inbounds` | [入站](./inbound/) | | `outbounds` | [出站](./outbound/) | | `route` | [路由](./route/) | +| `services` | [服务](./service/) | | `experimental` | [实验性](./experimental/) | ### 检查 diff --git a/docs/configuration/service/derp.md b/docs/configuration/service/derp.md new file mode 100644 index 0000000000..adf80d82b3 --- /dev/null +++ b/docs/configuration/service/derp.md @@ -0,0 +1,130 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DERP + +DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper). + +### Structure + +```json +{ + "type": "derp", + + ... // Listen Fields + + "tls": {}, + "config_path": "", + "verify_client_endpoint": [], + "verify_client_url": [], + "mesh_with": [], + "mesh_psk": "", + "mesh_psk_file": "", + "stun": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +#### config_path + +==Required== + +Derper configuration file path. + +Example: `derper.key` + +#### verify_client_endpoint + +Tailscale endpoints tags to verify clients. + +#### verify_client_url + +URL to verify clients. + +Object format: + +```json +{ + "url": "https://my-headscale.com/verify", + + ... // Dial Fields +} +``` + +Setting Array value to a string `__URL__` is equivalent to configuring: + +```json +{ "url": __URL__ } +``` + +#### mesh_with + +Mesh with other DERP servers. + +Object format: + +```json +{ + "server": "", + "server_port": "", + "host": "", + "tls": {}, + + ... // Dial Fields +} +``` + +Object fields: + +- `server`: **Required** DERP server address. +- `server_port`: **Required** DERP server port. +- `host`: Custom DERP hostname. +- `tls`: [TLS](/configuration/shared/tls/#outbound) +- `Dial Fields`: [Dial Fields](/configuration/shared/dial/) + +#### mesh_psk + +Pre-shared key for DERP mesh. + +#### mesh_psk_file + +Pre-shared key file for DERP mesh. + +#### stun + +STUN server listen options. + +Object format: + +```json +{ + "enabled": true, + + ... // Listen Fields +} +``` + +Object fields: + +- `enabled`: **Required** Enable STUN server. +- `listen`: **Required** STUN server listen address, default to `::`. +- `listen_port`: **Required** STUN server listen port, default to `3478`. +- `other Listen Fields`: [Listen Fields](/configuration/shared/listen/) + +Setting `stun` value to a number `__PORT__` is equivalent to configuring: + +```json +{ "enabled": true, "listen_port": __PORT__ } +``` diff --git a/docs/configuration/service/index.md b/docs/configuration/service/index.md new file mode 100644 index 0000000000..7fc64a9f1a --- /dev/null +++ b/docs/configuration/service/index.md @@ -0,0 +1,30 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Service + +### Structure + +```json +{ + "endpoints": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### Fields + +| Type | Format | +|-------------|--------------------------| +| `derp` | [DERP](./derp) | + +#### tag + +The tag of the endpoint. diff --git a/go.mod b/go.mod index ebcc9f47b7..3c0ecb2439 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/anytls/sing-anytls v0.0.8 github.com/caddyserver/certmagic v0.23.0 github.com/cloudflare/circl v1.6.1 + github.com/coder/websocket v1.8.12 github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/render v1.0.3 @@ -65,7 +66,6 @@ require ( 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/coder/websocket v1.8.12 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect diff --git a/include/registry.go b/include/registry.go index a326e586d3..65e6db7bcb 100644 --- a/include/registry.go +++ b/include/registry.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/adapter/service" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/dns/transport" @@ -120,6 +121,9 @@ func DNSTransportRegistry() *dns.TransportRegistry { func ServiceRegistry() *service.Registry { registry := service.NewRegistry() + + registerDERPService(registry) + return registry } diff --git a/include/tailscale.go b/include/tailscale.go index 05eed2cd39..1757283b07 100644 --- a/include/tailscale.go +++ b/include/tailscale.go @@ -4,8 +4,10 @@ package include import ( "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/adapter/service" "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/protocol/tailscale" + "github.com/sagernet/sing-box/service/derp" ) func registerTailscaleEndpoint(registry *endpoint.Registry) { @@ -15,3 +17,7 @@ func registerTailscaleEndpoint(registry *endpoint.Registry) { func registerTailscaleTransport(registry *dns.TransportRegistry) { tailscale.RegistryTransport(registry) } + +func registerDERPService(registry *service.Registry) { + derp.Register(registry) +} diff --git a/include/tailscale_stub.go b/include/tailscale_stub.go index ddf6485ecb..78398875f8 100644 --- a/include/tailscale_stub.go +++ b/include/tailscale_stub.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/adapter/service" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/log" @@ -25,3 +26,9 @@ func registerTailscaleTransport(registry *dns.TransportRegistry) { return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`) }) } + +func registerDERPService(registry *service.Registry) { + service.Register[option.DERPServiceOptions](registry, C.TypeDERP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) { + return nil, E.New(`DERP is not included in this build, rebuild with -tags with_tailscale`) + }) +} diff --git a/mkdocs.yml b/mkdocs.yml index 8ee45c948f..a320809d7c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -169,6 +169,9 @@ nav: - DNS: configuration/outbound/dns.md - Selector: configuration/outbound/selector.md - URLTest: configuration/outbound/urltest.md + - Service: + - configuration/service/index.md + - DERP: configuration/service/derp.md markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets diff --git a/option/tailscale.go b/option/tailscale.go index 7b901770b9..74917fcda2 100644 --- a/option/tailscale.go +++ b/option/tailscale.go @@ -2,6 +2,12 @@ package option import ( "net/netip" + "net/url" + "reflect" + + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" + M "github.com/sagernet/sing/common/metadata" ) type TailscaleEndpointOptions struct { @@ -23,3 +29,87 @@ type TailscaleDNSServerOptions struct { Endpoint string `json:"endpoint,omitempty"` AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"` } + +type DERPServiceOptions struct { + ListenOptions + InboundTLSOptionsContainer + ConfigPath string `json:"config_path,omitempty"` + VerifyClientEndpoint badoption.Listable[string] `json:"verify_client_endpoint,omitempty"` + VerifyClientURL badoption.Listable[*DERPVerifyClientURLOptions] `json:"verify_client_url,omitempty"` + MeshWith badoption.Listable[*DERPMeshOptions] `json:"mesh_with,omitempty"` + MeshPSK string `json:"mesh_psk,omitempty"` + MeshPSKFile string `json:"mesh_psk_file,omitempty"` + STUN *DERPSTUNListenOptions `json:"stun,omitempty"` +} + +type _DERPVerifyClientURLOptions struct { + URL string `json:"url,omitempty"` + DialerOptions +} + +type DERPVerifyClientURLOptions _DERPVerifyClientURLOptions + +func (d DERPVerifyClientURLOptions) ServerIsDomain() bool { + verifyURL, err := url.Parse(d.URL) + if err != nil { + return false + } + return M.IsDomainName(verifyURL.Host) +} + +func (d DERPVerifyClientURLOptions) MarshalJSON() ([]byte, error) { + if reflect.DeepEqual(d, _DERPVerifyClientURLOptions{}) { + return json.Marshal(d.URL) + } else { + return json.Marshal(_DERPVerifyClientURLOptions(d)) + } +} + +func (d *DERPVerifyClientURLOptions) UnmarshalJSON(bytes []byte) error { + var stringValue string + err := json.Unmarshal(bytes, &stringValue) + if err == nil { + d.URL = stringValue + return nil + } + return json.Unmarshal(bytes, (*_DERPVerifyClientURLOptions)(d)) +} + +type DERPMeshOptions struct { + ServerOptions + Host string `json:"host,omitempty"` + OutboundTLSOptionsContainer + DialerOptions +} + +type _DERPSTUNListenOptions struct { + Enabled bool + ListenOptions +} + +type DERPSTUNListenOptions _DERPSTUNListenOptions + +func (d DERPSTUNListenOptions) MarshalJSON() ([]byte, error) { + portOptions := _DERPSTUNListenOptions{ + Enabled: d.Enabled, + ListenOptions: ListenOptions{ + ListenPort: d.ListenPort, + }, + } + if _DERPSTUNListenOptions(d) == portOptions { + return json.Marshal(d.Enabled) + } else { + return json.Marshal(_DERPSTUNListenOptions(d)) + } +} + +func (d *DERPSTUNListenOptions) UnmarshalJSON(bytes []byte) error { + var portValue uint16 + err := json.Unmarshal(bytes, &portValue) + if err == nil { + d.Enabled = true + d.ListenPort = portValue + return nil + } + return json.Unmarshal(bytes, (*_DERPSTUNListenOptions)(d)) +} diff --git a/service/derp/service.go b/service/derp/service.go new file mode 100644 index 0000000000..f950a813f4 --- /dev/null +++ b/service/derp/service.go @@ -0,0 +1,518 @@ +package derp + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/netip" + "os" + "path/filepath" + "regexp" + "strings" + "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" + boxScale "github.com/sagernet/sing-box/protocol/tailscale" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "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" + 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/derp" + "github.com/sagernet/tailscale/derp/derphttp" + "github.com/sagernet/tailscale/net/netmon" + "github.com/sagernet/tailscale/net/stun" + "github.com/sagernet/tailscale/net/wsconn" + "github.com/sagernet/tailscale/tsweb" + "github.com/sagernet/tailscale/types/key" + + "github.com/coder/websocket" + "github.com/go-chi/render" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +func Register(registry *boxService.Registry) { + boxService.Register[option.DERPServiceOptions](registry, C.TypeDERP, NewService) +} + +type Service struct { + boxService.Adapter + ctx context.Context + logger logger.ContextLogger + listener *listener.Listener + stunListener *listener.Listener + tlsConfig tls.ServerConfig + server *derp.Server + configPath string + verifyClientEndpoint []string + verifyClientURL []*option.DERPVerifyClientURLOptions + home string + meshKey string + meshKeyPath string + meshWith []*option.DERPMeshOptions +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) { + if options.TLS == nil || !options.TLS.Enabled { + return nil, E.New("TLS is required for DERP server") + } + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + + var configPath string + if options.ConfigPath != "" { + configPath = filemanager.BasePath(ctx, os.ExpandEnv(options.ConfigPath)) + } else { + return nil, E.New("missing config_path") + } + + if options.MeshPSK != "" { + err = checkMeshKey(options.MeshPSK) + if err != nil { + return nil, E.Cause(err, "invalid mesh_psk") + } + } + + var stunListener *listener.Listener + if options.STUN != nil && options.STUN.Enabled { + if options.STUN.Listen == nil { + options.STUN.Listen = (*badoption.Addr)(common.Ptr(netip.IPv6Unspecified())) + } + if options.STUN.ListenPort == 0 { + options.STUN.ListenPort = 3478 + } + stunListener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkUDP}, + Listen: options.STUN.ListenOptions, + }) + } + + return &Service{ + Adapter: boxService.NewAdapter(C.TypeDERP, tag), + ctx: ctx, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + }), + stunListener: stunListener, + tlsConfig: tlsConfig, + configPath: configPath, + verifyClientEndpoint: options.VerifyClientEndpoint, + verifyClientURL: options.VerifyClientURL, + meshKey: options.MeshPSK, + meshKeyPath: options.MeshPSKFile, + meshWith: options.MeshWith, + }, nil +} + +func (d *Service) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + config, err := readDERPConfig(d.configPath) + if err != nil { + return err + } + + server := derp.NewServer(config.PrivateKey, func(format string, args ...any) { + d.logger.Debug(fmt.Sprintf(format, args...)) + }) + + if len(d.verifyClientURL) > 0 { + var httpClients []*http.Client + var urls []string + for index, options := range d.verifyClientURL { + verifyDialer, createErr := dialer.NewWithOptions(dialer.Options{ + Context: d.ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain(), + NewDialer: true, + }) + if createErr != nil { + return E.Cause(createErr, "verify_client_url[", index, "]") + } + httpClients = append(httpClients, &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return verifyDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + }) + urls = append(urls, options.URL) + } + server.SetVerifyClientHTTPClient(httpClients) + server.SetVerifyClientURL(urls) + } + + if d.meshKey != "" { + server.SetMeshKey(d.meshKey) + } else if d.meshKeyPath != "" { + var meshKeyContent []byte + meshKeyContent, err = os.ReadFile(d.meshKeyPath) + if err != nil { + return err + } + err = checkMeshKey(string(meshKeyContent)) + if err != nil { + return E.Cause(err, "invalid mesh_psk_path file") + } + server.SetMeshKey(string(meshKeyContent)) + } + d.server = server + + derpMux := http.NewServeMux() + derpHandler := derphttp.Handler(server) + derpHandler = addWebSocketSupport(server, derpHandler) + derpMux.Handle("/derp", derpHandler) + + homeHandler, ok := getHomeHandler(d.home) + if !ok { + return E.New("invalid home value: ", d.home) + } + + derpMux.HandleFunc("/derp/probe", derphttp.ProbeHandler) + derpMux.HandleFunc("/derp/latency-check", derphttp.ProbeHandler) + derpMux.HandleFunc("/bootstrap-dns", tsweb.BrowserHeaderHandlerFunc(handleBootstrapDNS(d.ctx))) + derpMux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tsweb.AddBrowserHeaders(w) + homeHandler.ServeHTTP(w, r) + })) + derpMux.Handle("/robots.txt", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tsweb.AddBrowserHeaders(w) + io.WriteString(w, "User-agent: *\nDisallow: /\n") + })) + derpMux.Handle("/generate_204", http.HandlerFunc(derphttp.ServeNoContent)) + + err = d.tlsConfig.Start() + if err != nil { + return err + } + + tcpListener, err := d.listener.ListenTCP() + if err != nil { + return err + } + if len(d.tlsConfig.NextProtos()) == 0 { + d.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"}) + } else if !common.Contains(d.tlsConfig.NextProtos(), http2.NextProtoTLS) { + d.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, d.tlsConfig.NextProtos()...)) + } + tcpListener = aTLS.NewListener(tcpListener, d.tlsConfig) + httpServer := &http.Server{ + Handler: h2c.NewHandler(derpMux, &http2.Server{}), + } + go httpServer.Serve(tcpListener) + + if d.stunListener != nil { + stunConn, err := d.stunListener.ListenUDP() + if err != nil { + return err + } + go d.loopSTUNPacket(stunConn.(*net.UDPConn)) + } + case adapter.StartStatePostStart: + if len(d.verifyClientEndpoint) > 0 { + var endpoints []*tailscale.LocalClient + endpointManager := service.FromContext[adapter.EndpointManager](d.ctx) + for _, endpointTag := range d.verifyClientEndpoint { + endpoint, loaded := endpointManager.Get(endpointTag) + if !loaded { + return E.New("verify_client_endpoint: endpoint not found: ", endpointTag) + } + tsEndpoint, isTailscale := endpoint.(*boxScale.Endpoint) + if !isTailscale { + return E.New("verify_client_endpoint: endpoint is not Tailscale: ", endpointTag) + } + localClient, err := tsEndpoint.Server().LocalClient() + if err != nil { + return err + } + endpoints = append(endpoints, localClient) + } + d.server.SetVerifyClientLocalClient(endpoints) + } + if len(d.meshWith) > 0 { + if !d.server.HasMeshKey() { + return E.New("missing mesh psk") + } + for _, options := range d.meshWith { + err := d.startMeshWithHost(d.server, options) + if err != nil { + return err + } + } + } + } + return nil +} + +func checkMeshKey(meshKey string) error { + checkRegex, err := regexp.Compile(`^[0-9a-f]{64}$`) + if err != nil { + return err + } + if !checkRegex.MatchString(meshKey) { + return E.New("key must contain exactly 64 hex digits") + } + return nil +} + +func (d *Service) startMeshWithHost(derpServer *derp.Server, server *option.DERPMeshOptions) error { + meshDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: d.ctx, + Options: server.DialerOptions, + RemoteIsDomain: server.ServerIsDomain(), + NewDialer: true, + }) + if err != nil { + return err + } + var hostname string + if server.Host != "" { + hostname = server.Host + } else { + hostname = server.Server + } + var stdConfig *tls.STDConfig + if server.TLS != nil && server.TLS.Enabled { + tlsConfig, err := tls.NewClient(d.ctx, hostname, common.PtrValueOrDefault(server.TLS)) + if err != nil { + return err + } + stdConfig, err = tlsConfig.Config() + if err != nil { + return err + } + } + logf := func(format string, args ...any) { + d.logger.Debug(F.ToString("mesh(", hostname, "): ", fmt.Sprintf(format, args...))) + } + var meshHost string + if server.ServerPort == 0 || server.ServerPort == 443 { + meshHost = hostname + } else { + meshHost = M.ParseSocksaddrHostPort(hostname, server.ServerPort).String() + } + var serverURL string + if stdConfig != nil { + serverURL = "https://" + meshHost + "/derp" + } else { + serverURL = "http://" + meshHost + "/derp" + } + meshClient, err := derphttp.NewClient(derpServer.PrivateKey(), serverURL, logf, netmon.NewStatic()) + if err != nil { + return err + } + meshClient.TLSConfig = stdConfig + meshClient.MeshKey = derpServer.MeshKey() + meshClient.WatchConnectionChanges = true + meshClient.SetURLDialer(func(ctx context.Context, network, addr string) (net.Conn, error) { + return meshDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }) + 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) + return nil +} + +func (d *Service) Close() error { + return common.Close( + common.PtrOrNil(d.listener), + d.tlsConfig, + ) +} + +var homePage = ` +

DERP

+

+ This is a Tailscale DERP server. +

+ +

+ It provides STUN, interactive connectivity establishment, and relaying of end-to-end encrypted traffic + for Tailscale clients. +

+ +

+ Documentation: +

+ +