mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
auto-redirect: Add route address set support for nftables
This commit is contained in:
parent
826ebd56a6
commit
1abe60755b
2
.gitignore
vendored
2
.gitignore
vendored
@ -14,3 +14,5 @@
|
|||||||
/*.xcframework/
|
/*.xcframework/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/config.d/
|
/config.d/
|
||||||
|
/venv/
|
||||||
|
|
||||||
|
8
Makefile
8
Makefile
@ -192,13 +192,15 @@ lib_install:
|
|||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|
||||||
publish_docs:
|
publish_docs:
|
||||||
mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||||
|
|
||||||
docs_install:
|
docs_install:
|
||||||
pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
python -m venv venv
|
||||||
|
source ./venv/bin/active && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
rm -f $(shell go env GOPATH)/sing-box
|
rm -f $(shell go env GOPATH)/sing-box
|
||||||
|
@ -10,15 +10,18 @@ import (
|
|||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
mdns "github.com/miekg/dns"
|
mdns "github.com/miekg/dns"
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
Service
|
Service
|
||||||
PreStarter
|
PreStarter
|
||||||
PostStarter
|
PostStarter
|
||||||
|
Cleanup() error
|
||||||
|
|
||||||
Outbounds() []Outbound
|
Outbounds() []Outbound
|
||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
@ -45,7 +48,9 @@ type Router interface {
|
|||||||
DefaultInterface() string
|
DefaultInterface() string
|
||||||
AutoDetectInterface() bool
|
AutoDetectInterface() bool
|
||||||
AutoDetectInterfaceFunc() control.Func
|
AutoDetectInterfaceFunc() control.Func
|
||||||
DefaultMark() int
|
DefaultMark() uint32
|
||||||
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
|
AutoRedirectOutputMark() uint32
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
@ -92,12 +97,22 @@ type DNSRule interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
|
Name() string
|
||||||
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
||||||
|
PostStart() error
|
||||||
Metadata() RuleSetMetadata
|
Metadata() RuleSetMetadata
|
||||||
|
ExtractIPSet() []*netipx.IPSet
|
||||||
|
IncRef()
|
||||||
|
DecRef()
|
||||||
|
Cleanup()
|
||||||
|
RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback]
|
||||||
|
UnregisterCallback(element *list.Element[RuleSetUpdateCallback])
|
||||||
Close() error
|
Close() error
|
||||||
HeadlessRule
|
HeadlessRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RuleSetUpdateCallback func(it RuleSet)
|
||||||
|
|
||||||
type RuleSetMetadata struct {
|
type RuleSetMetadata struct {
|
||||||
ContainsProcessRule bool
|
ContainsProcessRule bool
|
||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
|
28
box.go
28
box.go
@ -303,7 +303,11 @@ func (s *Box) start() error {
|
|||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
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 {
|
func (s *Box) postStart() error {
|
||||||
@ -313,16 +317,28 @@ func (s *Box) postStart() error {
|
|||||||
return E.Cause(err, "start ", serviceName)
|
return E.Cause(err, "start ", serviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, outbound := range s.outbounds {
|
// TODO: reorganize ALL start order
|
||||||
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
|
for _, out := range s.outbounds {
|
||||||
|
if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound {
|
||||||
err := lateOutbound.PostStart()
|
err := lateOutbound.PostStart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "post-start outbound/", outbound.Tag())
|
return E.Cause(err, "post-start outbound/", out.Tag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
err := s.router.PostStart()
|
||||||
return 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 {
|
func (s *Box) Close() error {
|
||||||
|
@ -9,8 +9,10 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -32,7 +34,10 @@ func init() {
|
|||||||
commandTools.AddCommand(commandFetch)
|
commandTools.AddCommand(commandFetch)
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient *http.Client
|
var (
|
||||||
|
httpClient *http.Client
|
||||||
|
http3Client *http.Client
|
||||||
|
)
|
||||||
|
|
||||||
func fetch(args []string) error {
|
func fetch(args []string) error {
|
||||||
instance, err := createPreStartedClient()
|
instance, err := createPreStartedClient()
|
||||||
@ -53,8 +58,16 @@ func fetch(args []string) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer httpClient.CloseIdleConnections()
|
defer httpClient.CloseIdleConnections()
|
||||||
|
if C.WithQUIC {
|
||||||
|
err = initializeHTTP3Client(instance)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer http3Client.CloseIdleConnections()
|
||||||
|
}
|
||||||
for _, urlString := range args {
|
for _, urlString := range args {
|
||||||
parsedURL, err := url.Parse(urlString)
|
var parsedURL *url.URL
|
||||||
|
parsedURL, err = url.Parse(urlString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -63,16 +76,27 @@ func fetch(args []string) error {
|
|||||||
parsedURL.Scheme = "http"
|
parsedURL.Scheme = "http"
|
||||||
fallthrough
|
fallthrough
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
err = fetchHTTP(parsedURL)
|
err = fetchHTTP(httpClient, parsedURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
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)
|
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal file
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal file
@ -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
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal file
18
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal file
@ -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
|
||||||
|
}
|
@ -50,12 +50,26 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.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))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||||
listener.Control = control.Append(listener.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()))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
listener.Control = control.Append(listener.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 {
|
if options.ReuseAddr {
|
||||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
|
@ -4,7 +4,18 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.10.0"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-plus: [auto_redirect](#auto_redirect)
|
: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"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
@ -27,27 +38,57 @@ icon: material/new-box
|
|||||||
"type": "tun",
|
"type": "tun",
|
||||||
"tag": "tun-in",
|
"tag": "tun-in",
|
||||||
"interface_name": "tun0",
|
"interface_name": "tun0",
|
||||||
"inet4_address": "172.19.0.1/30",
|
"address": [
|
||||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
"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,
|
"mtu": 9000,
|
||||||
"gso": false,
|
"gso": false,
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"strict_route": true,
|
"strict_route": true,
|
||||||
"auto_redirect": false,
|
"auto_redirect": false,
|
||||||
|
"route_address": [
|
||||||
|
"0.0.0.0/1",
|
||||||
|
"128.0.0.0/1",
|
||||||
|
"::/1",
|
||||||
|
"8000::/1"
|
||||||
|
],
|
||||||
|
// deprecated
|
||||||
"inet4_route_address": [
|
"inet4_route_address": [
|
||||||
"0.0.0.0/1",
|
"0.0.0.0/1",
|
||||||
"128.0.0.0/1"
|
"128.0.0.0/1"
|
||||||
],
|
],
|
||||||
|
// deprecated
|
||||||
"inet6_route_address": [
|
"inet6_route_address": [
|
||||||
"::/1",
|
"::/1",
|
||||||
"8000::/1"
|
"8000::/1"
|
||||||
],
|
],
|
||||||
|
"route_exclude_address": [
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"fc00::/7"
|
||||||
|
],
|
||||||
|
// deprecated
|
||||||
"inet4_route_exclude_address": [
|
"inet4_route_exclude_address": [
|
||||||
"192.168.0.0/16"
|
"192.168.0.0/16"
|
||||||
],
|
],
|
||||||
|
// deprecated
|
||||||
"inet6_route_exclude_address": [
|
"inet6_route_exclude_address": [
|
||||||
"fc00::/7"
|
"fc00::/7"
|
||||||
],
|
],
|
||||||
|
"route_address_set": [
|
||||||
|
"geoip-cloudflare"
|
||||||
|
],
|
||||||
|
"route_exclude_address_set": [
|
||||||
|
"geoip-cn"
|
||||||
|
],
|
||||||
"endpoint_independent_nat": false,
|
"endpoint_independent_nat": false,
|
||||||
"udp_timeout": "5m",
|
"udp_timeout": "5m",
|
||||||
"stack": "system",
|
"stack": "system",
|
||||||
@ -107,14 +148,26 @@ icon: material/new-box
|
|||||||
|
|
||||||
Virtual device name, automatically selected if empty.
|
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
|
#### 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.
|
IPv4 prefix for the tun interface.
|
||||||
|
|
||||||
#### inet6_address
|
#### 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.
|
IPv6 prefix for the tun interface.
|
||||||
|
|
||||||
#### mtu
|
#### mtu
|
||||||
@ -168,7 +221,7 @@ It may prevent some applications (such as VirtualBox) from working properly in c
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux.
|
Only supported on Linux with `auto_route` enabled.
|
||||||
|
|
||||||
Automatically configure iptables/nftables to redirect connections.
|
Automatically configure iptables/nftables to redirect connections.
|
||||||
|
|
||||||
@ -181,22 +234,76 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
|||||||
|
|
||||||
`auto_route` with `auto_redirect` now works as expected on routers **without intervention**.
|
`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
|
#### 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.
|
Use custom routes instead of default when `auto_route` is enabled.
|
||||||
|
|
||||||
#### inet6_route_address
|
#### 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.
|
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
|
#### 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.
|
Exclude custom routes when `auto_route` is enabled.
|
||||||
|
|
||||||
#### inet6_route_exclude_address
|
#### 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.
|
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
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
!!! info ""
|
!!! info ""
|
||||||
|
@ -2,9 +2,20 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.10.0 中的更改"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-plus: [auto_redirect](#auto_redirect)
|
: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 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
@ -27,27 +38,57 @@ icon: material/new-box
|
|||||||
"type": "tun",
|
"type": "tun",
|
||||||
"tag": "tun-in",
|
"tag": "tun-in",
|
||||||
"interface_name": "tun0",
|
"interface_name": "tun0",
|
||||||
"inet4_address": "172.19.0.1/30",
|
"address": [
|
||||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
"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,
|
"mtu": 9000,
|
||||||
"gso": false,
|
"gso": false,
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"strict_route": true,
|
"strict_route": true,
|
||||||
"auto_redirect": false,
|
"auto_redirect": false,
|
||||||
|
"route_address": [
|
||||||
|
"0.0.0.0/1",
|
||||||
|
"128.0.0.0/1",
|
||||||
|
"::/1",
|
||||||
|
"8000::/1"
|
||||||
|
],
|
||||||
|
// 已弃用
|
||||||
"inet4_route_address": [
|
"inet4_route_address": [
|
||||||
"0.0.0.0/1",
|
"0.0.0.0/1",
|
||||||
"128.0.0.0/1"
|
"128.0.0.0/1"
|
||||||
],
|
],
|
||||||
|
// 已弃用
|
||||||
"inet6_route_address": [
|
"inet6_route_address": [
|
||||||
"::/1",
|
"::/1",
|
||||||
"8000::/1"
|
"8000::/1"
|
||||||
],
|
],
|
||||||
|
"route_exclude_address": [
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"fc00::/7"
|
||||||
|
],
|
||||||
|
// 已弃用
|
||||||
"inet4_route_exclude_address": [
|
"inet4_route_exclude_address": [
|
||||||
"192.168.0.0/16"
|
"192.168.0.0/16"
|
||||||
],
|
],
|
||||||
|
// 已弃用
|
||||||
"inet6_route_exclude_address": [
|
"inet6_route_exclude_address": [
|
||||||
"fc00::/7"
|
"fc00::/7"
|
||||||
],
|
],
|
||||||
|
"route_address_set": [
|
||||||
|
"geoip-cloudflare"
|
||||||
|
],
|
||||||
|
"route_exclude_address_set": [
|
||||||
|
"geoip-cn"
|
||||||
|
],
|
||||||
"endpoint_independent_nat": false,
|
"endpoint_independent_nat": false,
|
||||||
"udp_timeout": "5m",
|
"udp_timeout": "5m",
|
||||||
"stack": "system",
|
"stack": "system",
|
||||||
@ -107,14 +148,30 @@ icon: material/new-box
|
|||||||
|
|
||||||
虚拟设备名称,默认自动选择。
|
虚拟设备名称,默认自动选择。
|
||||||
|
|
||||||
|
#### address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
tun 接口的 IPv4 和 IPv6 前缀。
|
||||||
|
|
||||||
#### inet4_address
|
#### inet4_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
tun 接口的 IPv4 前缀。
|
tun 接口的 IPv4 前缀。
|
||||||
|
|
||||||
#### inet6_address
|
#### inet6_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
tun 接口的 IPv6 前缀。
|
tun 接口的 IPv6 前缀。
|
||||||
|
|
||||||
#### mtu
|
#### mtu
|
||||||
@ -181,22 +238,76 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。
|
带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。
|
||||||
|
|
||||||
|
#### route_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
设置到 Tun 的自定义路由。
|
||||||
|
|
||||||
#### inet4_route_address
|
#### inet4_route_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||||
|
|
||||||
#### inet6_route_address
|
#### inet6_route_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||||
|
|
||||||
|
#### route_exclude_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
设置到 Tun 的排除自定义路由。
|
||||||
|
|
||||||
#### inet4_route_exclude_address
|
#### inet4_route_exclude_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
启用 `auto_route` 时排除自定义路由。
|
启用 `auto_route` 时排除自定义路由。
|
||||||
|
|
||||||
#### inet6_route_exclude_address
|
#### inet6_route_exclude_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
启用 `auto_route` 时排除自定义路由。
|
启用 `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
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
启用独立于端点的 NAT。
|
启用独立于端点的 NAT。
|
||||||
@ -312,7 +423,7 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
!!! note ""
|
!!! note ""
|
||||||
|
|
||||||
在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**.
|
在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**.
|
||||||
|
|
||||||
绕过代理的主机名列表。
|
绕过代理的主机名列表。
|
||||||
|
|
||||||
|
@ -6,6 +6,14 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
## 1.10.0
|
## 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
|
#### 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.
|
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||||
|
@ -6,6 +6,14 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
## 1.10.0
|
## 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 的支持
|
#### 移除对 go1.18 和 go1.19 的支持
|
||||||
|
|
||||||
由于维护困难,sing-box 1.10.0 要求至少 Go 1.20 才能编译。
|
由于维护困难,sing-box 1.10.0 要求至少 Go 1.20 才能编译。
|
||||||
|
@ -2,12 +2,76 @@
|
|||||||
icon: material/arrange-bring-forward
|
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.0
|
## 1.9.0
|
||||||
|
|
||||||
!!! warning "Unstable"
|
|
||||||
|
|
||||||
This version is still under development, and the following migration guide may be changed in the future.
|
|
||||||
|
|
||||||
### `domain_suffix` behavior update
|
### `domain_suffix` behavior update
|
||||||
|
|
||||||
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.
|
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.
|
||||||
|
@ -2,12 +2,76 @@
|
|||||||
icon: material/arrange-bring-forward
|
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.0
|
## 1.9.0
|
||||||
|
|
||||||
!!! warning "不稳定的"
|
|
||||||
|
|
||||||
该版本仍在开发中,迁移指南可能将在未来更改。
|
|
||||||
|
|
||||||
### `domain_suffix` 行为更新
|
### `domain_suffix` 行为更新
|
||||||
|
|
||||||
由于历史原因,sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。
|
由于历史原因,sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。
|
||||||
|
232
inbound/tun.go
232
inbound/tun.go
@ -3,7 +3,9 @@ package inbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -20,28 +22,91 @@ import (
|
|||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ranges"
|
"github.com/sagernet/sing/common/ranges"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Inbound = (*Tun)(nil)
|
var _ adapter.Inbound = (*Tun)(nil)
|
||||||
|
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
tag string
|
tag string
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
inboundOptions option.InboundOptions
|
inboundOptions option.InboundOptions
|
||||||
tunOptions tun.Options
|
tunOptions tun.Options
|
||||||
endpointIndependentNat bool
|
endpointIndependentNat bool
|
||||||
udpTimeout int64
|
udpTimeout int64
|
||||||
stack string
|
stack string
|
||||||
tunIf tun.Tun
|
tunIf tun.Tun
|
||||||
tunStack tun.Stack
|
tunStack tun.Stack
|
||||||
platformInterface platform.Interface
|
platformInterface platform.Interface
|
||||||
platformOptions option.TunPlatformOptions
|
platformOptions option.TunPlatformOptions
|
||||||
autoRedirect tun.AutoRedirect
|
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) {
|
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
|
tunMTU := options.MTU
|
||||||
if tunMTU == 0 {
|
if tunMTU == 0 {
|
||||||
tunMTU = 9000
|
tunMTU = 9000
|
||||||
@ -68,6 +133,23 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{
|
inbound := &Tun{
|
||||||
tag: tag,
|
tag: tag,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@ -78,23 +160,26 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
Name: options.InterfaceName,
|
Name: options.InterfaceName,
|
||||||
MTU: tunMTU,
|
MTU: tunMTU,
|
||||||
GSO: options.GSO,
|
GSO: options.GSO,
|
||||||
Inet4Address: options.Inet4Address,
|
Inet4Address: inet4Address,
|
||||||
Inet6Address: options.Inet6Address,
|
Inet6Address: inet6Address,
|
||||||
AutoRoute: options.AutoRoute,
|
AutoRoute: options.AutoRoute,
|
||||||
|
IPRoute2TableIndex: tableIndex,
|
||||||
|
IPRoute2RuleIndex: ruleIndex,
|
||||||
|
AutoRedirectInputMark: inputMark,
|
||||||
|
AutoRedirectOutputMark: outputMark,
|
||||||
StrictRoute: options.StrictRoute,
|
StrictRoute: options.StrictRoute,
|
||||||
IncludeInterface: options.IncludeInterface,
|
IncludeInterface: options.IncludeInterface,
|
||||||
ExcludeInterface: options.ExcludeInterface,
|
ExcludeInterface: options.ExcludeInterface,
|
||||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
Inet4RouteAddress: inet4RouteAddress,
|
||||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
Inet6RouteAddress: inet6RouteAddress,
|
||||||
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
|
Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
|
||||||
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
|
Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
|
||||||
IncludeUID: includeUID,
|
IncludeUID: includeUID,
|
||||||
ExcludeUID: excludeUID,
|
ExcludeUID: excludeUID,
|
||||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
IncludePackage: options.IncludePackage,
|
IncludePackage: options.IncludePackage,
|
||||||
ExcludePackage: options.ExcludePackage,
|
ExcludePackage: options.ExcludePackage,
|
||||||
InterfaceMonitor: router.InterfaceMonitor(),
|
InterfaceMonitor: router.InterfaceMonitor(),
|
||||||
TableIndex: 2022,
|
|
||||||
},
|
},
|
||||||
endpointIndependentNat: options.EndpointIndependentNat,
|
endpointIndependentNat: options.EndpointIndependentNat,
|
||||||
udpTimeout: int64(udpTimeout.Seconds()),
|
udpTimeout: int64(udpTimeout.Seconds()),
|
||||||
@ -108,15 +193,47 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
}
|
}
|
||||||
disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES"))
|
disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES"))
|
||||||
inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{
|
inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{
|
||||||
TunOptions: &inbound.tunOptions,
|
TunOptions: &inbound.tunOptions,
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Handler: inbound,
|
Handler: inbound,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
TableName: "sing-box",
|
NetworkMonitor: router.NetworkMonitor(),
|
||||||
DisableNFTables: dErr == nil && disableNFTables,
|
InterfaceFinder: router.InterfaceFinder(),
|
||||||
|
TableName: "sing-box",
|
||||||
|
DisableNFTables: dErr == nil && disableNFTables,
|
||||||
|
RouteAddressSet: &inbound.routeAddressSet,
|
||||||
|
RouteExcludeAddressSet: &inbound.routeExcludeAddressSet,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize auto redirect")
|
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
|
return inbound, nil
|
||||||
@ -141,11 +258,11 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.
|
|||||||
}
|
}
|
||||||
var start, end uint64
|
var start, end uint64
|
||||||
var err error
|
var err error
|
||||||
start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32)
|
start, err = strconv.ParseUint(uidRange[:subIndex], 0, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse range start")
|
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 {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse range end")
|
return nil, E.Cause(err, "parse range end")
|
||||||
}
|
}
|
||||||
@ -215,18 +332,57 @@ func (t *Tun) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if t.autoRedirect != nil {
|
|
||||||
monitor.Start("initiating auto redirect")
|
|
||||||
err = t.autoRedirect.Start()
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "auto redirect")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.logger.Info("started at ", t.tunOptions.Name)
|
t.logger.Info("started at ", t.tunOptions.Name)
|
||||||
return nil
|
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("initiating 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 {
|
func (t *Tun) Close() error {
|
||||||
return common.Close(
|
return common.Close(
|
||||||
t.tunStack,
|
t.tunStack,
|
||||||
|
@ -113,7 +113,7 @@ type DialerOptions struct {
|
|||||||
Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"`
|
Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"`
|
||||||
Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"`
|
Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"`
|
||||||
ProtectPath string `json:"protect_path,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"`
|
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
||||||
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
|
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
|
||||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||||
|
@ -10,7 +10,7 @@ type RouteOptions struct {
|
|||||||
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
|
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
|
||||||
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
||||||
DefaultInterface string `json:"default_interface,omitempty"`
|
DefaultInterface string `json:"default_interface,omitempty"`
|
||||||
DefaultMark int `json:"default_mark,omitempty"`
|
DefaultMark uint32 `json:"default_mark,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeoIPOptions struct {
|
type GeoIPOptions struct {
|
||||||
|
@ -3,30 +3,46 @@ package option
|
|||||||
import "net/netip"
|
import "net/netip"
|
||||||
|
|
||||||
type TunInboundOptions struct {
|
type TunInboundOptions struct {
|
||||||
InterfaceName string `json:"interface_name,omitempty"`
|
InterfaceName string `json:"interface_name,omitempty"`
|
||||||
MTU uint32 `json:"mtu,omitempty"`
|
MTU uint32 `json:"mtu,omitempty"`
|
||||||
GSO bool `json:"gso,omitempty"`
|
GSO bool `json:"gso,omitempty"`
|
||||||
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
|
Address Listable[netip.Prefix] `json:"address,omitempty"`
|
||||||
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
|
AutoRoute bool `json:"auto_route,omitempty"`
|
||||||
AutoRoute bool `json:"auto_route,omitempty"`
|
IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"`
|
||||||
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"`
|
||||||
StrictRoute bool `json:"strict_route,omitempty"`
|
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
||||||
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
|
AutoRedirectInputMark uint32 `json:"auto_redirect_input_mark,omitempty"`
|
||||||
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
|
AutoRedirectOutputMark uint32 `json:"auto_redirect_output_mark,omitempty"`
|
||||||
Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"`
|
StrictRoute bool `json:"strict_route,omitempty"`
|
||||||
Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
|
RouteAddress Listable[netip.Prefix] `json:"route_address,omitempty"`
|
||||||
IncludeInterface Listable[string] `json:"include_interface,omitempty"`
|
RouteAddressSet Listable[string] `json:"route_address_set,omitempty"`
|
||||||
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
|
RouteExcludeAddress Listable[netip.Prefix] `json:"route_exclude_address,omitempty"`
|
||||||
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
|
RouteExcludeAddressSet Listable[string] `json:"route_exclude_address_set,omitempty"`
|
||||||
IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"`
|
IncludeInterface Listable[string] `json:"include_interface,omitempty"`
|
||||||
ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"`
|
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
|
||||||
ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"`
|
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
|
||||||
IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"`
|
IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"`
|
||||||
IncludePackage Listable[string] `json:"include_package,omitempty"`
|
ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"`
|
||||||
ExcludePackage Listable[string] `json:"exclude_package,omitempty"`
|
ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"`
|
||||||
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
|
IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"`
|
||||||
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
IncludePackage Listable[string] `json:"include_package,omitempty"`
|
||||||
Stack string `json:"stack,omitempty"`
|
ExcludePackage Listable[string] `json:"exclude_package,omitempty"`
|
||||||
Platform *TunPlatformOptions `json:"platform,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
|
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"`
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,8 @@ type Router struct {
|
|||||||
interfaceFinder *control.DefaultInterfaceFinder
|
interfaceFinder *control.DefaultInterfaceFinder
|
||||||
autoDetectInterface bool
|
autoDetectInterface bool
|
||||||
defaultInterface string
|
defaultInterface string
|
||||||
defaultMark int
|
defaultMark uint32
|
||||||
|
autoRedirectOutputMark uint32
|
||||||
networkMonitor tun.NetworkUpdateMonitor
|
networkMonitor tun.NetworkUpdateMonitor
|
||||||
interfaceMonitor tun.DefaultInterfaceMonitor
|
interfaceMonitor tun.DefaultInterfaceMonitor
|
||||||
packageManager tun.PackageManager
|
packageManager tun.PackageManager
|
||||||
@ -724,10 +725,26 @@ func (r *Router) PostStart() error {
|
|||||||
return E.Cause(err, "initialize rule[", i, "]")
|
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
|
r.started = true
|
||||||
return nil
|
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) {
|
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
outbound, loaded := r.outboundByTag[tag]
|
outbound, loaded := r.outboundByTag[tag]
|
||||||
return outbound, loaded
|
return outbound, loaded
|
||||||
@ -1131,11 +1148,23 @@ 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 {
|
func (r *Router) DefaultInterface() string {
|
||||||
return r.defaultInterface
|
return r.defaultInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) DefaultMark() int {
|
func (r *Router) DefaultMark() uint32 {
|
||||||
return r.defaultMark
|
return r.defaultMark
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ func (r *RuleSetItem) Start() error {
|
|||||||
if !loaded {
|
if !loaded {
|
||||||
return E.New("rule-set not found: ", tag)
|
return E.New("rule-set not found: ", tag)
|
||||||
}
|
}
|
||||||
|
ruleSet.IncRef()
|
||||||
r.setList = append(r.setList, ruleSet)
|
r.setList = append(r.setList, ruleSet)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -9,10 +9,13 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
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) {
|
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)
|
var _ adapter.RuleSetStartContext = (*RuleSetStartContext)(nil)
|
||||||
|
|
||||||
type RuleSetStartContext struct {
|
type RuleSetStartContext struct {
|
||||||
|
@ -9,16 +9,23 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
||||||
|
|
||||||
type LocalRuleSet struct {
|
type LocalRuleSet struct {
|
||||||
|
tag string
|
||||||
rules []adapter.HeadlessRule
|
rules []adapter.HeadlessRule
|
||||||
metadata adapter.RuleSetMetadata
|
metadata adapter.RuleSetMetadata
|
||||||
|
refs atomic.Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) {
|
func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) {
|
||||||
@ -58,16 +65,11 @@ func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleS
|
|||||||
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||||
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
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 {
|
func (s *LocalRuleSet) Name() string {
|
||||||
for _, rule := range s.rules {
|
return s.tag
|
||||||
if rule.Match(metadata) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) String() string {
|
func (s *LocalRuleSet) String() string {
|
||||||
@ -78,10 +80,51 @@ func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.Ru
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) PostStart() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {
|
func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {
|
||||||
return s.metadata
|
return s.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) Close() error {
|
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
|
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
|
||||||
|
}
|
||||||
|
@ -8,20 +8,26 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
|
var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
|
||||||
@ -40,6 +46,9 @@ type RemoteRuleSet struct {
|
|||||||
lastEtag string
|
lastEtag string
|
||||||
updateTicker *time.Ticker
|
updateTicker *time.Ticker
|
||||||
pauseManager pause.Manager
|
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 {
|
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 {
|
func (s *RemoteRuleSet) Name() string {
|
||||||
for _, rule := range s.rules {
|
return s.options.Tag
|
||||||
if rule.Match(metadata) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) String() string {
|
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)
|
s.updateTicker = time.NewTicker(s.updateInterval)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) PostStart() error {
|
||||||
go s.loopUpdate()
|
go s.loopUpdate()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -116,6 +124,38 @@ func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
|
|||||||
return s.metadata
|
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 {
|
func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
||||||
var (
|
var (
|
||||||
plainRuleSet option.PlainRuleSet
|
plainRuleSet option.PlainRuleSet
|
||||||
@ -148,6 +188,12 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
|||||||
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||||
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
|
s.callbackAccess.Lock()
|
||||||
|
callbacks := s.callbacks.Array()
|
||||||
|
s.callbackAccess.Unlock()
|
||||||
|
for _, callback := range callbacks {
|
||||||
|
callback(s)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +202,8 @@ func (s *RemoteRuleSet) loopUpdate() {
|
|||||||
err := s.fetchOnce(s.ctx, nil)
|
err := s.fetchOnce(s.ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
|
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
|
||||||
|
} else if s.refs.Load() == 0 {
|
||||||
|
s.rules = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
@ -168,6 +216,8 @@ func (s *RemoteRuleSet) loopUpdate() {
|
|||||||
err := s.fetchOnce(s.ctx, nil)
|
err := s.fetchOnce(s.ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
|
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 {
|
func (s *RemoteRuleSet) Close() error {
|
||||||
|
s.rules = nil
|
||||||
s.updateTicker.Stop()
|
s.updateTicker.Stop()
|
||||||
s.cancel()
|
s.cancel()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
for _, rule := range s.rules {
|
||||||
|
if rule.Match(metadata) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user