Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix typo: Filer -> Filter #2170

Open
wants to merge 26 commits into
base: dev-next
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
25065d7
platform: Prepare connections list
nekohasekai Jun 11, 2024
03f5d6e
platform: Add log update interval
nekohasekai Jun 18, 2024
8d0d49c
platform: Fix clash server reload on android
nekohasekai Jun 25, 2024
388c75a
WTF is this
nekohasekai Jun 24, 2024
a18179a
Implement read deadline for QUIC based UDP inbounds
nekohasekai Aug 18, 2024
bd12247
Bump rule-set version
nekohasekai Jul 17, 2024
744cc98
Improve usages of `json.Unmarshal`
nekohasekai Jul 22, 2024
2dbeb63
Add IP address support for `rule-set match` match
nekohasekai Jul 3, 2024
00e6210
Add `rule-set decompile` command
nekohasekai Jul 3, 2024
a50b0bc
Add auto-redirect & Improve auto-route
nekohasekai Jun 7, 2024
1cdaea4
Improve base DNS transports & Minor fixes
nekohasekai Jun 7, 2024
7a2cd77
Add custom options for TUN `auto-route` and `auto-redirect`
nekohasekai Jun 22, 2024
1e3c136
Add accept empty DNS rule option
nekohasekai Jun 24, 2024
c2ab497
Unique rule-set names
nekohasekai Jun 25, 2024
852c07c
Add inline rule-set & Add reload for local rule-set
nekohasekai Jun 25, 2024
5a2c703
Improve QUIC sniffer
nekohasekai Jul 7, 2024
1e36c75
Add AdGuard DNS filter support
nekohasekai Jul 26, 2024
795e1cf
Write close error to log
nekohasekai Aug 10, 2024
04613c2
Add SSH sniffer
nekohasekai Aug 20, 2024
cb46b64
Add RDP sniffer
nekohasekai Aug 28, 2024
6362b77
clash-api: Add PNA support
nekohasekai Aug 23, 2024
a07219d
Add `process_path_regex` rule type
srk24 Sep 15, 2024
a9bb07d
Update utls to v1.6.7
nekohasekai Sep 23, 2024
e68a9e6
documentation: Bump version
nekohasekai Jun 7, 2024
0ef4f80
auto-redirect: Let fw4 take precedence over prerouting
nekohasekai Oct 8, 2024
36f6f6e
Fix typo: Filer -> Filter
Dreista Oct 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add auto-redirect & Improve auto-route
nekohasekai committed Oct 6, 2024
commit a50b0bc1a97dd16355879e8a6610db0952efdbfb
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ FROM --platform=$TARGETPLATFORM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
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"]
15 changes: 15 additions & 0 deletions adapter/router.go
Original file line number Diff line number Diff line change
@@ -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
28 changes: 22 additions & 6 deletions box.go
Original file line number Diff line number Diff line change
@@ -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 {
32 changes: 28 additions & 4 deletions cmd/sing-box/cmd_tools_fetch.go
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions cmd/sing-box/cmd_tools_fetch_http3.go
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions cmd/sing-box/cmd_tools_fetch_http3_stub.go
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 16 additions & 2 deletions common/dialer/default.go
Original file line number Diff line number Diff line change
@@ -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())
144 changes: 140 additions & 4 deletions docs/configuration/inbound/tun.md
Original file line number Diff line number Diff line change
@@ -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`.
147 changes: 143 additions & 4 deletions docs/configuration/inbound/tun.zh.md
Original file line number Diff line number Diff line change
@@ -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` 项匹配主机名 **后缀**.

绕过代理的主机名列表。

8 changes: 8 additions & 0 deletions docs/deprecated.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions docs/deprecated.zh.md
Original file line number Diff line number Diff line change
@@ -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 才能编译。
68 changes: 68 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -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
68 changes: 68 additions & 0 deletions docs/migration.zh.md
Original file line number Diff line number Diff line change
@@ -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 更新
19 changes: 12 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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,31 +65,36 @@ 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
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/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
40 changes: 24 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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,16 +195,16 @@ 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=
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=
243 changes: 216 additions & 27 deletions inbound/tun.go
Original file line number Diff line number Diff line change
@@ -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,23 +117,40 @@ 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")
}
}
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 {
63 changes: 40 additions & 23 deletions option/tun.go
Original file line number Diff line number Diff line change
@@ -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
}
52 changes: 42 additions & 10 deletions route/router.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions route/rule_item_rule_set.go
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions route/rule_set.go
Original file line number Diff line number Diff line change
@@ -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 {
59 changes: 51 additions & 8 deletions route/rule_set_local.go
Original file line number Diff line number Diff line change
@@ -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
}
74 changes: 67 additions & 7 deletions route/rule_set_remote.go
Original file line number Diff line number Diff line change
@@ -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
}