auto-redirect: Add route address set support for nftables

This commit is contained in:
世界 2024-06-12 15:20:13 +08:00
parent 468d78da49
commit 98e8fce9cd
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
17 changed files with 526 additions and 111 deletions

View File

@ -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,21 @@ 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
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
View File

@ -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 {

View File

@ -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

View 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
}

View 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
}

View File

@ -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())

10
go.mod
View File

@ -26,14 +26,14 @@ require (
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
github.com/sagernet/quic-go v0.45.0-beta.2 github.com/sagernet/quic-go v0.45.0-beta.2
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.5.0-alpha.10 github.com/sagernet/sing v0.5.0-alpha.10.0.20240615025518-3de31664ce39
github.com/sagernet/sing-dns v0.3.0-beta.5 github.com/sagernet/sing-dns v0.3.0-beta.5
github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-mux v0.2.0
github.com/sagernet/sing-quic v0.2.0-beta.9 github.com/sagernet/sing-quic v0.2.0-beta.9
github.com/sagernet/sing-shadowsocks v0.2.6 github.com/sagernet/sing-shadowsocks v0.2.6
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.1.4 github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.4.0-beta.9 github.com/sagernet/sing-tun v0.4.0-beta.9.0.20240616073727-85f5f2dd587d
github.com/sagernet/sing-vmess v0.1.8 github.com/sagernet/sing-vmess v0.1.8
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
@ -81,14 +81,14 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // 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/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.2 // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.16.0 // indirect

20
go.sum
View File

@ -104,17 +104,17 @@ github.com/sagernet/gomobile v0.1.3 h1:ohjIb1Ou2+1558PnZour3od69suSuvkdSVOlO1tC4
github.com/sagernet/gomobile v0.1.3/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= github.com/sagernet/gomobile v0.1.3/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 h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= github.com/sagernet/gvisor v0.0.0-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-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.2 h1:yKqMl4Dpb6nKxAmlE6fXjJRlLO2c1f2wyNFBg4hBr8w= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.2/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.45.0-beta.2 h1:nWq9KJTR+cGU8UU4E20XNjdM6QgbLkBgpq+NCExg5RY= github.com/sagernet/quic-go v0.45.0-beta.2 h1:nWq9KJTR+cGU8UU4E20XNjdM6QgbLkBgpq+NCExg5RY=
github.com/sagernet/quic-go v0.45.0-beta.2/go.mod h1:rs3XCo3SQ2sB96NtaKnEyq+ZkyaKWL51BvIW3veaiWw= github.com/sagernet/quic-go v0.45.0-beta.2/go.mod h1:rs3XCo3SQ2sB96NtaKnEyq+ZkyaKWL51BvIW3veaiWw=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.5.0-alpha.10 h1:kuHl10gpjbKQAdQfyogQU3u0CVnpqC3wrAHe/+BFaXc= github.com/sagernet/sing v0.5.0-alpha.10.0.20240615025518-3de31664ce39 h1:Q1Hlje8FURQLHyDq8V2myk/YG+0RPdcnTRZ7Of9/iv8=
github.com/sagernet/sing v0.5.0-alpha.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.5.0-alpha.10.0.20240615025518-3de31664ce39/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.3.0-beta.5 h1:lX+wfnBVaOlSd7+GBgb431Tt/gmYwJXSHvS1HutfnD4= github.com/sagernet/sing-dns v0.3.0-beta.5 h1:lX+wfnBVaOlSd7+GBgb431Tt/gmYwJXSHvS1HutfnD4=
github.com/sagernet/sing-dns v0.3.0-beta.5/go.mod h1:qeO/lOUK/c3Zczp5a1VO13fbmolaM8xGKCUXtaX0/NQ= github.com/sagernet/sing-dns v0.3.0-beta.5/go.mod h1:qeO/lOUK/c3Zczp5a1VO13fbmolaM8xGKCUXtaX0/NQ=
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
@ -127,8 +127,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-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 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.4.0-beta.9 h1:/5hXQ0u7tHtngfXozRc+o/gt6zfHBHMOwSIHXF0+S3I= github.com/sagernet/sing-tun v0.4.0-beta.9.0.20240616073727-85f5f2dd587d h1:ppBKR8H4x64nbtselcGdGOrIMJUIXQL135s5tNUcQMQ=
github.com/sagernet/sing-tun v0.4.0-beta.9/go.mod h1:uoRiCzWHzHLw/angVqXDzUNiQcMRl/ZrElJryQLJFhY= github.com/sagernet/sing-tun v0.4.0-beta.9.0.20240616073727-85f5f2dd587d/go.mod h1:YgaSM4cm+YIn6erBZN/eF+sW7I27BfBt91EWGo53MME=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc= github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA= github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
@ -172,8 +172,8 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 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 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=

View File

@ -3,6 +3,7 @@ package inbound
import ( import (
"context" "context"
"net" "net"
"net/netip"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@ -20,6 +21,9 @@ 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)
@ -39,9 +43,69 @@ type Tun struct {
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 +132,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 +159,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()),
@ -112,11 +196,41 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
Context: ctx, Context: ctx,
Handler: inbound, Handler: inbound,
Logger: logger, Logger: logger,
NetworkMonitor: router.NetworkMonitor(),
InterfaceFinder: router.InterfaceFinder(),
TableName: "sing-box", TableName: "sing-box",
DisableNFTables: dErr == nil && disableNFTables, 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")
}
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 +255,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 +329,43 @@ 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)
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
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,

View File

@ -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"`

View File

@ -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 {

View File

@ -6,15 +6,18 @@ 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"`
IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"`
AutoRedirect bool `json:"auto_redirect,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"` StrictRoute bool `json:"strict_route,omitempty"`
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` RouteAddress Listable[netip.Prefix] `json:"route_address,omitempty"`
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` RouteAddressSet Listable[string] `json:"route_address_set,omitempty"`
Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` RouteExcludeAddress Listable[netip.Prefix] `json:"route_exclude_address,omitempty"`
Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` RouteExcludeAddressSet Listable[string] `json:"route_exclude_address_set,omitempty"`
IncludeInterface Listable[string] `json:"include_interface,omitempty"` IncludeInterface Listable[string] `json:"include_interface,omitempty"`
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"` ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
IncludeUID Listable[uint32] `json:"include_uid,omitempty"` IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
@ -29,4 +32,17 @@ type TunInboundOptions struct {
Stack string `json:"stack,omitempty"` Stack string `json:"stack,omitempty"`
Platform *TunPlatformOptions `json:"platform,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"`
} }

View File

@ -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
@ -728,6 +729,14 @@ func (r *Router) PostStart() error {
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 +1140,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
} }

View File

@ -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

View File

@ -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 {

View File

@ -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 {
@ -82,6 +84,43 @@ 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
}

View File

@ -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 {
@ -116,6 +120,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 +184,16 @@ 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)
}
if s.refs.Load() == 0 {
s.rules = nil
runtime.GC()
}
return nil return nil
} }
@ -253,7 +299,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
}