Compare commits

...

47 Commits

Author SHA1 Message Date
世界
0bccaedc3c Bump version 2024-06-08 21:43:04 +08:00
世界
be15ce5054 Fix parse UDP DNS server with addr:port address 2024-06-08 21:39:47 +08:00
世界
2d0326982b Stop passing device sleep events on Android and Apple platforms 2024-06-08 21:39:47 +08:00
世界
98c1dd17a0 Fix rule-set start order 2024-06-08 21:39:47 +08:00
renovate[bot]
bdba2365de [dependencies] Update goreleaser/goreleaser-action action to v6 2024-06-08 18:58:22 +08:00
世界
ce0da5b557 Fix typo 2024-06-08 18:58:14 +08:00
世界
3853201412 Bump version 2024-06-07 19:07:46 +08:00
世界
7003ef40a3 Fix wireguard start 2024-06-07 19:07:46 +08:00
世界
59ec92228c Fix repeatedly no route logs 2024-06-07 15:03:48 +08:00
世界
0eeb2da323 Fix reset HTTP3 DNS transport 2024-06-06 22:28:43 +08:00
世界
977b0fac02 Fix get source address from X-Forwarded-For 2024-06-06 22:21:37 +08:00
世界
51964801ff Fix enforced power listener on windows 2024-06-06 22:20:23 +08:00
世界
e08c052fc9 Fix wireguard client bind 2024-06-06 22:20:21 +08:00
世界
53927d8bbd Remove logs in router initialize 2024-06-06 22:20:19 +08:00
世界
968b9bc217 Fix crash on *bsd 2024-06-06 22:20:17 +08:00
lgjint
69dc87aa6d Fix set KDE6 system proxy 2024-06-06 22:20:14 +08:00
Fei1Yang
4193df375f build: Remove vendor in RPM packages 2024-05-27 19:28:15 +08:00
世界
5ff7006326 Bump version 2024-05-25 11:30:43 +08:00
世界
a89107ea9d documentation: Bump version 2024-05-23 14:57:45 +08:00
世界
9ffdbba2ed documentation: Add manuel for mitigating tunnelvision attacks 2024-05-23 14:57:27 +08:00
世界
65c71049ea documentation: Update DNS manual 2024-05-23 14:57:26 +08:00
世界
7d4e6a7f4e dialer: Allow nil router 2024-05-23 14:57:26 +08:00
世界
d612620c5d Add rule-set match command 2024-05-23 14:57:26 +08:00
世界
8a9a77a438 Add bypass_domain and search_domain platform HTTP proxy options 2024-05-23 14:57:26 +08:00
世界
a2098c18e1 Handle includeAllNetworks 2024-05-23 14:57:26 +08:00
世界
cf2181dd3a Update gVisor to 20240422.0 2024-05-23 14:57:15 +08:00
世界
5899e95ff1 Update quic-go to v0.43.1 2024-05-21 15:12:05 +08:00
世界
d7160c19cf Fixed order for Clash modes 2024-05-21 15:12:05 +08:00
世界
da9e22b4e6 Add custom prefix support in EDNS0 client subnet options 2024-05-21 15:12:05 +08:00
气息
0e120f8a44 Fix DNS exchange index
Signed-off-by: 气息 <qdshizh@gmail.com>
2024-05-21 15:12:05 +08:00
dyhkwong
d918863ac5 Always disable cache for fake-ip servers 2024-05-21 15:12:04 +08:00
PuerNya
2ae192305c Always disable cache for fake-ip DNS transport if independent_cache disabled 2024-05-21 15:12:03 +08:00
世界
71d1879bd6 Fix missing rule_set_ipcidr_match_source item in DNS rules 2024-05-21 15:12:03 +08:00
世界
917514e09f Improve DNS truncate behavior 2024-05-21 15:12:03 +08:00
世界
5327aeaea4 Fix DNS fallthrough incorrectly 2024-05-21 15:12:03 +08:00
世界
93ae3f7a1e Add rejected DNS response cache support 2024-05-21 15:12:03 +08:00
世界
f24a2aed7d Add support for client-subnet DNS options 2024-05-21 15:12:03 +08:00
世界
0517ceef76 Add address filter support for DNS rules 2024-05-21 15:12:02 +08:00
世界
830ea46932 Fix timezone for Android and iOS 2024-05-21 15:11:52 +08:00
世界
cd0fcd5ddc Improve loopback detector 2024-05-21 15:11:52 +08:00
世界
003176f069 Remove unused fakeip packet conn 2024-05-21 15:11:52 +08:00
世界
71d92518c1 Set the default TCP keep alive period 2024-05-21 15:11:52 +08:00
世界
b5dcd6bf59 Migrate ntp service to library 2024-05-21 15:11:52 +08:00
世界
11c7b4a866 Handle Windows power events 2024-05-21 15:11:52 +08:00
世界
ee14135298 Improve domain suffix match behavior
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.

This change modifies the behavior of `domain_suffix`: If the rule value is prefixed with `.`,
the behavior is unchanged, otherwise it matches `(domain|.+\.domain)` instead.
2024-05-21 15:11:41 +08:00
世界
cbcf005f37 Remove PROCESS_NAME_NATIVE dwFlag in process query output
The `process_path` rule of sing-box is inherited from Clash,
the original code uses the local system's path format (e.g. `\Device\HarddiskVolume1\folder\program.exe`),
but when the device has multiple disks, the HarddiskVolume serial number is not stable.

This change make QueryFullProcessImageNameW output a Win32 path (such as `C:\folder\program.exe`),
which will disrupt the existing `process_path` use cases in Windows.
2024-05-18 17:22:14 +08:00
世界
daee0b154e badtls: Support uTLS and TLS ECH for read waiter 2024-05-18 17:22:14 +08:00
104 changed files with 2199 additions and 1222 deletions

View File

@@ -26,7 +26,7 @@ jobs:
EOF EOF
echo "HOME=$HOME" >> "$GITHUB_ENV" echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Publish release - name: Publish release
uses: goreleaser/goreleaser-action@v5 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest version: latest

View File

@@ -36,7 +36,6 @@ nfpms:
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
builds: builds:
- main - main
vendor: sagernet
homepage: https://sing-box.sagernet.org/ homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu> maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform. description: The universal proxy platform.

View File

@@ -113,7 +113,6 @@ nfpms:
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
builds: builds:
- main - main
vendor: sagernet
homepage: https://sing-box.sagernet.org/ homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu> maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform. description: The universal proxy platform.

View File

@@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-dns"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
) )
@@ -30,6 +31,9 @@ type CacheFile interface {
StoreFakeIP() bool StoreFakeIP() bool
FakeIPStorage FakeIPStorage
StoreRDRC() bool
dns.RDRCStore
LoadMode() string LoadMode() string
StoreMode(mode string) error StoreMode(mode string) error
LoadSelected(group string) string LoadSelected(group string) string

View File

@@ -51,11 +51,13 @@ type InboundContext struct {
// rule cache // rule cache
IPCIDRMatchSource bool IPCIDRMatchSource bool
SourceAddressMatch bool SourceAddressMatch bool
SourcePortMatch bool SourcePortMatch bool
DestinationAddressMatch bool DestinationAddressMatch bool
DestinationPortMatch bool DestinationPortMatch bool
DidMatch bool
IgnoreDestinationIPCIDRMatch bool
} }
func (c *InboundContext) ResetRuleCache() { func (c *InboundContext) ResetRuleCache() {
@@ -64,6 +66,7 @@ func (c *InboundContext) ResetRuleCache() {
c.SourcePortMatch = false c.SourcePortMatch = false
c.DestinationAddressMatch = false c.DestinationAddressMatch = false
c.DestinationPortMatch = false c.DestinationPortMatch = false
c.DidMatch = false
} }
type inboundContextKey struct{} type inboundContextKey struct{}

View File

@@ -71,6 +71,7 @@ func RouterFromContext(ctx context.Context) Router {
type HeadlessRule interface { type HeadlessRule interface {
Match(metadata *InboundContext) bool Match(metadata *InboundContext) bool
String() string
} }
type Rule interface { type Rule interface {
@@ -79,18 +80,19 @@ type Rule interface {
Type() string Type() string
UpdateGeosite() error UpdateGeosite() error
Outbound() string Outbound() string
String() string
} }
type DNSRule interface { type DNSRule interface {
Rule Rule
DisableCache() bool DisableCache() bool
RewriteTTL() *uint32 RewriteTTL() *uint32
ClientSubnet() *netip.Prefix
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext) bool
} }
type RuleSet interface { type RuleSet interface {
StartContext(ctx context.Context, startContext RuleSetStartContext) error StartContext(ctx context.Context, startContext RuleSetStartContext) error
PostStart() error
Metadata() RuleSetMetadata Metadata() RuleSetMetadata
Close() error Close() error
HeadlessRule HeadlessRule
@@ -99,6 +101,7 @@ type RuleSet interface {
type RuleSetMetadata struct { type RuleSetMetadata struct {
ContainsProcessRule bool ContainsProcessRule bool
ContainsWIFIRule bool ContainsWIFIRule bool
ContainsIPCIDRRule bool
} }
type RuleSetStartContext interface { type RuleSetStartContext interface {

View File

@@ -0,0 +1,86 @@
package main
import (
"bytes"
"io"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var flagRuleSetMatchFormat string
var commandRuleSetMatch = &cobra.Command{
Use: "match <rule-set path> <domain>",
Short: "Check if a domain matches the rule set",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
err := ruleSetMatch(args[0], args[1])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSetMatch.Flags().StringVarP(&flagRuleSetMatchFormat, "format", "f", "source", "rule-set format")
commandRuleSet.AddCommand(commandRuleSetMatch)
}
func ruleSetMatch(sourcePath string, domain string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return E.Cause(err, "read rule-set")
}
}
content, err := io.ReadAll(reader)
if err != nil {
return E.Cause(err, "read rule-set")
}
var plainRuleSet option.PlainRuleSet
switch flagRuleSetMatchFormat {
case C.RuleSetFormatSource:
var compat option.PlainRuleSetCompat
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
plainRuleSet = compat.Upgrade()
case C.RuleSetFormatBinary:
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
if err != nil {
return err
}
default:
return E.New("unknown rule set format: ", flagRuleSetMatchFormat)
}
for i, ruleOptions := range plainRuleSet.Rules {
var currentRule adapter.HeadlessRule
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
if err != nil {
return E.Cause(err, "parse rule_set.rules.[", i, "]")
}
if currentRule.Match(&adapter.InboundContext{
Domain: domain,
}) {
println("match rules.[", i, "]: "+currentRule.String())
}
}
return nil
}

View File

@@ -4,6 +4,8 @@ package badtls
import ( import (
"bytes" "bytes"
"context"
"net"
"os" "os"
"reflect" "reflect"
"sync" "sync"
@@ -18,20 +20,32 @@ import (
var _ N.ReadWaiter = (*ReadWaitConn)(nil) var _ N.ReadWaiter = (*ReadWaitConn)(nil)
type ReadWaitConn struct { type ReadWaitConn struct {
*tls.STDConn tls.Conn
halfAccess *sync.Mutex halfAccess *sync.Mutex
rawInput *bytes.Buffer rawInput *bytes.Buffer
input *bytes.Reader input *bytes.Reader
hand *bytes.Buffer hand *bytes.Buffer
readWaitOptions N.ReadWaitOptions readWaitOptions N.ReadWaitOptions
tlsReadRecord func() error
tlsHandlePostHandshakeMessage func() error
} }
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) { func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
stdConn, isSTDConn := conn.(*tls.STDConn) var (
if !isSTDConn { loaded bool
tlsReadRecord func() error
tlsHandlePostHandshakeMessage func() error
)
for _, tlsCreator := range tlsRegistry {
loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn)
if loaded {
break
}
}
if !loaded {
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }
rawConn := reflect.Indirect(reflect.ValueOf(stdConn)) rawConn := reflect.Indirect(reflect.ValueOf(conn))
rawHalfConn := rawConn.FieldByName("in") rawHalfConn := rawConn.FieldByName("in")
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct { if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half conn") return nil, E.New("badtls: invalid half conn")
@@ -57,11 +71,13 @@ func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
} }
hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr())) hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
return &ReadWaitConn{ return &ReadWaitConn{
STDConn: stdConn, Conn: conn,
halfAccess: halfAccess, halfAccess: halfAccess,
rawInput: rawInput, rawInput: rawInput,
input: input, input: input,
hand: hand, hand: hand,
tlsReadRecord: tlsReadRecord,
tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage,
}, nil }, nil
} }
@@ -71,19 +87,19 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy
} }
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) { func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
err = c.Handshake() err = c.HandshakeContext(context.Background())
if err != nil { if err != nil {
return return
} }
c.halfAccess.Lock() c.halfAccess.Lock()
defer c.halfAccess.Unlock() defer c.halfAccess.Unlock()
for c.input.Len() == 0 { for c.input.Len() == 0 {
err = tlsReadRecord(c.STDConn) err = c.tlsReadRecord()
if err != nil { if err != nil {
return return
} }
for c.hand.Len() > 0 { for c.hand.Len() > 0 {
err = tlsHandlePostHandshakeMessage(c.STDConn) err = c.tlsHandlePostHandshakeMessage()
if err != nil { if err != nil {
return return
} }
@@ -100,7 +116,7 @@ func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 && if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
// recordType(c.rawInput.Bytes()[0]) == recordTypeAlert { // recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
c.rawInput.Bytes()[0] == 21 { c.rawInput.Bytes()[0] == 21 {
_ = tlsReadRecord(c.STDConn) _ = c.tlsReadRecord()
// return n, err // will be io.EOF on closeNotify // return n, err // will be io.EOF on closeNotify
} }
@@ -109,11 +125,27 @@ func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
} }
func (c *ReadWaitConn) Upstream() any { func (c *ReadWaitConn) Upstream() any {
return c.STDConn return c.Conn
} }
//go:linkname tlsReadRecord crypto/tls.(*Conn).readRecord var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
func tlsReadRecord(c *tls.STDConn) error
//go:linkname tlsHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage func init() {
func tlsHandlePostHandshakeMessage(c *tls.STDConn) error tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
tlsConn, loaded := conn.(*tls.STDConn)
if !loaded {
return
}
return true, func() error {
return stdTLSReadRecord(tlsConn)
}, func() error {
return stdTLSHandlePostHandshakeMessage(tlsConn)
}
})
}
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
func stdTLSReadRecord(c *tls.STDConn) error
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error

View File

@@ -0,0 +1,31 @@
//go:build go1.21 && !without_badtls && with_ech
package badtls
import (
"net"
_ "unsafe"
"github.com/sagernet/cloudflare-tls"
"github.com/sagernet/sing/common"
)
func init() {
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
tlsConn, loaded := common.Cast[*tls.Conn](conn)
if !loaded {
return
}
return true, func() error {
return echReadRecord(tlsConn)
}, func() error {
return echHandlePostHandshakeMessage(tlsConn)
}
})
}
//go:linkname echReadRecord github.com/sagernet/cloudflare-tls.(*Conn).readRecord
func echReadRecord(c *tls.Conn) error
//go:linkname echHandlePostHandshakeMessage github.com/sagernet/cloudflare-tls.(*Conn).handlePostHandshakeMessage
func echHandlePostHandshakeMessage(c *tls.Conn) error

View File

@@ -0,0 +1,31 @@
//go:build go1.21 && !without_badtls && with_utls
package badtls
import (
"net"
_ "unsafe"
"github.com/sagernet/sing/common"
"github.com/sagernet/utls"
)
func init() {
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
tlsConn, loaded := common.Cast[*tls.UConn](conn)
if !loaded {
return
}
return true, func() error {
return utlsReadRecord(tlsConn.Conn)
}, func() error {
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
}
})
}
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
func utlsReadRecord(c *tls.Conn) error
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
func utlsHandlePostHandshakeMessage(c *tls.Conn) error

View File

@@ -32,14 +32,20 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
var dialer net.Dialer var dialer net.Dialer
var listener net.ListenConfig var listener net.ListenConfig
if options.BindInterface != "" { if options.BindInterface != "" {
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1) var interfaceFinder control.InterfaceFinder
if router != nil {
interfaceFinder = router.InterfaceFinder()
} else {
interfaceFinder = control.NewDefaultInterfaceFinder()
}
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
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)
} else if router.AutoDetectInterface() { } else if router != nil && router.AutoDetectInterface() {
bindFunc := router.AutoDetectInterfaceFunc() bindFunc := router.AutoDetectInterfaceFunc()
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)
} else if router.DefaultInterface() != "" { } else if router != nil && router.DefaultInterface() != "" {
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1) bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
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)
@@ -47,7 +53,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
if options.RoutingMark != 0 { 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.DefaultMark() != 0 { } 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()))
} }
@@ -63,6 +69,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
} else { } else {
dialer.Timeout = C.TCPTimeout dialer.Timeout = C.TCPTimeout
} }
// TODO: Add an option to customize the keep alive period
dialer.KeepAlive = C.TCPKeepAliveInitial
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
var udpFragment bool var udpFragment bool
if options.UDPFragment != nil { if options.UDPFragment != nil {
udpFragment = *options.UDPFragment udpFragment = *options.UDPFragment

View File

@@ -13,6 +13,9 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error)
if options.IsWireGuardListener { if options.IsWireGuardListener {
return NewDefault(router, options) return NewDefault(router, options)
} }
if router == nil {
return NewDefault(nil, options)
}
var ( var (
dialer N.Dialer dialer N.Dialer
err error err error

View File

@@ -223,7 +223,7 @@ func getExecPathFromPID(pid uint32) (string, error) {
r1, _, err := syscall.SyscallN( r1, _, err := syscall.SyscallN(
procQueryFullProcessImageNameW.Addr(), procQueryFullProcessImageNameW.Addr(),
uintptr(h), uintptr(h),
uintptr(1), uintptr(0),
uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)), uintptr(unsafe.Pointer(&size)),
) )

View File

@@ -16,30 +16,40 @@ import (
) )
type LinuxSystemProxy struct { type LinuxSystemProxy struct {
hasGSettings bool hasGSettings bool
hasKWriteConfig5 bool kWriteConfigCmd string
sudoUser string sudoUser string
serverAddr M.Socksaddr serverAddr M.Socksaddr
supportSOCKS bool supportSOCKS bool
isEnabled bool isEnabled bool
} }
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) { func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
hasGSettings := common.Error(exec.LookPath("gsettings")) == nil hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
hasKWriteConfig5 := common.Error(exec.LookPath("kwriteconfig5")) == nil kWriteConfigCmds := []string{
"kwriteconfig5",
"kwriteconfig6",
}
var kWriteConfigCmd string
for _, cmd := range kWriteConfigCmds {
if common.Error(exec.LookPath(cmd)) == nil {
kWriteConfigCmd = cmd
break
}
}
var sudoUser string var sudoUser string
if os.Getuid() == 0 { if os.Getuid() == 0 {
sudoUser = os.Getenv("SUDO_USER") sudoUser = os.Getenv("SUDO_USER")
} }
if !hasGSettings && !hasKWriteConfig5 { if !hasGSettings && kWriteConfigCmd == "" {
return nil, E.New("unsupported desktop environment") return nil, E.New("unsupported desktop environment")
} }
return &LinuxSystemProxy{ return &LinuxSystemProxy{
hasGSettings: hasGSettings, hasGSettings: hasGSettings,
hasKWriteConfig5: hasKWriteConfig5, kWriteConfigCmd: kWriteConfigCmd,
sudoUser: sudoUser, sudoUser: sudoUser,
serverAddr: serverAddr, serverAddr: serverAddr,
supportSOCKS: supportSOCKS, supportSOCKS: supportSOCKS,
}, nil }, nil
} }
@@ -70,8 +80,8 @@ func (p *LinuxSystemProxy) Enable() error {
return err return err
} }
} }
if p.hasKWriteConfig5 { if p.kWriteConfigCmd != "" {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1") err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
if err != nil { if err != nil {
return err return err
} }
@@ -83,7 +93,7 @@ func (p *LinuxSystemProxy) Enable() error {
if err != nil { if err != nil {
return err return err
} }
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0") err = p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
if err != nil { if err != nil {
return err return err
} }
@@ -103,8 +113,8 @@ func (p *LinuxSystemProxy) Disable() error {
return err return err
} }
} }
if p.hasKWriteConfig5 { if p.kWriteConfigCmd != "" {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0") err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
if err != nil { if err != nil {
return err return err
} }
@@ -150,7 +160,7 @@ func (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {
proxyUrl = "http://" + p.serverAddr.String() proxyUrl = "http://" + p.serverAddr.String()
} }
err := p.runAsUser( err := p.runAsUser(
"kwriteconfig5", p.kWriteConfigCmd,
"--file", "--file",
"kioslaverc", "kioslaverc",
"--group", "--group",

View File

@@ -27,11 +27,10 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
return quic.DialEarly(ctx, conn, addr, c.config, config) return quic.DialEarly(ctx, conn, addr, c.config, config)
} }
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config, enableDatagrams bool) http.RoundTripper { func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
return &http3.RoundTripper{ return &http3.RoundTripper{
TLSClientConfig: c.config, TLSClientConfig: c.config,
QuicConfig: quicConfig, QUICConfig: quicConfig,
EnableDatagrams: enableDatagrams,
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg) quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg)
if err != nil { if err != nil {

5
constant/quic.go Normal file
View File

@@ -0,0 +1,5 @@
//go:build with_quic
package constant
const WithQUIC = true

5
constant/quic_stub.go Normal file
View File

@@ -0,0 +1,5 @@
//go:build !with_quic
package constant
const WithQUIC = false

View File

@@ -3,6 +3,8 @@ package constant
import "time" import "time"
const ( const (
TCPKeepAliveInitial = 10 * time.Minute
TCPKeepAliveInterval = 75 * time.Second
TCPTimeout = 5 * time.Second TCPTimeout = 5 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second DNSTimeout = 10 * time.Second

View File

@@ -2,16 +2,127 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
### 1.9.2
* Fixes and improvements
### 1.9.1
* Fixes and improvements
### 1.9.0
* Fixes and improvements
Important changes since 1.8:
* `domain_suffix` behavior update **1**
* `process_path` format update on Windows **2**
* Add address filter DNS rule items **3**
* Add support for `client-subnet` DNS options **4**
* Add rejected DNS response cache support **5**
* Add `bypass_domain` and `search_domain` platform HTTP proxy options **6**
* Fix missing `rule_set_ipcidr_match_source` item in DNS rules **7**
* Handle Windows power events
* Always disable cache for fake-ip DNS transport if `dns.independent_cache` disabled
* Improve DNS truncate behavior
* Update Hysteria protocol
* Update quic-go to v0.43.1
* Update gVisor to 20240422.0
* Mitigating TunnelVision attacks **8**
**1**:
See [Migration](/migration/#domain_suffix-behavior-update).
**2**:
See [Migration](/migration/#process_path-format-update-on-windows).
**3**:
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
if using this method.
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
**4**:
See [DNS](/configuration/dns), [DNS Server](/configuration/dns/server) and [DNS Rules](/configuration/dns/rule).
Since this feature makes the scenario mentioned in `alpha.1` no longer leak DNS requests,
the [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) has been updated.
**5**:
The new feature allows you to cache the check results of
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
**6**:
See [TUN](/configuration/inbound/tun) inbound.
**7**:
See [DNS Rule](/configuration/dns/rule/).
**8**:
See [TunnelVision](/manual/misc/tunnelvision).
#### 1.9.0-rc.22
* Fixes and improvements
#### 1.9.0-rc.20
* Prioritize `*_route_address` in linux auto-route
* Fix `*_route_address` in darwin auto-route
#### 1.8.14 #### 1.8.14
* Fix hysteria2 panic * Fix hysteria2 panic
* Fixes and improvements * Fixes and improvements
#### 1.9.0-rc.18
* Add custom prefix support in EDNS0 client subnet options
* Fix hysteria2 crash
* Fix `store_rdrc` corrupted
* Update quic-go to v0.43.1
* Fixes and improvements
#### 1.9.0-rc.16
* Mitigating TunnelVision attacks **1**
* Fixes and improvements
**1**:
See [TunnelVision](/manual/misc/tunnelvision).
#### 1.9.0-rc.15
* Fixes and improvements
#### 1.8.13 #### 1.8.13
* Fix fake-ip mapping * Fix fake-ip mapping
* Fixes and improvements * Fixes and improvements
#### 1.9.0-rc.14
* Fixes and improvements
#### 1.9.0-rc.13
* Update Hysteria protocol
* Update quic-go to v0.43.0
* Update gVisor to 20240422.0
* Fixes and improvements
#### 1.8.12 #### 1.8.12
* Now we have official APT and DNF repositories **1** * Now we have official APT and DNF repositories **1**
@@ -22,6 +133,10 @@ icon: material/alert-decagram
Including stable and beta versions, see https://sing-box.sagernet.org/installation/package-manager/ Including stable and beta versions, see https://sing-box.sagernet.org/installation/package-manager/
#### 1.9.0-rc.11
* Fixes and improvements
#### 1.8.11 #### 1.8.11
* Fixes and improvements * Fixes and improvements
@@ -30,6 +145,24 @@ Including stable and beta versions, see https://sing-box.sagernet.org/installati
* Fixes and improvements * Fixes and improvements
#### 1.9.0-beta.17
* Update `quic-go` to v0.42.0
* Fixes and improvements
#### 1.9.0-beta.16
* Fixes and improvements
_Our Testflight distribution has been temporarily blocked by Apple (possibly due to too many beta versions)
and you cannot join the test, install or update the sing-box beta app right now.
Please wait patiently for processing._
#### 1.9.0-beta.14
* Update gVisor to 20240212.0-65-g71212d503
* Fixes and improvements
#### 1.8.9 #### 1.8.9
* Fixes and improvements * Fixes and improvements
@@ -38,14 +171,125 @@ Including stable and beta versions, see https://sing-box.sagernet.org/installati
* Fixes and improvements * Fixes and improvements
#### 1.9.0-beta.7
* Fixes and improvements
#### 1.9.0-beta.6
* Fix address filter DNS rule items **1**
* Fix DNS outbound responding with wrong data
* Fixes and improvements
**1**:
Fixed an issue where address filter DNS rule was incorrectly rejected under certain circumstances.
If you have enabled `store_rdrc` to save results, consider clearing the cache file.
#### 1.8.7 #### 1.8.7
* Fixes and improvements * Fixes and improvements
#### 1.9.0-alpha.15
* Fixes and improvements
#### 1.9.0-alpha.14
* Improve DNS truncate behavior
* Fixes and improvements
#### 1.9.0-alpha.13
* Fixes and improvements
#### 1.8.6 #### 1.8.6
* Fixes and improvements * Fixes and improvements
#### 1.9.0-alpha.12
* Handle Windows power events
* Always disable cache for fake-ip DNS transport if `dns.independent_cache` disabled
* Fixes and improvements
#### 1.9.0-alpha.11
* Fix missing `rule_set_ipcidr_match_source` item in DNS rules **1**
* Fixes and improvements
**1**:
See [DNS Rule](/configuration/dns/rule/).
#### 1.9.0-alpha.10
* Add `bypass_domain` and `search_domain` platform HTTP proxy options **1**
* Fixes and improvements
**1**:
See [TUN](/configuration/inbound/tun) inbound.
#### 1.9.0-alpha.8
* Add rejected DNS response cache support **1**
* Fixes and improvements
**1**:
The new feature allows you to cache the check results of
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
#### 1.9.0-alpha.7
* Update gVisor to 20240206.0
* Fixes and improvements
#### 1.9.0-alpha.6
* Fixes and improvements
#### 1.9.0-alpha.3
* Update `quic-go` to v0.41.0
* Fixes and improvements
#### 1.9.0-alpha.2
* Add support for `client-subnet` DNS options **1**
* Fixes and improvements
**1**:
See [DNS](/configuration/dns), [DNS Server](/configuration/dns/server) and [DNS Rules](/configuration/dns/rule).
Since this feature makes the scenario mentioned in `alpha.1` no longer leak DNS requests,
the [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) has been updated.
#### 1.9.0-alpha.1
* `domain_suffix` behavior update **1**
* `process_path` format update on Windows **2**
* Add address filter DNS rule items **3**
**1**:
See [Migration](/migration/#domain_suffix-behavior-update).
**2**:
See [Migration](/migration/#process_path-format-update-on-windows).
**3**:
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
if using this method.
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
#### 1.8.5 #### 1.8.5
* Fixes and improvements * Fixes and improvements
@@ -62,7 +306,7 @@ Including stable and beta versions, see https://sing-box.sagernet.org/installati
* Fixes and improvements * Fixes and improvements
#### 1.8.0 ### 1.8.0
* Fixes and improvements * Fixes and improvements
@@ -353,7 +597,7 @@ New commands manage GeoIP, Geosite and rule set resources, and help you migrate
Logical rules in route rules, DNS rules, and the new headless rule now allow nesting of logical rules. Logical rules in route rules, DNS rules, and the new headless rule now allow nesting of logical rules.
#### 1.7.0 ### 1.7.0
* Fixes and improvements * Fixes and improvements
@@ -395,7 +639,7 @@ see [TCP Brutal](/configuration/shared/tcp-brutal/) for details.
**5**: **5**:
Only supported in graphical clients on Android and iOS. Only supported in graphical clients on Android and Apple platforms.
#### 1.7.0-rc.3 #### 1.7.0-rc.3
@@ -432,7 +676,7 @@ Only supported in graphical clients on Android and iOS.
**1**: **1**:
Only supported in graphical clients on Android and iOS. Only supported in graphical clients on Android and Apple platforms.
#### 1.7.0-beta.3 #### 1.7.0-beta.3
@@ -513,7 +757,7 @@ Introduced in V2Ray 5.10.0.
The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse. The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.
#### 1.6.0 ### 1.6.0
* Fixes and improvements * Fixes and improvements
@@ -692,7 +936,7 @@ introduce new issues.
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested. None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
This update is intended to address the multi-send defects of the old implementation and may introduce new issues. This update is intended to address the multi-send defects of the old implementation and may introduce new issues.
#### 1.5.0 ### 1.5.0
* Fixes and improvements * Fixes and improvements
@@ -886,7 +1130,7 @@ All inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria`, `T
* Fixes and improvements * Fixes and improvements
#### 1.4.0 ### 1.4.0
* Fix bugs and update dependencies * Fix bugs and update dependencies
@@ -1028,7 +1272,7 @@ The old testflight link and app are no longer valid.
* Fixes and improvements * Fixes and improvements
#### 1.3.0 ### 1.3.0
* Fix bugs and update dependencies * Fix bugs and update dependencies
@@ -1220,7 +1464,7 @@ to `domain` rule.
* Flush DNS cache for macOS when tun start/close * Flush DNS cache for macOS when tun start/close
* Fix tun's DNS hijacking compatibility with systemd-resolved * Fix tun's DNS hijacking compatibility with systemd-resolved
#### 1.2.0 ### 1.2.0
* Fix bugs and update dependencies * Fix bugs and update dependencies

View File

@@ -1,3 +1,11 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [client_subnet](#client_subnet)
# DNS # DNS
### Structure ### Structure
@@ -13,6 +21,7 @@
"disable_expire": false, "disable_expire": false,
"independent_cache": false, "independent_cache": false,
"reverse_mapping": false, "reverse_mapping": false,
"client_subnet": "",
"fakeip": {} "fakeip": {}
} }
} }
@@ -21,8 +30,8 @@
### Fields ### Fields
| Key | Format | | Key | Format |
|----------|--------------------------------| |----------|---------------------------------|
| `server` | List of [DNS Server](./server/) | | `server` | List of [DNS Server](./server/) |
| `rules` | List of [DNS Rule](./rule/) | | `rules` | List of [DNS Rule](./rule/) |
| `fakeip` | [FakeIP](./fakeip/) | | `fakeip` | [FakeIP](./fakeip/) |
@@ -60,6 +69,12 @@ Stores a reverse mapping of IP addresses after responding to a DNS query in orde
Since this process relies on the act of resolving domain names by an application before making a request, it can be Since this process relies on the act of resolving domain names by an application before making a request, it can be
problematic in environments such as macOS, where DNS is proxied and cached by the system. problematic in environments such as macOS, where DNS is proxied and cached by the system.
#### fakeip #### client_subnet
[FakeIP](./fakeip/) settings. !!! question "Since sing-box 1.9.0"
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.

View File

@@ -1,3 +1,11 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [client_subnet](#client_subnet)
# DNS # DNS
### 结构 ### 结构
@@ -13,6 +21,7 @@
"disable_expire": false, "disable_expire": false,
"independent_cache": false, "independent_cache": false,
"reverse_mapping": false, "reverse_mapping": false,
"client_subnet": "",
"fakeip": {} "fakeip": {}
} }
} }
@@ -58,6 +67,16 @@
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。 由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
#### client_subnet
!!! question "自 sing-box 1.9.0 起"
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
可以被 `servers.[].client_subnet``rules.[].client_subnet` 覆盖。
#### fakeip #### fakeip
[FakeIP](./fakeip/) 设置。 [FakeIP](./fakeip/) 设置。

View File

@@ -1,7 +1,15 @@
--- ---
icon: material/alert-decagram icon: material/new-box
--- ---
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [geoip](#geoip)
:material-plus: [ip_cidr](#ip_cidr)
:material-plus: [ip_is_private](#ip_is_private)
:material-plus: [client_subnet](#client_subnet)
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"
:material-plus: [rule_set](#rule_set) :material-plus: [rule_set](#rule_set)
@@ -53,11 +61,19 @@ icon: material/alert-decagram
"source_geoip": [ "source_geoip": [
"private" "private"
], ],
"geoip": [
"cn"
],
"source_ip_cidr": [ "source_ip_cidr": [
"10.0.0.0/24", "10.0.0.0/24",
"192.168.0.1" "192.168.0.1"
], ],
"source_ip_is_private": false, "source_ip_is_private": false,
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_is_private": false,
"source_port": [ "source_port": [
12345 12345
], ],
@@ -101,13 +117,15 @@ icon: material/alert-decagram
"geoip-cn", "geoip-cn",
"geosite-cn" "geosite-cn"
], ],
"rule_set_ipcidr_match_source": false,
"invert": false, "invert": false,
"outbound": [ "outbound": [
"direct" "direct"
], ],
"server": "local", "server": "local",
"disable_cache": false, "disable_cache": false,
"rewrite_ttl": 100 "rewrite_ttl": 100,
"client_subnet": "127.0.0.1/24"
}, },
{ {
"type": "logical", "type": "logical",
@@ -115,7 +133,8 @@ icon: material/alert-decagram
"rules": [], "rules": [],
"server": "local", "server": "local",
"disable_cache": false, "disable_cache": false,
"rewrite_ttl": 100 "rewrite_ttl": 100,
"client_subnet": "127.0.0.1/24"
} }
] ]
} }
@@ -266,11 +285,9 @@ Match Clash mode.
#### wifi_ssid #### wifi_ssid
<!-- md:version 1.7.0-beta.4 -->
!!! quote "" !!! quote ""
Only supported in graphical clients on Android and iOS. Only supported in graphical clients on Android and Apple platforms.
Match WiFi SSID. Match WiFi SSID.
@@ -278,7 +295,7 @@ Match WiFi SSID.
!!! quote "" !!! quote ""
Only supported in graphical clients on Android and iOS. Only supported in graphical clients on Android and Apple platforms.
Match WiFi BSSID. Match WiFi BSSID.
@@ -288,6 +305,12 @@ Match WiFi BSSID.
Match [Rule Set](/configuration/route/#rule_set). Match [Rule Set](/configuration/route/#rule_set).
#### rule_set_ipcidr_match_source
!!! question "Since sing-box 1.9.0"
Make `ipcidr` in rule sets match the source IP.
#### invert #### invert
Invert match result. Invert match result.
@@ -312,6 +335,46 @@ Disable cache and save cache in this query.
Rewrite TTL in DNS responses. Rewrite TTL in DNS responses.
#### client_subnet
!!! question "Since sing-box 1.9.0"
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
### Address Filter Fields
Only takes effect for IP address requests. When the query results do not match the address filtering rule items, the current rule will be skipped.
!!! info ""
`ip_cidr` items in included rule sets also takes effect as an address filtering field.
!!! note ""
Enable `experimental.cache_file.store_rdrc` to cache results.
#### geoip
!!! question "Since sing-box 1.9.0"
Match GeoIP with query response.
#### ip_cidr
!!! question "Since sing-box 1.9.0"
Match IP CIDR with query response.
#### ip_is_private
!!! question "Since sing-box 1.9.0"
Match private IP with query response.
### Logical Fields ### Logical Fields
#### type #### type

View File

@@ -1,7 +1,15 @@
--- ---
icon: material/alert-decagram icon: material/new-box
--- ---
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [geoip](#geoip)
:material-plus: [ip_cidr](#ip_cidr)
:material-plus: [ip_is_private](#ip_is_private)
:material-plus: [client_subnet](#client_subnet)
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"
:material-plus: [rule_set](#rule_set) :material-plus: [rule_set](#rule_set)
@@ -53,10 +61,19 @@ icon: material/alert-decagram
"source_geoip": [ "source_geoip": [
"private" "private"
], ],
"geoip": [
"cn"
],
"source_ip_cidr": [ "source_ip_cidr": [
"10.0.0.0/24" "10.0.0.0/24",
"192.168.0.1"
], ],
"source_ip_is_private": false, "source_ip_is_private": false,
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_is_private": false,
"source_port": [ "source_port": [
12345 12345
], ],
@@ -100,19 +117,22 @@ icon: material/alert-decagram
"geoip-cn", "geoip-cn",
"geosite-cn" "geosite-cn"
], ],
"rule_set_ipcidr_match_source": false,
"invert": false, "invert": false,
"outbound": [ "outbound": [
"direct" "direct"
], ],
"server": "local", "server": "local",
"disable_cache": false "disable_cache": false,
"client_subnet": "127.0.0.1/24"
}, },
{ {
"type": "logical", "type": "logical",
"mode": "and", "mode": "and",
"rules": [], "rules": [],
"server": "local", "server": "local",
"disable_cache": false "disable_cache": false,
"client_subnet": "127.0.0.1/24"
} }
] ]
} }
@@ -265,7 +285,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! quote "" !!! quote ""
仅在 Android 与 iOS 的图形客户端中支持。 仅在 Android 与 Apple 平台图形客户端中支持。
匹配 WiFi SSID。 匹配 WiFi SSID。
@@ -273,7 +293,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! quote "" !!! quote ""
仅在 Android 与 iOS 的图形客户端中支持。 仅在 Android 与 Apple 平台图形客户端中支持。
匹配 WiFi BSSID。 匹配 WiFi BSSID。
@@ -283,6 +303,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配[规则集](/zh/configuration/route/#rule_set)。 匹配[规则集](/zh/configuration/route/#rule_set)。
#### rule_set_ipcidr_match_source
!!! question "自 sing-box 1.9.0 起"
使规则集中的 `ipcidr` 规则匹配源 IP。
#### invert #### invert
反选匹配结果。 反选匹配结果。
@@ -307,6 +333,46 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
重写 DNS 回应中的 TTL。 重写 DNS 回应中的 TTL。
#### client_subnet
!!! question "自 sing-box 1.9.0 起"
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
将覆盖 `dns.client_subnet``servers.[].client_subnet`
### 地址筛选字段
仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
!!! info ""
引用的规则集中的 `ip_cidr` 项也作为地址筛选字段生效。
!!! note ""
启用 `experimental.cache_file.store_rdrc` 以缓存结果。
#### geoip
!!! question "自 sing-box 1.9.0 起"
与查询响应匹配 GeoIP。
#### ip_cidr
!!! question "自 sing-box 1.9.0 起"
与查询相应匹配 IP CIDR。
#### ip_is_private
!!! question "自 sing-box 1.9.0 起"
与查询响应匹配非公开 IP。
### 逻辑字段 ### 逻辑字段
#### type #### type
@@ -319,4 +385,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### rules #### rules
包括的规则。 包括的规则。

View File

@@ -1,3 +1,11 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [client_subnet](#client_subnet)
### Structure ### Structure
```json ```json
@@ -5,17 +13,17 @@
"dns": { "dns": {
"servers": [ "servers": [
{ {
"tag": "google", "tag": "",
"address": "tls://dns.google", "address": "",
"address_resolver": "local", "address_resolver": "",
"address_strategy": "prefer_ipv4", "address_strategy": "",
"strategy": "ipv4_only", "strategy": "",
"detour": "direct" "detour": "",
"client_subnet": ""
} }
] ]
} }
} }
``` ```
### Fields ### Fields
@@ -80,10 +88,22 @@ Default domain strategy for resolving the domain names.
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
Take no effect if override by other settings. Take no effect if overridden by other settings.
#### detour #### detour
Tag of an outbound for connecting to the dns server. Tag of an outbound for connecting to the dns server.
Default outbound will be used if empty. Default outbound will be used if empty.
#### client_subnet
!!! question "Since sing-box 1.9.0"
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Can be overrides by `rules.[].client_subnet`.
Will overrides `dns.client_subnet`.

View File

@@ -1,3 +1,11 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [client_subnet](#client_subnet)
### 结构 ### 结构
```json ```json
@@ -5,17 +13,17 @@
"dns": { "dns": {
"servers": [ "servers": [
{ {
"tag": "google", "tag": "",
"address": "tls://dns.google", "address": "",
"address_resolver": "local", "address_resolver": "",
"address_strategy": "prefer_ipv4", "address_strategy": "",
"strategy": "ipv4_only", "strategy": "",
"detour": "direct" "detour": "",
"client_subnet": ""
} }
] ]
} }
} }
``` ```
### 字段 ### 字段
@@ -87,3 +95,15 @@ DNS 服务器的地址。
用于连接到 DNS 服务器的出站的标签。 用于连接到 DNS 服务器的出站的标签。
如果为空,将使用默认出站。 如果为空,将使用默认出站。
#### client_subnet
!!! question "自 sing-box 1.9.0 起"
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
可以被 `rules.[].client_subnet` 覆盖。
将覆盖 `dns.client_subnet`

View File

@@ -4,6 +4,11 @@ icon: material/new-box
!!! question "Since sing-box 1.8.0" !!! question "Since sing-box 1.8.0"
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [store_rdrc](#store_rdrc)
:material-plus: [rdrc_timeout](#rdrc_timeout)
### Structure ### Structure
```json ```json
@@ -11,7 +16,9 @@ icon: material/new-box
"enabled": true, "enabled": true,
"path": "", "path": "",
"cache_id": "", "cache_id": "",
"store_fakeip": false "store_fakeip": false,
"store_rdrc": false,
"rdrc_timeout": ""
} }
``` ```
@@ -29,6 +36,23 @@ Path to the cache file.
#### cache_id #### cache_id
Identifier in cache file. Identifier in the cache file
If not empty, configuration specified data will use a separate store keyed by it. If not empty, configuration specified data will use a separate store keyed by it.
#### store_fakeip
Store fakeip in the cache file
#### store_rdrc
Store rejected DNS response cache in the cache file
The check results of [Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields)
will be cached until expiration.
#### rdrc_timeout
Timeout of rejected DNS response cache.
`7d` is used by default.

View File

@@ -4,6 +4,11 @@ icon: material/new-box
!!! question "自 sing-box 1.8.0 起" !!! question "自 sing-box 1.8.0 起"
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [store_rdrc](#store_rdrc)
:material-plus: [rdrc_timeout](#rdrc_timeout)
### 结构 ### 结构
```json ```json
@@ -11,7 +16,9 @@ icon: material/new-box
"enabled": true, "enabled": true,
"path": "", "path": "",
"cache_id": "", "cache_id": "",
"store_fakeip": false "store_fakeip": false,
"store_rdrc": false,
"rdrc_timeout": ""
} }
``` ```
@@ -30,3 +37,19 @@ icon: material/new-box
缓存文件中的标识符。 缓存文件中的标识符。
如果不为空,配置特定的数据将使用由其键控的单独存储。 如果不为空,配置特定的数据将使用由其键控的单独存储。
#### store_fakeip
将 fakeip 存储在缓存文件中。
#### store_rdrc
将拒绝的 DNS 响应缓存存储在缓存文件中。
[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#_3) 的检查结果将被缓存至过期。
#### rdrc_timeout
拒绝的 DNS 响应缓存超时。
默认使用 `7d`

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"
:material-delete-alert: [store_mode](#store_mode) :material-delete-alert: [store_mode](#store_mode)

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"
:material-delete-alert: [store_mode](#store_mode) :material-delete-alert: [store_mode](#store_mode)

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
# Experimental # Experimental
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
# 实验性 # 实验性
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"

View File

@@ -42,6 +42,6 @@ No authentication required if empty.
!!! warning "" !!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead. To work on Android and Apple platforms without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop. Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -39,6 +39,6 @@ No authentication required if empty.
!!! warning "" !!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead. To work on Android and Apple platforms without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop. Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -1,7 +1,12 @@
--- ---
icon: material/alert-decagram icon: material/new-box
--- ---
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
:material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain)
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"
:material-plus: [gso](#gso) :material-plus: [gso](#gso)
@@ -73,7 +78,9 @@ icon: material/alert-decagram
"http_proxy": { "http_proxy": {
"enabled": false, "enabled": false,
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 8080 "server_port": 8080,
"bypass_domain": [],
"match_domain": []
} }
}, },
@@ -260,6 +267,38 @@ Platform-specific settings, provided by client applications.
System HTTP proxy settings. System HTTP proxy settings.
#### platform.http_proxy.enabled
Enable system HTTP proxy.
#### platform.http_proxy.server
==Required==
HTTP proxy server address.
#### platform.http_proxy.server_port
==Required==
HTTP proxy server port.
#### platform.http_proxy.bypass_domain
!!! note ""
On Apple platforms, `bypass_domain` items matches hostname **suffixes**.
Hostnames that bypass the HTTP proxy.
#### platform.http_proxy.match_domain
!!! quote ""
Only supported in graphical clients on Apple platforms.
Hostnames that use the HTTP proxy.
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen/) for details. See [Listen Fields](/configuration/shared/listen/) for details.

View File

@@ -1,7 +1,12 @@
--- ---
icon: material/alert-decagram icon: material/new-box
--- ---
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
:material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain)
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"
:material-plus: [gso](#gso) :material-plus: [gso](#gso)
@@ -73,7 +78,9 @@ icon: material/alert-decagram
"http_proxy": { "http_proxy": {
"enabled": false, "enabled": false,
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 8080 "server_port": 8080,
"bypass_domain": [],
"match_domain": []
} }
}, },
@@ -257,6 +264,38 @@ TCP/IP 栈。
系统 HTTP 代理设置。 系统 HTTP 代理设置。
##### platform.http_proxy.enabled
启用系统 HTTP 代理。
##### platform.http_proxy.server
==必填==
系统 HTTP 代理服务器地址。
##### platform.http_proxy.server_port
==必填==
系统 HTTP 代理服务器端口。
##### platform.http_proxy.bypass_domain
!!! note ""
在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**.
绕过代理的主机名列表。
##### platform.http_proxy.match_domain
!!! quote ""
仅在 Apple 平台图形客户端中支持。
代理的主机名列表。
### 监听字段 ### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。 参阅 [监听字段](/zh/configuration/shared/listen/)。

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"
:material-plus: [gso](#gso) :material-plus: [gso](#gso)

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"
:material-plus: [gso](#gso) :material-plus: [gso](#gso)

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
# Route # Route
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
# 路由 # 路由
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"
:material-plus: [rule_set](#rule_set) :material-plus: [rule_set](#rule_set)
@@ -109,6 +105,7 @@ icon: material/alert-decagram
"geoip-cn", "geoip-cn",
"geosite-cn" "geosite-cn"
], ],
"rule_set_ipcidr_match_source": false,
"invert": false, "invert": false,
"outbound": "direct" "outbound": "direct"
}, },
@@ -284,7 +281,7 @@ Match Clash mode.
!!! quote "" !!! quote ""
Only supported in graphical clients on Android and iOS. Only supported in graphical clients on Android and Apple platforms.
Match WiFi SSID. Match WiFi SSID.
@@ -292,7 +289,7 @@ Match WiFi SSID.
!!! quote "" !!! quote ""
Only supported in graphical clients on Android and iOS. Only supported in graphical clients on Android and Apple platforms.
Match WiFi BSSID. Match WiFi BSSID.

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"
:material-plus: [rule_set](#rule_set) :material-plus: [rule_set](#rule_set)
@@ -107,6 +103,7 @@ icon: material/alert-decagram
"geoip-cn", "geoip-cn",
"geosite-cn" "geosite-cn"
], ],
"rule_set_ipcidr_match_source": false,
"invert": false, "invert": false,
"outbound": "direct" "outbound": "direct"
}, },
@@ -282,7 +279,7 @@ icon: material/alert-decagram
!!! quote "" !!! quote ""
仅在 Android 与 iOS 的图形客户端中支持。 仅在 Android 与 Apple 平台图形客户端中支持。
匹配 WiFi SSID。 匹配 WiFi SSID。
@@ -290,7 +287,7 @@ icon: material/alert-decagram
!!! quote "" !!! quote ""
仅在 Android 与 iOS 的图形客户端中支持。 仅在 Android 与 Apple 平台图形客户端中支持。
匹配 WiFi BSSID。 匹配 WiFi BSSID。

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
### Structure ### Structure
!!! question "Since sing-box 1.8.0" !!! question "Since sing-box 1.8.0"
@@ -128,7 +124,7 @@ Match source IP CIDR.
!!! info "" !!! info ""
`ip_cidr` is an alias for `source_ip_cidr` when the Rule Set is used in DNS rules or `rule_set_ipcidr_match_source` enabled in route rules. `ip_cidr` is an alias for `source_ip_cidr` when `rule_set_ipcidr_match_source` enabled in route/DNS rules.
Match IP CIDR. Match IP CIDR.
@@ -172,7 +168,7 @@ Match android package name.
!!! quote "" !!! quote ""
Only supported in graphical clients on Android and iOS. Only supported in graphical clients on Android and Apple platforms.
Match WiFi SSID. Match WiFi SSID.
@@ -180,7 +176,7 @@ Match WiFi SSID.
!!! quote "" !!! quote ""
Only supported in graphical clients on Android and iOS. Only supported in graphical clients on Android and Apple platforms.
Match WiFi BSSID. Match WiFi BSSID.

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
# Rule Set # Rule Set
!!! question "Since sing-box 1.8.0" !!! question "Since sing-box 1.8.0"

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
# Source Format # Source Format
!!! question "Since sing-box 1.8.0" !!! question "Since sing-box 1.8.0"

View File

@@ -1,8 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"
:material-alert-decagram: [utls](#utls) :material-alert-decagram: [utls](#utls)

View File

@@ -1,7 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"
:material-alert-decagram: [utls](#utls) :material-alert-decagram: [utls](#utls)

View File

@@ -0,0 +1,38 @@
---
icon: material/book-lock-open
---
# TunnelVision
TunnelVision is an attack that uses DHCP option 121 to set higher priority routes
so that traffic does not go through the VPN.
Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-3661
## Status
### Android
Android does not handle DHCP option 121 and is not affected.
### Apple platforms
Update [sing-box graphical client](/clients/apple/#download) to `1.9.0-rc.16` or newer,
then enable `includeAllNetworks` in `Settings``Packet Tunnel` and you will be unaffected.
Note: when `includeAllNetworks` is enabled, the default TUN stack is changed to `gvisor`,
and the `system` and `mixed` stacks are not available.
### Linux
Update sing-box to `1.9.0-rc.16` or newer, rules generated by `auto-route` are unaffected.
### Windows
No solution yet.
## Workarounds
* Don't connect to untrusted networks
* Relay untrusted network through another device
* Just ignore it

View File

@@ -1,208 +0,0 @@
---
icon: material/alpha-t-box
---
# TUIC
A recently popular Chinese-made simple protocol based on QUIC, the selling point is the BBR congestion control algorithm.
!!! warning
Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more characteristics than TCP based proxies.
| Specification | Binary Characteristics | Active Detect Hiddenness |
|-----------------------------------------------------------|------------------------|--------------------------|
| [GitHub](https://github.com/EAimTY/tuic/blob/dev/SPEC.md) | :material-alert: | :material-check: |
## Password Generator
| Generated UUID | Generated Password | Action |
|------------------------|----------------------------|-----------------------------------------------------------------|
| <code id="uuid"><code> | <code id="password"><code> | <button class="md-button" onclick="generate()">Refresh</button> |
<script>
function generateUUID() {
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
let r = Math.random() * 16 | 0,
v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
document.getElementById("uuid").textContent = uuid;
}
function generatePassword() {
const array = new Uint8Array(16);
window.crypto.getRandomValues(array);
document.getElementById("password").textContent = btoa(String.fromCharCode.apply(null, array));
}
function generate() {
generateUUID();
generatePassword();
}
generate();
</script>
## :material-server: Server Example
=== ":material-harddisk: With local certificate"
```json
{
"inbounds": [
{
"type": "tuic",
"listen": "::",
"listen_port": 8080,
"users": [
{
"name": "sekai",
"uuid": "<uuid>",
"password": "<password>"
}
],
"congestion_control": "bbr",
"tls": {
"enabled": true,
"server_name": "example.org",
"key_path": "/path/to/key.pem",
"certificate_path": "/path/to/certificate.pem"
}
}
]
}
```
=== ":material-auto-fix: With ACME"
```json
{
"inbounds": [
{
"type": "tuic",
"listen": "::",
"listen_port": 8080,
"users": [
{
"name": "sekai",
"uuid": "<uuid>",
"password": "<password>"
}
],
"congestion_control": "bbr",
"tls": {
"enabled": true,
"server_name": "example.org",
"acme": {
"domain": "example.org",
"email": "admin@example.org"
}
}
}
]
}
```
=== ":material-cloud: With ACME and Cloudflare API"
```json
{
"inbounds": [
{
"type": "tuic",
"listen": "::",
"listen_port": 8080,
"users": [
{
"name": "sekai",
"uuid": "<uuid>",
"password": "<password>"
}
],
"congestion_control": "bbr",
"tls": {
"enabled": true,
"server_name": "example.org",
"acme": {
"domain": "example.org",
"email": "admin@example.org",
"dns01_challenge": {
"provider": "cloudflare",
"api_token": "my_token"
}
}
}
}
]
}
```
## :material-cellphone-link: Client Example
=== ":material-web-check: With valid certificate"
```json
{
"outbounds": [
{
"type": "tuic",
"server": "127.0.0.1",
"server_port": 8080,
"uuid": "<uuid>",
"password": "<password>",
"congestion_control": "bbr",
"tls": {
"enabled": true,
"server_name": "example.org"
}
}
]
}
```
=== ":material-check: With self-sign certificate"
!!! info "Tip"
Use `sing-box merge` command to merge configuration and certificate into one file.
```json
{
"outbounds": [
{
"type": "tuic",
"server": "127.0.0.1",
"server_port": 8080,
"uuid": "<uuid>",
"password": "<password>",
"congestion_control": "bbr",
"tls": {
"enabled": true,
"server_name": "example.org",
"certificate_path": "/path/to/certificate.pem"
}
}
]
}
```
=== ":material-alert: Ignore certificate verification"
```json
{
"outbounds": [
{
"type": "tuic",
"server": "127.0.0.1",
"server_port": 8080,
"uuid": "<uuid>",
"password": "<password>",
"congestion_control": "bbr",
"tls": {
"enabled": true,
"server_name": "example.org",
"insecure": true
}
}
]
}
```

View File

@@ -290,52 +290,6 @@ flowchart TB
=== ":material-dns: DNS rules" === ":material-dns: DNS rules"
!!! info
DNS rules are optional if FakeIP is used.
```json
{
"dns": {
"servers": [
{
"tag": "google",
"address": "tls://8.8.8.8"
},
{
"tag": "local",
"address": "223.5.5.5",
"detour": "direct"
}
],
"rules": [
{
"outbound": "any",
"server": "local"
},
{
"clash_mode": "Direct",
"server": "local"
},
{
"clash_mode": "Global",
"server": "google"
},
{
"geosite": "geolocation-cn",
"server": "local"
}
]
}
}
```
=== ":material-dns: DNS rules (1.8.0+)"
!!! info
DNS rules are optional if FakeIP is used.
```json ```json
{ {
"dns": { "dns": {
@@ -382,74 +336,180 @@ flowchart TB
} }
``` ```
=== ":material-router-network: Route rules" === ":material-dns: DNS rules (Enhanced, but slower) (1.9.0+)"
```json === ":material-shield-off: With DNS leaks"
{
"outbounds": [ ```json
{ {
"type": "direct", "dns": {
"tag": "direct" "servers": [
},
{
"type": "block",
"tag": "block"
}
],
"route": {
"rules": [
{
"type": "logical",
"mode": "or",
"rules": [
{ {
"protocol": "dns" "tag": "google",
"address": "tls://8.8.8.8"
}, },
{ {
"port": 53 "tag": "local",
"address": "https://223.5.5.5/dns-query",
"detour": "direct"
} }
], ],
"outbound": "dns"
},
{
"geoip": "private",
"outbound": "direct"
},
{
"clash_mode": "Direct",
"outbound": "direct"
},
{
"clash_mode": "Global",
"outbound": "default"
},
{
"type": "logical",
"mode": "or",
"rules": [ "rules": [
{ {
"port": 853 "outbound": "any",
"server": "local"
}, },
{ {
"network": "udp", "clash_mode": "Direct",
"port": 443 "server": "local"
}, },
{ {
"protocol": "stun" "clash_mode": "Global",
"server": "google"
},
{
"rule_set": "geosite-geolocation-cn",
"server": "local"
},
{
"clash_mode": "Default",
"server": "google"
},
{
"type": "logical",
"mode": "and",
"rules": [
{
"rule_set": "geosite-geolocation-!cn",
"invert": true
},
{
"rule_set": "geoip-cn"
}
],
"server": "local"
} }
], ]
"outbound": "block"
}, },
{ "route": {
"geosite": "geolocation-cn", "rule_set": [
"outbound": "direct" {
"type": "remote",
"tag": "geosite-geolocation-cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
},
{
"type": "remote",
"tag": "geosite-geolocation-!cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs"
},
{
"type": "remote",
"tag": "geoip-cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
}
]
},
"experimental": {
"cache_file": {
"enabled": true,
"store_rdrc": true
},
"clash_api": {
"default_mode": "Enhanced"
}
} }
] }
} ```
}
```
=== ":material-router-network: Route rules (1.8.0+)" === ":material-security: Without DNS leaks, but slower (1.9.0-alpha.2+)"
```json
{
"dns": {
"servers": [
{
"tag": "google",
"address": "tls://8.8.8.8"
},
{
"tag": "local",
"address": "https://223.5.5.5/dns-query",
"detour": "direct"
}
],
"rules": [
{
"outbound": "any",
"server": "local"
},
{
"clash_mode": "Direct",
"server": "local"
},
{
"clash_mode": "Global",
"server": "google"
},
{
"rule_set": "geosite-geolocation-cn",
"server": "local"
},
{
"type": "logical",
"mode": "and",
"rules": [
{
"rule_set": "geosite-geolocation-!cn",
"invert": true
},
{
"rule_set": "geoip-cn"
}
],
"server": "google",
"client_subnet": "114.114.114.114/24" // Any China client IP address
}
]
},
"route": {
"rule_set": [
{
"type": "remote",
"tag": "geosite-geolocation-cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
},
{
"type": "remote",
"tag": "geosite-geolocation-!cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs"
},
{
"type": "remote",
"tag": "geoip-cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
}
]
},
"experimental": {
"cache_file": {
"enabled": true,
"store_rdrc": true
},
"clash_api": {
"default_mode": "Enhanced"
}
}
}
```
=== ":material-router-network: Route rules"
```json ```json
{ {

View File

@@ -2,6 +2,28 @@
icon: material/arrange-bring-forward icon: material/arrange-bring-forward
--- ---
## 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
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.
sing-box 1.9.0 modifies the behavior of `domain_suffix`: If the rule value is prefixed with `.`,
the behavior is unchanged, otherwise it matches `(domain|.+\.domain)` instead.
### `process_path` format update on Windows
The `process_path` rule of sing-box is inherited from Clash,
the original code uses the local system's path format (e.g. `\Device\HarddiskVolume1\folder\program.exe`),
but when the device has multiple disks, the HarddiskVolume serial number is not stable.
sing-box 1.9.0 make QueryFullProcessImageNameW output a Win32 path (such as `C:\folder\program.exe`),
which will disrupt the existing `process_path` use cases in Windows.
## 1.8.0 ## 1.8.0
### :material-close-box: Migrate cache file from Clash API to independent options ### :material-close-box: Migrate cache file from Clash API to independent options

View File

@@ -2,6 +2,27 @@
icon: material/arrange-bring-forward icon: material/arrange-bring-forward
--- ---
## 1.9.0
!!! warning "不稳定的"
该版本仍在开发中,迁移指南可能将在未来更改。
### `domain_suffix` 行为更新
由于历史原因sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。
sing-box 1.9.0 修改了 `domain_suffix` 的行为:如果规则值以 `.` 为前缀则行为不变,否则改为匹配 `(domain|.+\.domain)`
### 对 Windows 上 `process_path` 格式的更新
sing-box 的 `process_path` 规则继承自Clash
原始代码使用本地系统的路径格式(例如 `\Device\HarddiskVolume1\folder\program.exe`
但是当设备有多个硬盘时,该 HarddiskVolume 系列号并不稳定。
sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\folder\program.exe`
这将会破坏现有的 Windows `process_path` 用例。
## 1.8.0 ## 1.8.0
### :material-close-box: 将缓存文件从 Clash API 迁移到独立选项 ### :material-close-box: 将缓存文件从 Clash API 迁移到独立选项

View File

@@ -29,6 +29,7 @@ var (
string(bucketExpand), string(bucketExpand),
string(bucketMode), string(bucketMode),
string(bucketRuleSet), string(bucketRuleSet),
string(bucketRDRC),
} }
cacheIDDefault = []byte("default") cacheIDDefault = []byte("default")
@@ -37,17 +38,26 @@ var (
var _ adapter.CacheFile = (*CacheFile)(nil) var _ adapter.CacheFile = (*CacheFile)(nil)
type CacheFile struct { type CacheFile struct {
ctx context.Context ctx context.Context
path string path string
cacheID []byte cacheID []byte
storeFakeIP bool storeFakeIP bool
storeRDRC bool
rdrcTimeout time.Duration
DB *bbolt.DB DB *bbolt.DB
saveAccess sync.RWMutex saveMetadataTimer *time.Timer
saveFakeIPAccess sync.RWMutex
saveDomain map[netip.Addr]string saveDomain map[netip.Addr]string
saveAddress4 map[string]netip.Addr saveAddress4 map[string]netip.Addr
saveAddress6 map[string]netip.Addr saveAddress6 map[string]netip.Addr
saveMetadataTimer *time.Timer saveRDRCAccess sync.RWMutex
saveRDRC map[saveRDRCCacheKey]bool
}
type saveRDRCCacheKey struct {
TransportName string
QuestionName string
QType uint16
} }
func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
@@ -61,14 +71,25 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
if options.CacheID != "" { if options.CacheID != "" {
cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...) cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...)
} }
var rdrcTimeout time.Duration
if options.StoreRDRC {
if options.RDRCTimeout > 0 {
rdrcTimeout = time.Duration(options.RDRCTimeout)
} else {
rdrcTimeout = 7 * 24 * time.Hour
}
}
return &CacheFile{ return &CacheFile{
ctx: ctx, ctx: ctx,
path: filemanager.BasePath(ctx, path), path: filemanager.BasePath(ctx, path),
cacheID: cacheIDBytes, cacheID: cacheIDBytes,
storeFakeIP: options.StoreFakeIP, storeFakeIP: options.StoreFakeIP,
storeRDRC: options.StoreRDRC,
rdrcTimeout: rdrcTimeout,
saveDomain: make(map[netip.Addr]string), saveDomain: make(map[netip.Addr]string),
saveAddress4: make(map[string]netip.Addr), saveAddress4: make(map[string]netip.Addr),
saveAddress6: make(map[string]netip.Addr), saveAddress6: make(map[string]netip.Addr),
saveRDRC: make(map[saveRDRCCacheKey]bool),
} }
} }

View File

@@ -97,7 +97,7 @@ func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
} }
func (c *CacheFile) FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger) { func (c *CacheFile) FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger) {
c.saveAccess.Lock() c.saveFakeIPAccess.Lock()
if oldDomain, loaded := c.saveDomain[address]; loaded { if oldDomain, loaded := c.saveDomain[address]; loaded {
if address.Is4() { if address.Is4() {
delete(c.saveAddress4, oldDomain) delete(c.saveAddress4, oldDomain)
@@ -111,27 +111,27 @@ func (c *CacheFile) FakeIPStoreAsync(address netip.Addr, domain string, logger l
} else { } else {
c.saveAddress6[domain] = address c.saveAddress6[domain] = address
} }
c.saveAccess.Unlock() c.saveFakeIPAccess.Unlock()
go func() { go func() {
err := c.FakeIPStore(address, domain) err := c.FakeIPStore(address, domain)
if err != nil { if err != nil {
logger.Warn("save FakeIP address pair: ", err) logger.Warn("save FakeIP cache: ", err)
} }
c.saveAccess.Lock() c.saveFakeIPAccess.Lock()
delete(c.saveDomain, address) delete(c.saveDomain, address)
if address.Is4() { if address.Is4() {
delete(c.saveAddress4, domain) delete(c.saveAddress4, domain)
} else { } else {
delete(c.saveAddress6, domain) delete(c.saveAddress6, domain)
} }
c.saveAccess.Unlock() c.saveFakeIPAccess.Unlock()
}() }()
} }
func (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) { func (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) {
c.saveAccess.RLock() c.saveFakeIPAccess.RLock()
cachedDomain, cached := c.saveDomain[address] cachedDomain, cached := c.saveDomain[address]
c.saveAccess.RUnlock() c.saveFakeIPAccess.RUnlock()
if cached { if cached {
return cachedDomain, true return cachedDomain, true
} }
@@ -152,13 +152,13 @@ func (c *CacheFile) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bo
cachedAddress netip.Addr cachedAddress netip.Addr
cached bool cached bool
) )
c.saveAccess.RLock() c.saveFakeIPAccess.RLock()
if !isIPv6 { if !isIPv6 {
cachedAddress, cached = c.saveAddress4[domain] cachedAddress, cached = c.saveAddress4[domain]
} else { } else {
cachedAddress, cached = c.saveAddress6[domain] cachedAddress, cached = c.saveAddress6[domain]
} }
c.saveAccess.RUnlock() c.saveFakeIPAccess.RUnlock()
if cached { if cached {
return cachedAddress, true return cachedAddress, true
} }

View File

@@ -0,0 +1,109 @@
package cachefile
import (
"encoding/binary"
"time"
"github.com/sagernet/bbolt"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/logger"
)
var bucketRDRC = []byte("rdrc2")
func (c *CacheFile) StoreRDRC() bool {
return c.storeRDRC
}
func (c *CacheFile) RDRCTimeout() time.Duration {
return c.rdrcTimeout
}
func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) {
c.saveRDRCAccess.RLock()
rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName, qType}]
c.saveRDRCAccess.RUnlock()
if cached {
return
}
key := buf.Get(2 + len(qName))
binary.BigEndian.PutUint16(key, qType)
copy(key[2:], qName)
defer buf.Put(key)
var deleteCache bool
err := c.DB.View(func(tx *bbolt.Tx) error {
bucket := c.bucket(tx, bucketRDRC)
if bucket == nil {
return nil
}
bucket = bucket.Bucket([]byte(transportName))
if bucket == nil {
return nil
}
content := bucket.Get(key)
if content == nil {
return nil
}
expiresAt := time.Unix(int64(binary.BigEndian.Uint64(content)), 0)
if time.Now().After(expiresAt) {
deleteCache = true
return nil
}
rejected = true
return nil
})
if err != nil {
return
}
if deleteCache {
c.DB.Update(func(tx *bbolt.Tx) error {
bucket := c.bucket(tx, bucketRDRC)
if bucket == nil {
return nil
}
bucket = bucket.Bucket([]byte(transportName))
if bucket == nil {
return nil
}
return bucket.Delete(key)
})
}
return
}
func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) error {
return c.DB.Batch(func(tx *bbolt.Tx) error {
bucket, err := c.createBucket(tx, bucketRDRC)
if err != nil {
return err
}
bucket, err = bucket.CreateBucketIfNotExists([]byte(transportName))
if err != nil {
return err
}
key := buf.Get(2 + len(qName))
binary.BigEndian.PutUint16(key, qType)
copy(key[2:], qName)
defer buf.Put(key)
expiresAt := buf.Get(8)
defer buf.Put(expiresAt)
binary.BigEndian.PutUint64(expiresAt, uint64(time.Now().Add(c.rdrcTimeout).Unix()))
return bucket.Put(key, expiresAt)
})
}
func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) {
saveKey := saveRDRCCacheKey{transportName, qName, qType}
c.saveRDRCAccess.Lock()
c.saveRDRC[saveKey] = true
c.saveRDRCAccess.Unlock()
go func() {
err := c.SaveRDRC(transportName, qName, qType)
if err != nil {
logger.Warn("save RDRC: ", err)
}
c.saveRDRCAccess.Lock()
delete(c.saveRDRC, saveKey)
c.saveRDRCAccess.Unlock()
}()
}

View File

@@ -3,6 +3,7 @@ package experimental
import ( import (
"context" "context"
"os" "os"
"sort"
"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"
@@ -27,11 +28,26 @@ func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.O
} }
func CalculateClashModeList(options option.Options) []string { func CalculateClashModeList(options option.Options) []string {
var clashMode []string var clashModes []string
clashMode = append(clashMode, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...) clashModes = append(clashModes, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...)
clashMode = append(clashMode, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...) clashModes = append(clashModes, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...)
clashMode = common.FilterNotDefault(common.Uniq(clashMode)) clashModes = common.FilterNotDefault(common.Uniq(clashModes))
return clashMode predefinedOrder := []string{
"Rule", "Global", "Direct",
}
var newClashModes []string
for _, mode := range clashModes {
if !common.Contains(predefinedOrder, mode) {
newClashModes = append(newClashModes, mode)
}
}
sort.Strings(newClashModes)
for _, mode := range predefinedOrder {
if common.Contains(clashModes, mode) {
newClashModes = append(newClashModes, mode)
}
}
return newClashModes
} }
func extraClashModeFromRule(rules []option.Rule) []string { func extraClashModeFromRule(rules []option.Rule) []string {

View File

@@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box" "github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
@@ -75,7 +74,7 @@ func (s *platformInterfaceStub) UsePlatformInterfaceGetter() bool {
return true return true
} }
func (s *platformInterfaceStub) Interfaces() ([]platform.NetworkInterface, error) { func (s *platformInterfaceStub) Interfaces() ([]control.Interface, error) {
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }
@@ -83,6 +82,10 @@ func (s *platformInterfaceStub) UnderNetworkExtension() bool {
return false return false
} }
func (s *platformInterfaceStub) IncludeAllNetworks() bool {
return false
}
func (s *platformInterfaceStub) ClearDNSCache() { func (s *platformInterfaceStub) ClearDNSCache() {
} }

View File

@@ -9,9 +9,7 @@ import (
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common" "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"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/task"
mDNS "github.com/miekg/dns" mDNS "github.com/miekg/dns"
@@ -25,9 +23,11 @@ type LocalDNSTransport interface {
func RegisterLocalDNSTransport(transport LocalDNSTransport) { func RegisterLocalDNSTransport(transport LocalDNSTransport) {
if transport == nil { if transport == nil {
dns.RegisterTransport([]string{"local"}, dns.CreateLocalTransport) dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) {
return dns.NewLocalTransport(options), nil
})
} else { } else {
dns.RegisterTransport([]string{"local"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) {
return &platformLocalDNSTransport{ return &platformLocalDNSTransport{
iif: transport, iif: transport,
}, nil }, nil

View File

@@ -19,6 +19,7 @@ type PlatformInterface interface {
UsePlatformInterfaceGetter() bool UsePlatformInterfaceGetter() bool
GetInterfaces() (NetworkInterfaceIterator, error) GetInterfaces() (NetworkInterfaceIterator, error)
UnderNetworkExtension() bool UnderNetworkExtension() bool
IncludeAllNetworks() bool
ReadWIFIState() *WIFIState ReadWIFIState() *WIFIState
ClearDNSCache() ClearDNSCache()
} }

View File

@@ -2,7 +2,6 @@ package platform
import ( import (
"context" "context"
"net/netip"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/process"
@@ -20,16 +19,10 @@ type Interface interface {
UsePlatformDefaultInterfaceMonitor() bool UsePlatformDefaultInterfaceMonitor() bool
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
UsePlatformInterfaceGetter() bool UsePlatformInterfaceGetter() bool
Interfaces() ([]NetworkInterface, error) Interfaces() ([]control.Interface, error)
UnderNetworkExtension() bool UnderNetworkExtension() bool
IncludeAllNetworks() bool
ClearDNSCache() ClearDNSCache()
ReadWIFIState() adapter.WIFIState ReadWIFIState() adapter.WIFIState
process.Searcher process.Searcher
} }
type NetworkInterface struct {
Index int
MTU int
Name string
Addresses []netip.Prefix
}

View File

@@ -192,14 +192,14 @@ func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool {
return w.iif.UsePlatformInterfaceGetter() return w.iif.UsePlatformInterfaceGetter()
} }
func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, error) { func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) {
interfaceIterator, err := w.iif.GetInterfaces() interfaceIterator, err := w.iif.GetInterfaces()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var interfaces []platform.NetworkInterface var interfaces []control.Interface
for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) { for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) {
interfaces = append(interfaces, platform.NetworkInterface{ interfaces = append(interfaces, control.Interface{
Index: int(netInterface.Index), Index: int(netInterface.Index),
MTU: int(netInterface.MTU), MTU: int(netInterface.MTU),
Name: netInterface.Name, Name: netInterface.Name,
@@ -213,6 +213,10 @@ func (w *platformInterfaceWrapper) UnderNetworkExtension() bool {
return w.iif.UnderNetworkExtension() return w.iif.UnderNetworkExtension()
} }
func (w *platformInterfaceWrapper) IncludeAllNetworks() bool {
return w.iif.IncludeAllNetworks()
}
func (w *platformInterfaceWrapper) ClearDNSCache() { func (w *platformInterfaceWrapper) ClearDNSCache() {
w.iif.ClearDNSCache() w.iif.ClearDNSCache()
} }

View File

@@ -16,25 +16,18 @@ func (s *BoxService) Pause() {
if s.pauseTimer != nil { if s.pauseTimer != nil {
s.pauseTimer.Stop() s.pauseTimer.Stop()
} }
s.pauseTimer = time.AfterFunc(time.Minute, s.pause) s.pauseTimer = time.AfterFunc(3*time.Second, s.ResetNetwork)
}
func (s *BoxService) pause() {
s.pauseAccess.Lock()
defer s.pauseAccess.Unlock()
s.pauseManager.DevicePause()
_ = s.instance.Router().ResetNetwork()
s.pauseTimer = nil
} }
func (s *BoxService) Wake() { func (s *BoxService) Wake() {
_ = s.instance.Router().ResetNetwork()
s.pauseAccess.Lock() s.pauseAccess.Lock()
defer s.pauseAccess.Unlock() defer s.pauseAccess.Unlock()
if s.pauseTimer != nil { if s.pauseTimer != nil {
s.pauseTimer.Stop() s.pauseTimer.Stop()
s.pauseTimer = nil
return
} }
s.pauseManager.DeviceWake() s.pauseTimer = time.AfterFunc(3*time.Minute, s.ResetNetwork)
}
func (s *BoxService) ResetNetwork() {
_ = s.instance.Router().ResetNetwork()
} }

View File

@@ -7,6 +7,7 @@ import (
"github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/humanize"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
_ "github.com/sagernet/sing-box/include"
) )
var ( var (

View File

@@ -28,6 +28,8 @@ type TunOptions interface {
IsHTTPProxyEnabled() bool IsHTTPProxyEnabled() bool
GetHTTPProxyServer() string GetHTTPProxyServer() string
GetHTTPProxyServerPort() int32 GetHTTPProxyServerPort() int32
GetHTTPProxyBypassDomain() StringIterator
GetHTTPProxyMatchDomain() StringIterator
} }
type RoutePrefix struct { type RoutePrefix struct {
@@ -156,3 +158,11 @@ func (o *tunOptions) GetHTTPProxyServer() string {
func (o *tunOptions) GetHTTPProxyServerPort() int32 { func (o *tunOptions) GetHTTPProxyServerPort() int32 {
return int32(o.TunPlatformOptions.HTTPProxy.ServerPort) return int32(o.TunPlatformOptions.HTTPProxy.ServerPort)
} }
func (o *tunOptions) GetHTTPProxyBypassDomain() StringIterator {
return newIterator(o.TunPlatformOptions.HTTPProxy.BypassDomain)
}
func (o *tunOptions) GetHTTPProxyMatchDomain() StringIterator {
return newIterator(o.TunPlatformOptions.HTTPProxy.MatchDomain)
}

28
go.mod
View File

@@ -11,7 +11,7 @@ require (
github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.3 github.com/go-chi/render v1.0.3
github.com/gofrs/uuid/v5 v5.1.0 github.com/gofrs/uuid/v5 v5.2.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/libdns/alidns v1.0.3 github.com/libdns/alidns v1.0.3
github.com/libdns/cloudflare v0.1.1 github.com/libdns/cloudflare v0.1.1
@@ -23,17 +23,17 @@ require (
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
github.com/sagernet/gomobile v0.1.3 github.com/sagernet/gomobile v0.1.3
github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
github.com/sagernet/quic-go v0.40.1 github.com/sagernet/quic-go v0.43.1-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.3.8 github.com/sagernet/sing v0.4.1
github.com/sagernet/sing-dns v0.1.14 github.com/sagernet/sing-dns v0.2.0
github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-mux v0.2.0
github.com/sagernet/sing-quic v0.1.15 github.com/sagernet/sing-quic v0.2.0-beta.5
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.2.7 github.com/sagernet/sing-tun v0.3.2
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
@@ -44,9 +44,9 @@ require (
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.22.0 golang.org/x/crypto v0.23.0
golang.org/x/net v0.24.0 golang.org/x/net v0.25.0
golang.org/x/sys v0.19.0 golang.org/x/sys v0.21.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.63.2 google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0 google.golang.org/protobuf v1.33.0
@@ -78,18 +78,18 @@ 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-20220905062125-8043b4a9aa97 // indirect github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // 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.0-20211101163701-50045581ed74 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // 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-20240416160154-fe59bbe5cc7f // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.20.0 // indirect golang.org/x/tools v0.21.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

60
go.sum
View File

@@ -34,8 +34,8 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gofrs/uuid/v5 v5.1.0 h1:S5rqVKIigghZTCBKPCw0Y+bXkn26K3TB5mvQq2Ix8dk= github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
github.com/gofrs/uuid/v5 v5.1.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
@@ -97,33 +97,31 @@ github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQ
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
github.com/sagernet/gomobile v0.1.3 h1:ohjIb1Ou2+1558PnZour3od69suSuvkdSVOlO1tC4B8= github.com/sagernet/gomobile v0.1.3 h1:ohjIb1Ou2+1558PnZour3od69suSuvkdSVOlO1tC4B8=
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-20231209105102-8d27a30e436e h1:DOkjByVeAR56dkszjnMZke4wr7yM/1xHaJF3G9olkEE= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/quic-go v0.40.1 h1:qLeTIJR0d0JWRmDWo346nLsVN6EWihd1kalJYPEd0TM= github.com/sagernet/quic-go v0.43.1-beta.1 h1:alizUjpvWYcz08dBCQsULOd+1xu0o7UtlyYf6SLbRNg=
github.com/sagernet/quic-go v0.40.1/go.mod h1:CcKTpzTAISxrM4PA5M20/wYuz9Tj6Tx4DwGbNl9UQrU= github.com/sagernet/quic-go v0.43.1-beta.1/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhYiNPyYEBL/BVJ1ifc=
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.3.8 h1:gm4JKalPhydMYX2zFOTnnd4TXtM/16WFRqSjMepYQQk= github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
github.com/sagernet/sing-dns v0.1.14 h1:kxE/Ik3jMXmD3sXsdt9MgrNzLFWt64mghV+MQqzyf40= github.com/sagernet/sing-dns v0.2.0 h1:dka3weRX6+CrYO3v+hrTy2z68rCOCZXNBiNXpLZ6JNs=
github.com/sagernet/sing-dns v0.1.14/go.mod h1:AA+vZMNovuPN5i/sPnfF6756Nq94nzb5nXodMWbta5w= github.com/sagernet/sing-dns v0.2.0/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg=
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-quic v0.1.14 h1:gzQAuvxDyh9oz3J595KchYpi0HcHOvQWeUG20FWc45A= github.com/sagernet/sing-quic v0.2.0-beta.5 h1:ceKFLd1iS5AtM+pScKmcDp5k7R6WgYIe8vl6nB0aVsE=
github.com/sagernet/sing-quic v0.1.14/go.mod h1:L+VtzvuPbf8VW8F4R7KiygqpXY4lO7t2wwcQuHjh8Ew= github.com/sagernet/sing-quic v0.2.0-beta.5/go.mod h1:lfad61lScAZhAxZ0DHZWvEIcAaT38O6zPTR4vLsHeP0=
github.com/sagernet/sing-quic v0.1.15 h1:LGWPxQEeg89+68RHP7HtAV0RZeEWQikUqOfE9nYmr2A=
github.com/sagernet/sing-quic v0.1.15/go.mod h1:L+VtzvuPbf8VW8F4R7KiygqpXY4lO7t2wwcQuHjh8Ew=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s= github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM= github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-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.2.7 h1:6QtJkeSj6BTTQPGxbbiuV8eh7GdV46w2G0N8CmISwdc= github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY=
github.com/sagernet/sing-tun v0.2.7/go.mod h1:MKAAHUzVfj7d9zos4lsz6wjXu86/mJyd/gejiAnWj/w= github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
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=
@@ -165,16 +163,16 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.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=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -186,19 +184,19 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=

View File

@@ -5,7 +5,9 @@ import (
"net" "net"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
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"
@@ -16,6 +18,9 @@ func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort)
var tcpListener net.Listener var tcpListener net.Listener
var listenConfig net.ListenConfig var listenConfig net.ListenConfig
// TODO: Add an option to customize the keep alive period
listenConfig.KeepAlive = C.TCPKeepAliveInitial
listenConfig.Control = control.Append(listenConfig.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
if a.listenOptions.TCPMultiPath { if a.listenOptions.TCPMultiPath {
if !go121Available { if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")

View File

@@ -15,7 +15,6 @@ import (
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/common/uot" "github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
@@ -109,8 +108,8 @@ func (n *Naive) Start() error {
if common.Contains(n.network, N.NetworkUDP) { if common.Contains(n.network, N.NetworkUDP) {
err := n.configureHTTP3Listener() err := n.configureHTTP3Listener()
if !include.WithQUIC && len(n.network) > 1 { if !C.WithQUIC && len(n.network) > 1 {
log.Warn(E.Cause(err, "naive http3 disabled")) n.logger.Warn(E.Cause(err, "naive http3 disabled"))
} else if err != nil { } else if err != nil {
return err return err
} }

View File

@@ -166,6 +166,14 @@ func (t *Tun) Start() error {
} }
t.logger.Trace("creating stack") t.logger.Trace("creating stack")
t.tunIf = tunInterface t.tunIf = tunInterface
var (
forwarderBindInterface bool
includeAllNetworks bool
)
if t.platformInterface != nil {
forwarderBindInterface = true
includeAllNetworks = t.platformInterface.IncludeAllNetworks()
}
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{ t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
Context: t.ctx, Context: t.ctx,
Tun: tunInterface, Tun: tunInterface,
@@ -174,8 +182,9 @@ func (t *Tun) Start() error {
UDPTimeout: t.udpTimeout, UDPTimeout: t.udpTimeout,
Handler: t, Handler: t,
Logger: t.logger, Logger: t.logger,
ForwarderBindInterface: t.platformInterface != nil, ForwarderBindInterface: forwarderBindInterface,
InterfaceFinder: t.router.InterfaceFinder(), InterfaceFinder: t.router.InterfaceFinder(),
IncludeAllNetworks: includeAllNetworks,
}) })
if err != nil { if err != nil {
return err return err

View File

@@ -3,16 +3,12 @@
package include package include
import ( import (
"context"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
) )
func init() { func init() {
dns.RegisterTransport([]string{"dhcp"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) {
return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`) return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`)
}) })
} }

View File

@@ -6,5 +6,3 @@ import (
_ "github.com/sagernet/sing-box/transport/v2rayquic" _ "github.com/sagernet/sing-box/transport/v2rayquic"
_ "github.com/sagernet/sing-dns/quic" _ "github.com/sagernet/sing-dns/quic"
) )
const WithQUIC = true

View File

@@ -11,15 +11,12 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-box/transport/v2ray"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"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"
) )
const WithQUIC = false
func init() { func init() {
dns.RegisterTransport([]string{"quic", "h3"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { dns.RegisterTransport([]string{"quic", "h3"}, func(options dns.TransportOptions) (dns.Transport, error) {
return nil, C.ErrQUICNotIncluded return nil, C.ErrQUICNotIncluded
}) })
v2ray.RegisterQUICConstructor( v2ray.RegisterQUICConstructor(

21
include/tz_android.go Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// kanged from https://github.com/golang/mobile/blob/c713f31d574bb632a93f169b2cc99c9e753fef0e/app/android.go#L89
package include
// #include <time.h>
import "C"
import "time"
func init() {
var currentT C.time_t
var currentTM C.struct_tm
C.time(&currentT)
C.localtime_r(&currentT, &currentTM)
tzOffset := int(currentTM.tm_gmtoff)
tz := C.GoString(currentTM.tm_zone)
time.Local = time.FixedZone(tz, tzOffset)
}

30
include/tz_ios.go Normal file
View File

@@ -0,0 +1,30 @@
package include
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation
#import <Foundation/Foundation.h>
const char* getSystemTimeZone() {
NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
NSString *timeZoneName = [timeZone description];
return [timeZoneName UTF8String];
}
*/
import "C"
import (
"strings"
"time"
)
func init() {
tzDescription := C.GoString(C.getSystemTimeZone())
if len(tzDescription) == 0 {
return
}
location, err := time.LoadLocation(strings.Split(tzDescription, " ")[0])
if err != nil {
return
}
time.Local = location
}

View File

@@ -66,8 +66,9 @@ nav:
- Proxy Protocol: - Proxy Protocol:
- Shadowsocks: manual/proxy-protocol/shadowsocks.md - Shadowsocks: manual/proxy-protocol/shadowsocks.md
- Trojan: manual/proxy-protocol/trojan.md - Trojan: manual/proxy-protocol/trojan.md
- TUIC: manual/proxy-protocol/tuic.md
- Hysteria 2: manual/proxy-protocol/hysteria2.md - Hysteria 2: manual/proxy-protocol/hysteria2.md
- Misc:
- TunnelVision: manual/misc/tunnelvision.md
- Configuration: - Configuration:
- configuration/index.md - configuration/index.md
- Log: - Log:

View File

@@ -1,112 +0,0 @@
package ntp
import (
"context"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/settings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
)
var _ ntp.TimeService = (*Service)(nil)
type Service struct {
ctx context.Context
cancel common.ContextCancelCauseFunc
server M.Socksaddr
writeToSystem bool
dialer N.Dialer
logger logger.Logger
ticker *time.Ticker
clockOffset time.Duration
}
func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) (*Service, error) {
ctx, cancel := common.ContextWithCancelCause(ctx)
server := M.ParseSocksaddrHostPort(options.Server, options.ServerPort)
if server.Port == 0 {
server.Port = 123
}
var interval time.Duration
if options.Interval > 0 {
interval = time.Duration(options.Interval)
} else {
interval = 30 * time.Minute
}
outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil {
return nil, err
}
return &Service{
ctx: ctx,
cancel: cancel,
server: server,
writeToSystem: options.WriteToSystem,
dialer: outboundDialer,
logger: logger,
ticker: time.NewTicker(interval),
}, nil
}
func (s *Service) Start() error {
err := s.update()
if err != nil {
return E.Cause(err, "initialize time")
}
s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(C.TimeLayout))
go s.loopUpdate()
return nil
}
func (s *Service) Close() error {
s.ticker.Stop()
s.cancel(os.ErrClosed)
return nil
}
func (s *Service) TimeFunc() func() time.Time {
return func() time.Time {
return time.Now().Add(s.clockOffset)
}
}
func (s *Service) loopUpdate() {
for {
select {
case <-s.ctx.Done():
return
case <-s.ticker.C:
}
err := s.update()
if err == nil {
s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(C.TimeLayout))
} else {
s.logger.Warn("update time: ", err)
}
}
}
func (s *Service) update() error {
response, err := ntp.Exchange(s.ctx, s.dialer, s.server)
if err != nil {
return err
}
s.clockOffset = response.ClockOffset
if s.writeToSystem {
writeErr := settings.SetSystemTime(s.TimeFunc()())
if writeErr != nil {
s.logger.Warn("write time to system: ", writeErr)
}
}
return nil
}

View File

@@ -19,6 +19,7 @@ type DNSServerOptions struct {
AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"` AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"`
Strategy DomainStrategy `json:"strategy,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"`
Detour string `json:"detour,omitempty"` Detour string `json:"detour,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
} }
type DNSClientOptions struct { type DNSClientOptions struct {
@@ -26,6 +27,7 @@ type DNSClientOptions struct {
DisableCache bool `json:"disable_cache,omitempty"` DisableCache bool `json:"disable_cache,omitempty"`
DisableExpire bool `json:"disable_expire,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"`
IndependentCache bool `json:"independent_cache,omitempty"` IndependentCache bool `json:"independent_cache,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
} }
type DNSFakeIPOptions struct { type DNSFakeIPOptions struct {

View File

@@ -8,10 +8,12 @@ type ExperimentalOptions struct {
} }
type CacheFileOptions struct { type CacheFileOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
CacheID string `json:"cache_id,omitempty"` CacheID string `json:"cache_id,omitempty"`
StoreFakeIP bool `json:"store_fakeip,omitempty"` StoreFakeIP bool `json:"store_fakeip,omitempty"`
StoreRDRC bool `json:"store_rdrc,omitempty"`
RDRCTimeout Duration `json:"rdrc_timeout,omitempty"`
} }
type ClashAPIOptions struct { type ClashAPIOptions struct {

View File

@@ -2,9 +2,8 @@ package option
type NTPOptions struct { type NTPOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
Server string `json:"server,omitempty"`
ServerPort uint16 `json:"server_port,omitempty"`
Interval Duration `json:"interval,omitempty"` Interval Duration `json:"interval,omitempty"`
WriteToSystem bool `json:"write_to_system,omitempty"` WriteToSystem bool `json:"write_to_system,omitempty"`
ServerOptions
DialerOptions DialerOptions
} }

View File

@@ -65,38 +65,43 @@ func (r DNSRule) IsValid() bool {
} }
type DefaultDNSRule struct { type DefaultDNSRule struct {
Inbound Listable[string] `json:"inbound,omitempty"` Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"` IPVersion int `json:"ip_version,omitempty"`
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
Network Listable[string] `json:"network,omitempty"` Network Listable[string] `json:"network,omitempty"`
AuthUser Listable[string] `json:"auth_user,omitempty"` AuthUser Listable[string] `json:"auth_user,omitempty"`
Protocol Listable[string] `json:"protocol,omitempty"` Protocol Listable[string] `json:"protocol,omitempty"`
Domain Listable[string] `json:"domain,omitempty"` Domain Listable[string] `json:"domain,omitempty"`
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex Listable[string] `json:"domain_regex,omitempty"` DomainRegex Listable[string] `json:"domain_regex,omitempty"`
Geosite Listable[string] `json:"geosite,omitempty"` Geosite Listable[string] `json:"geosite,omitempty"`
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` GeoIP Listable[string] `json:"geoip,omitempty"`
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
SourcePort Listable[uint16] `json:"source_port,omitempty"` IPIsPrivate bool `json:"ip_is_private,omitempty"`
SourcePortRange Listable[string] `json:"source_port_range,omitempty"` SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
Port Listable[uint16] `json:"port,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
PortRange Listable[string] `json:"port_range,omitempty"` SourcePort Listable[uint16] `json:"source_port,omitempty"`
ProcessName Listable[string] `json:"process_name,omitempty"` SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
ProcessPath Listable[string] `json:"process_path,omitempty"` Port Listable[uint16] `json:"port,omitempty"`
PackageName Listable[string] `json:"package_name,omitempty"` PortRange Listable[string] `json:"port_range,omitempty"`
User Listable[string] `json:"user,omitempty"` ProcessName Listable[string] `json:"process_name,omitempty"`
UserID Listable[int32] `json:"user_id,omitempty"` ProcessPath Listable[string] `json:"process_path,omitempty"`
Outbound Listable[string] `json:"outbound,omitempty"` PackageName Listable[string] `json:"package_name,omitempty"`
ClashMode string `json:"clash_mode,omitempty"` User Listable[string] `json:"user,omitempty"`
WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` UserID Listable[int32] `json:"user_id,omitempty"`
WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` Outbound Listable[string] `json:"outbound,omitempty"`
RuleSet Listable[string] `json:"rule_set,omitempty"` ClashMode string `json:"clash_mode,omitempty"`
Invert bool `json:"invert,omitempty"` WIFISSID Listable[string] `json:"wifi_ssid,omitempty"`
Server string `json:"server,omitempty"` WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"` RuleSet Listable[string] `json:"rule_set,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
} }
func (r DefaultDNSRule) IsValid() bool { func (r DefaultDNSRule) IsValid() bool {
@@ -105,16 +110,18 @@ func (r DefaultDNSRule) IsValid() bool {
defaultValue.Server = r.Server defaultValue.Server = r.Server
defaultValue.DisableCache = r.DisableCache defaultValue.DisableCache = r.DisableCache
defaultValue.RewriteTTL = r.RewriteTTL defaultValue.RewriteTTL = r.RewriteTTL
defaultValue.ClientSubnet = r.ClientSubnet
return !reflect.DeepEqual(r, defaultValue) return !reflect.DeepEqual(r, defaultValue)
} }
type LogicalDNSRule struct { type LogicalDNSRule struct {
Mode string `json:"mode"` Mode string `json:"mode"`
Rules []DNSRule `json:"rules,omitempty"` Rules []DNSRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"` Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"` Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"` DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
} }
func (r LogicalDNSRule) IsValid() bool { func (r LogicalDNSRule) IsValid() bool {

View File

@@ -7,4 +7,6 @@ type TunPlatformOptions struct {
type HTTPProxyOptions struct { type HTTPProxyOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
ServerOptions ServerOptions
BypassDomain Listable[string] `json:"bypass_domain,omitempty"`
MatchDomain Listable[string] `json:"match_domain,omitempty"`
} }

View File

@@ -51,6 +51,40 @@ func (a *ListenAddress) Build() netip.Addr {
return (netip.Addr)(*a) return (netip.Addr)(*a)
} }
type AddrPrefix netip.Prefix
func (a AddrPrefix) MarshalJSON() ([]byte, error) {
prefix := netip.Prefix(a)
if prefix.Bits() == prefix.Addr().BitLen() {
return json.Marshal(prefix.Addr().String())
} else {
return json.Marshal(prefix.String())
}
}
func (a *AddrPrefix) UnmarshalJSON(content []byte) error {
var value string
err := json.Unmarshal(content, &value)
if err != nil {
return err
}
prefix, prefixErr := netip.ParsePrefix(value)
if prefixErr == nil {
*a = AddrPrefix(prefix)
return nil
}
addr, addrErr := netip.ParseAddr(value)
if addrErr == nil {
*a = AddrPrefix(netip.PrefixFrom(addr, addr.BitLen()))
return nil
}
return prefixErr
}
func (a AddrPrefix) Build() netip.Prefix {
return netip.Prefix(a)
}
type NetworkList string type NetworkList string
func (v *NetworkList) UnmarshalJSON(content []byte) error { func (v *NetworkList) UnmarshalJSON(content []byte) error {

View File

@@ -51,7 +51,7 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti
domainStrategy: dns.DomainStrategy(options.DomainStrategy), domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay), fallbackDelay: time.Duration(options.FallbackDelay),
dialer: outboundDialer, dialer: outboundDialer,
loopBack: newLoopBackDetector(), loopBack: newLoopBackDetector(router),
} }
if options.ProxyProtocol != 0 { if options.ProxyProtocol != 0 {
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")

View File

@@ -5,21 +5,22 @@ import (
"net/netip" "net/netip"
"sync" "sync"
"github.com/sagernet/sing-box/adapter"
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"
) )
type loopBackDetector struct { type loopBackDetector struct {
// router adapter.Router router adapter.Router
connAccess sync.RWMutex connAccess sync.RWMutex
packetConnAccess sync.RWMutex packetConnAccess sync.RWMutex
connMap map[netip.AddrPort]netip.AddrPort connMap map[netip.AddrPort]netip.AddrPort
packetConnMap map[uint16]uint16 packetConnMap map[uint16]uint16
} }
func newLoopBackDetector( /*router adapter.Router*/ ) *loopBackDetector { func newLoopBackDetector(router adapter.Router) *loopBackDetector {
return &loopBackDetector{ return &loopBackDetector{
// router: router, router: router,
connMap: make(map[netip.AddrPort]netip.AddrPort), connMap: make(map[netip.AddrPort]netip.AddrPort),
packetConnMap: make(map[uint16]uint16), packetConnMap: make(map[uint16]uint16),
} }
@@ -31,12 +32,12 @@ func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn {
return conn return conn
} }
if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn { if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn {
/*if !source.Addr().IsLoopback() { if !source.Addr().IsLoopback() {
_, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr())
if err != nil { if err != nil {
return conn return conn
} }
}*/ }
if !N.IsPublicAddr(source.Addr()) { if !N.IsPublicAddr(source.Addr()) {
return conn return conn
} }
@@ -57,6 +58,12 @@ func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Soc
if !source.IsValid() { if !source.IsValid() {
return conn return conn
} }
if !source.Addr().IsLoopback() {
_, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr())
if err != nil {
return conn
}
}
l.packetConnAccess.Lock() l.packetConnAccess.Lock()
l.packetConnMap[source.Port()] = destination.AddrPort().Port() l.packetConnMap[source.Port()] = destination.AddrPort().Port()
l.packetConnAccess.Unlock() l.packetConnAccess.Unlock()
@@ -74,12 +81,12 @@ func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.Ad
if !source.IsValid() { if !source.IsValid() {
return false return false
} }
/*if !source.Addr().IsLoopback() { if !source.Addr().IsLoopback() {
_, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr())
if err != nil { if err != nil {
return false return false
} }
}*/ }
if N.IsPublicAddr(source.Addr()) { if N.IsPublicAddr(source.Addr()) {
return false return false
} }

View File

@@ -46,8 +46,8 @@ func (d *DNS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.Pa
} }
func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
metadata.Destination = M.Socksaddr{}
defer conn.Close() defer conn.Close()
ctx = adapter.WithContext(ctx, &metadata)
for { for {
err := d.handleConnection(ctx, conn, metadata) err := d.handleConnection(ctx, conn, metadata)
if err != nil { if err != nil {
@@ -98,6 +98,7 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap
} }
func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
metadata.Destination = M.Socksaddr{}
var reader N.PacketReader = conn var reader N.PacketReader = conn
var counters []N.CountFunc var counters []N.CountFunc
var cachedPackets []*N.PacketBuffer var cachedPackets []*N.PacketBuffer
@@ -111,14 +112,11 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
} }
} }
if readWaiter, created := bufio.CreatePacketReadWaiter(reader); created { if readWaiter, created := bufio.CreatePacketReadWaiter(reader); created {
readWaiter.InitializeReadWaiter(N.ReadWaitOptions{ readWaiter.InitializeReadWaiter(N.ReadWaitOptions{})
MTU: dns.FixedPacketSize,
})
return d.newPacketConnection(ctx, conn, readWaiter, counters, cachedPackets, metadata) return d.newPacketConnection(ctx, conn, readWaiter, counters, cachedPackets, metadata)
} }
break break
} }
ctx = adapter.WithContext(ctx, &metadata)
fastClose, cancel := common.ContextWithCancelCause(ctx) fastClose, cancel := common.ContextWithCancelCause(ctx)
timeout := canceler.New(fastClose, cancel, C.DNSTimeout) timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
var group task.Group var group task.Group
@@ -167,15 +165,11 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
return err return err
} }
timeout.Update() timeout.Update()
responseBuffer := buf.NewPacket() responseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024)
responseBuffer.Resize(1024, 0)
n, err := response.PackBuffer(responseBuffer.FreeBytes())
if err != nil { if err != nil {
cancel(err) cancel(err)
responseBuffer.Release()
return err return err
} }
responseBuffer.Truncate(len(n))
err = conn.WritePacket(responseBuffer, destination) err = conn.WritePacket(responseBuffer, destination)
if err != nil { if err != nil {
cancel(err) cancel(err)
@@ -241,16 +235,11 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa
return err return err
} }
timeout.Update() timeout.Update()
response = truncateDNSMessage(response, 512) // TODO: add an option to custom UDP buffer size responseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024)
responseBuffer := buf.NewSize(dns.FixedPacketSize)
responseBuffer.Resize(1024, 0)
n, err := response.PackBuffer(responseBuffer.FreeBytes())
if err != nil { if err != nil {
cancel(err) cancel(err)
responseBuffer.Release()
return err return err
} }
responseBuffer.Truncate(len(n))
err = conn.WritePacket(responseBuffer, destination) err = conn.WritePacket(responseBuffer, destination)
if err != nil { if err != nil {
cancel(err) cancel(err)
@@ -264,22 +253,3 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa
}) })
return group.Run(fastClose) return group.Run(fastClose)
} }
func truncateDNSMessage(response *mDNS.Msg, maxLen int) *mDNS.Msg {
responseLen := response.Len()
if responseLen <= maxLen {
return response
}
newResponse := *response
response = &newResponse
for len(response.Answer) > 0 && responseLen > maxLen {
response.Answer = response.Answer[:len(response.Answer)-1]
response.Truncated = true
responseLen = response.Len()
}
if responseLen > maxLen {
response.Ns = nil
response.Extra = nil
}
return response
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/sagernet/sing-box/transport/wireguard" "github.com/sagernet/sing-box/transport/wireguard"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
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"
@@ -111,6 +112,25 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
} }
func (w *WireGuard) Start() error { func (w *WireGuard) Start() error {
if common.Any(w.peers, func(peer wireguard.PeerConfig) bool {
return !peer.Endpoint.IsValid()
}) {
// wait for all outbounds to be started and continue in PortStart
return nil
}
return w.start()
}
func (w *WireGuard) PostStart() error {
if common.All(w.peers, func(peer wireguard.PeerConfig) bool {
return peer.Endpoint.IsValid()
}) {
return nil
}
return w.start()
}
func (w *WireGuard) start() error {
err := wireguard.ResolvePeers(w.ctx, w.router, w.peers) err := wireguard.ResolvePeers(w.ctx, w.router, w.peers)
if err != nil { if err != nil {
return err return err

View File

@@ -1,54 +0,0 @@
package route
import (
"net"
"github.com/sagernet/sing/common/control"
)
var _ control.InterfaceFinder = (*myInterfaceFinder)(nil)
type myInterfaceFinder struct {
interfaces []net.Interface
}
func (f *myInterfaceFinder) update() error {
ifs, err := net.Interfaces()
if err != nil {
return err
}
f.interfaces = ifs
return nil
}
func (f *myInterfaceFinder) updateInterfaces(interfaces []net.Interface) {
f.interfaces = interfaces
}
func (f *myInterfaceFinder) InterfaceIndexByName(name string) (interfaceIndex int, err error) {
for _, netInterface := range f.interfaces {
if netInterface.Name == name {
return netInterface.Index, nil
}
}
netInterface, err := net.InterfaceByName(name)
if err != nil {
return
}
f.update()
return netInterface.Index, nil
}
func (f *myInterfaceFinder) InterfaceNameByIndex(index int) (interfaceName string, err error) {
for _, netInterface := range f.interfaces {
if netInterface.Index == index {
return netInterface.Name, nil
}
}
netInterface, err := net.InterfaceByIndex(index)
if err != nil {
return
}
f.update()
return netInterface.Name, nil
}

View File

@@ -8,6 +8,7 @@ import (
"net/url" "net/url"
"os" "os"
"os/user" "os/user"
"runtime"
"strings" "strings"
"time" "time"
@@ -22,12 +23,11 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/ntp"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/transport/fakeip" "github.com/sagernet/sing-box/transport/fakeip"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
mux "github.com/sagernet/sing-mux" "github.com/sagernet/sing-mux"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-vmess" "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
@@ -39,9 +39,10 @@ import (
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
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"
serviceNTP "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/task"
"github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/common/uot"
"github.com/sagernet/sing/common/winpowrprof"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause" "github.com/sagernet/sing/service/pause"
) )
@@ -78,13 +79,14 @@ type Router struct {
transportDomainStrategy map[dns.Transport]dns.DomainStrategy transportDomainStrategy map[dns.Transport]dns.DomainStrategy
dnsReverseMapping *DNSReverseMapping dnsReverseMapping *DNSReverseMapping
fakeIPStore adapter.FakeIPStore fakeIPStore adapter.FakeIPStore
interfaceFinder myInterfaceFinder interfaceFinder *control.DefaultInterfaceFinder
autoDetectInterface bool autoDetectInterface bool
defaultInterface string defaultInterface string
defaultMark int defaultMark int
networkMonitor tun.NetworkUpdateMonitor networkMonitor tun.NetworkUpdateMonitor
interfaceMonitor tun.DefaultInterfaceMonitor interfaceMonitor tun.DefaultInterfaceMonitor
packageManager tun.PackageManager packageManager tun.PackageManager
powerListener winpowrprof.EventListener
processSearcher process.Searcher processSearcher process.Searcher
timeService *ntp.Service timeService *ntp.Service
pauseManager pause.Manager pauseManager pause.Manager
@@ -122,6 +124,7 @@ func NewRouter(
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
defaultDetour: options.Final, defaultDetour: options.Final,
defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy), defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy),
interfaceFinder: control.NewDefaultInterfaceFinder(),
autoDetectInterface: options.AutoDetectInterface, autoDetectInterface: options.AutoDetectInterface,
defaultInterface: options.DefaultInterface, defaultInterface: options.DefaultInterface,
defaultMark: options.DefaultMark, defaultMark: options.DefaultMark,
@@ -136,7 +139,17 @@ func NewRouter(
DisableCache: dnsOptions.DNSClientOptions.DisableCache, DisableCache: dnsOptions.DNSClientOptions.DisableCache,
DisableExpire: dnsOptions.DNSClientOptions.DisableExpire, DisableExpire: dnsOptions.DNSClientOptions.DisableExpire,
IndependentCache: dnsOptions.DNSClientOptions.IndependentCache, IndependentCache: dnsOptions.DNSClientOptions.IndependentCache,
Logger: router.dnsLogger, RDRC: func() dns.RDRCStore {
cacheFile := service.FromContext[adapter.CacheFile](ctx)
if cacheFile == nil {
return nil
}
if !cacheFile.StoreRDRC() {
return nil
}
return cacheFile
},
Logger: router.dnsLogger,
}) })
for i, ruleOptions := range options.Rules { for i, ruleOptions := range options.Rules {
routeRule, err := NewRule(router, router.logger, ruleOptions, true) routeRule, err := NewRule(router, router.logger, ruleOptions, true)
@@ -208,7 +221,7 @@ func NewRouter(
if serverAddress == "" { if serverAddress == "" {
serverAddress = server.Address serverAddress = server.Address
} }
_, notIpAddress := netip.ParseAddr(serverAddress) notIpAddress := !M.ParseSocksaddr(serverAddress).Addr.IsValid()
if server.AddressResolver != "" { if server.AddressResolver != "" {
if !transportTagMap[server.AddressResolver] { if !transportTagMap[server.AddressResolver] {
return nil, E.New("parse dns server[", tag, "]: address resolver not found: ", server.AddressResolver) return nil, E.New("parse dns server[", tag, "]: address resolver not found: ", server.AddressResolver)
@@ -218,11 +231,24 @@ func NewRouter(
} else { } else {
continue continue
} }
} else if notIpAddress != nil && strings.Contains(server.Address, ".") { } else if notIpAddress && strings.Contains(server.Address, ".") {
return nil, E.New("parse dns server[", tag, "]: missing address_resolver") return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
} }
} }
transport, err := dns.CreateTransport(tag, ctx, logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")), detour, server.Address) var clientSubnet netip.Prefix
if server.ClientSubnet != nil {
clientSubnet = server.ClientSubnet.Build()
} else if dnsOptions.ClientSubnet != nil {
clientSubnet = dnsOptions.ClientSubnet.Build()
}
transport, err := dns.CreateTransport(dns.TransportOptions{
Context: ctx,
Logger: logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")),
Name: tag,
Dialer: detour,
Address: server.Address,
ClientSubnet: clientSubnet,
})
if err != nil { if err != nil {
return nil, E.Cause(err, "parse dns server[", tag, "]") return nil, E.Cause(err, "parse dns server[", tag, "]")
} }
@@ -262,7 +288,12 @@ func NewRouter(
} }
if defaultTransport == nil { if defaultTransport == nil {
if len(transports) == 0 { if len(transports) == 0 {
transports = append(transports, dns.NewLocalTransport("local", N.SystemDialer)) transports = append(transports, common.Must1(dns.CreateTransport(dns.TransportOptions{
Context: ctx,
Name: "local",
Address: "local",
Dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{})),
})))
} }
defaultTransport = transports[0] defaultTransport = transports[0]
} }
@@ -303,7 +334,7 @@ func NewRouter(
} }
router.networkMonitor = networkMonitor router.networkMonitor = networkMonitor
networkMonitor.RegisterCallback(func() { networkMonitor.RegisterCallback(func() {
_ = router.interfaceFinder.update() _ = router.interfaceFinder.Update()
}) })
interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{
OverrideAndroidVPN: options.OverrideAndroidVPN, OverrideAndroidVPN: options.OverrideAndroidVPN,
@@ -322,11 +353,19 @@ func NewRouter(
} }
if ntpOptions.Enabled { if ntpOptions.Enabled {
timeService, err := ntp.NewService(ctx, router, logFactory.NewLogger("ntp"), ntpOptions) ntpDialer, err := dialer.New(router, ntpOptions.DialerOptions)
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "create NTP service")
} }
service.ContextWith[serviceNTP.TimeService](ctx, timeService) timeService := ntp.NewService(ntp.Options{
Context: ctx,
Dialer: ntpDialer,
Logger: logFactory.NewLogger("ntp"),
Server: ntpOptions.ServerOptions.Build(),
Interval: time.Duration(ntpOptions.Interval),
WriteToSystem: ntpOptions.WriteToSystem,
})
service.MustRegister[ntp.TimeService](ctx, timeService)
router.timeService = timeService router.timeService = timeService
} }
return router, nil return router, nil
@@ -355,20 +394,17 @@ func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outb
defaultOutboundForPacketConnection = detour defaultOutboundForPacketConnection = detour
} }
} }
var index, packetIndex int
if defaultOutboundForConnection == nil { if defaultOutboundForConnection == nil {
for i, detour := range outbounds { for _, detour := range outbounds {
if common.Contains(detour.Network(), N.NetworkTCP) { if common.Contains(detour.Network(), N.NetworkTCP) {
index = i
defaultOutboundForConnection = detour defaultOutboundForConnection = detour
break break
} }
} }
} }
if defaultOutboundForPacketConnection == nil { if defaultOutboundForPacketConnection == nil {
for i, detour := range outbounds { for _, detour := range outbounds {
if common.Contains(detour.Network(), N.NetworkUDP) { if common.Contains(detour.Network(), N.NetworkUDP) {
packetIndex = i
defaultOutboundForPacketConnection = detour defaultOutboundForPacketConnection = detour
break break
} }
@@ -385,22 +421,6 @@ func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outb
outbounds = append(outbounds, detour) outbounds = append(outbounds, detour)
outboundByTag[detour.Tag()] = detour outboundByTag[detour.Tag()] = detour
} }
if defaultOutboundForConnection != defaultOutboundForPacketConnection {
var description string
if defaultOutboundForConnection.Tag() != "" {
description = defaultOutboundForConnection.Tag()
} else {
description = F.ToString(index)
}
var packetDescription string
if defaultOutboundForPacketConnection.Tag() != "" {
packetDescription = defaultOutboundForPacketConnection.Tag()
} else {
packetDescription = F.ToString(packetIndex)
}
r.logger.Info("using ", defaultOutboundForConnection.Type(), "[", description, "] as default outbound for connection")
r.logger.Info("using ", defaultOutboundForPacketConnection.Type(), "[", packetDescription, "] as default outbound for packet connection")
}
r.inboundByTag = inboundByTag r.inboundByTag = inboundByTag
r.outbounds = outbounds r.outbounds = outbounds
r.defaultOutboundForConnection = defaultOutboundForConnection r.defaultOutboundForConnection = defaultOutboundForConnection
@@ -489,6 +509,133 @@ func (r *Router) Start() error {
r.geositeReader = nil r.geositeReader = nil
} }
if runtime.GOOS == "windows" {
powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent)
if err == nil {
r.powerListener = powerListener
} else {
r.logger.Warn("initialize power listener: ", err)
}
}
if r.powerListener != nil {
monitor.Start("start power listener")
err := r.powerListener.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "start power listener")
}
}
monitor.Start("initialize DNS client")
r.dnsClient.Start()
monitor.Finish()
for i, rule := range r.dnsRules {
monitor.Start("initialize DNS rule[", i, "]")
err := rule.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "initialize DNS rule[", i, "]")
}
}
for i, transport := range r.transports {
monitor.Start("initialize DNS transport[", i, "]")
err := transport.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "initialize DNS server[", i, "]")
}
}
if r.timeService != nil {
monitor.Start("initialize time service")
err := r.timeService.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "initialize time service")
}
}
return nil
}
func (r *Router) Close() error {
monitor := taskmonitor.New(r.logger, C.StopTimeout)
var err error
for i, rule := range r.rules {
monitor.Start("close rule[", i, "]")
err = E.Append(err, rule.Close(), func(err error) error {
return E.Cause(err, "close rule[", i, "]")
})
monitor.Finish()
}
for i, rule := range r.dnsRules {
monitor.Start("close dns rule[", i, "]")
err = E.Append(err, rule.Close(), func(err error) error {
return E.Cause(err, "close dns rule[", i, "]")
})
monitor.Finish()
}
for i, transport := range r.transports {
monitor.Start("close dns transport[", i, "]")
err = E.Append(err, transport.Close(), func(err error) error {
return E.Cause(err, "close dns transport[", i, "]")
})
monitor.Finish()
}
if r.geoIPReader != nil {
monitor.Start("close geoip reader")
err = E.Append(err, r.geoIPReader.Close(), func(err error) error {
return E.Cause(err, "close geoip reader")
})
monitor.Finish()
}
if r.interfaceMonitor != nil {
monitor.Start("close interface monitor")
err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error {
return E.Cause(err, "close interface monitor")
})
monitor.Finish()
}
if r.networkMonitor != nil {
monitor.Start("close network monitor")
err = E.Append(err, r.networkMonitor.Close(), func(err error) error {
return E.Cause(err, "close network monitor")
})
monitor.Finish()
}
if r.packageManager != nil {
monitor.Start("close package manager")
err = E.Append(err, r.packageManager.Close(), func(err error) error {
return E.Cause(err, "close package manager")
})
monitor.Finish()
}
if r.powerListener != nil {
monitor.Start("close power listener")
err = E.Append(err, r.powerListener.Close(), func(err error) error {
return E.Cause(err, "close power listener")
})
monitor.Finish()
}
if r.timeService != nil {
monitor.Start("close time service")
err = E.Append(err, r.timeService.Close(), func(err error) error {
return E.Cause(err, "close time service")
})
monitor.Finish()
}
if r.fakeIPStore != nil {
monitor.Start("close fakeip store")
err = E.Append(err, r.fakeIPStore.Close(), func(err error) error {
return E.Cause(err, "close fakeip store")
})
monitor.Finish()
}
return err
}
func (r *Router) PostStart() error {
monitor := taskmonitor.New(r.logger, C.StopTimeout)
if len(r.ruleSets) > 0 { if len(r.ruleSets) > 0 {
monitor.Start("initialize rule-set") monitor.Start("initialize rule-set")
ruleSetStartContext := NewRuleSetStartContext() ruleSetStartContext := NewRuleSetStartContext()
@@ -569,7 +716,6 @@ func (r *Router) Start() error {
r.updateWIFIState() r.updateWIFIState()
monitor.Finish() monitor.Finish()
} }
for i, rule := range r.rules { for i, rule := range r.rules {
monitor.Start("initialize rule[", i, "]") monitor.Start("initialize rule[", i, "]")
err := rule.Start() err := rule.Start()
@@ -578,111 +724,6 @@ func (r *Router) Start() error {
return E.Cause(err, "initialize rule[", i, "]") return E.Cause(err, "initialize rule[", i, "]")
} }
} }
for i, rule := range r.dnsRules {
monitor.Start("initialize DNS rule[", i, "]")
err := rule.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "initialize DNS rule[", i, "]")
}
}
for i, transport := range r.transports {
monitor.Start("initialize DNS transport[", i, "]")
err := transport.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "initialize DNS server[", i, "]")
}
}
if r.timeService != nil {
monitor.Start("initialize time service")
err := r.timeService.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "initialize time service")
}
}
return nil
}
func (r *Router) Close() error {
monitor := taskmonitor.New(r.logger, C.StopTimeout)
var err error
for i, rule := range r.rules {
monitor.Start("close rule[", i, "]")
err = E.Append(err, rule.Close(), func(err error) error {
return E.Cause(err, "close rule[", i, "]")
})
monitor.Finish()
}
for i, rule := range r.dnsRules {
monitor.Start("close dns rule[", i, "]")
err = E.Append(err, rule.Close(), func(err error) error {
return E.Cause(err, "close dns rule[", i, "]")
})
monitor.Finish()
}
for i, transport := range r.transports {
monitor.Start("close dns transport[", i, "]")
err = E.Append(err, transport.Close(), func(err error) error {
return E.Cause(err, "close dns transport[", i, "]")
})
monitor.Finish()
}
if r.geoIPReader != nil {
monitor.Start("close geoip reader")
err = E.Append(err, r.geoIPReader.Close(), func(err error) error {
return E.Cause(err, "close geoip reader")
})
monitor.Finish()
}
if r.interfaceMonitor != nil {
monitor.Start("close interface monitor")
err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error {
return E.Cause(err, "close interface monitor")
})
monitor.Finish()
}
if r.networkMonitor != nil {
monitor.Start("close network monitor")
err = E.Append(err, r.networkMonitor.Close(), func(err error) error {
return E.Cause(err, "close network monitor")
})
monitor.Finish()
}
if r.packageManager != nil {
monitor.Start("close package manager")
err = E.Append(err, r.packageManager.Close(), func(err error) error {
return E.Cause(err, "close package manager")
})
monitor.Finish()
}
if r.timeService != nil {
monitor.Start("close time service")
err = E.Append(err, r.timeService.Close(), func(err error) error {
return E.Cause(err, "close time service")
})
monitor.Finish()
}
if r.fakeIPStore != nil {
monitor.Start("close fakeip store")
err = E.Append(err, r.fakeIPStore.Close(), func(err error) error {
return E.Cause(err, "close fakeip store")
})
monitor.Finish()
}
return err
}
func (r *Router) PostStart() error {
if len(r.ruleSets) > 0 {
for i, ruleSet := range r.ruleSets {
err := ruleSet.PostStart()
if err != nil {
return E.Cause(err, "post start rule-set[", i, "]")
}
}
}
r.started = true r.started = true
return nil return nil
} }
@@ -1028,24 +1069,18 @@ func (r *Router) match0(ctx context.Context, metadata *adapter.InboundContext, d
} }
func (r *Router) InterfaceFinder() control.InterfaceFinder { func (r *Router) InterfaceFinder() control.InterfaceFinder {
return &r.interfaceFinder return r.interfaceFinder
} }
func (r *Router) UpdateInterfaces() error { func (r *Router) UpdateInterfaces() error {
if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() {
return r.interfaceFinder.update() return r.interfaceFinder.Update()
} else { } else {
interfaces, err := r.platformInterface.Interfaces() interfaces, err := r.platformInterface.Interfaces()
if err != nil { if err != nil {
return err return err
} }
r.interfaceFinder.updateInterfaces(common.Map(interfaces, func(it platform.NetworkInterface) net.Interface { r.interfaceFinder.UpdateInterfaces(interfaces)
return net.Interface{
Name: it.Name,
Index: it.Index,
MTU: it.MTU,
}
}))
return nil return nil
} }
} }
@@ -1058,6 +1093,9 @@ func (r *Router) AutoDetectInterfaceFunc() control.Func {
if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() {
return r.platformInterface.AutoDetectInterfaceControl() return r.platformInterface.AutoDetectInterfaceControl()
} else { } else {
if r.interfaceMonitor == nil {
return nil
}
return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int, err error) { return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
remoteAddr := M.ParseSocksaddr(address).Addr remoteAddr := M.ParseSocksaddr(address).Addr
if C.IsLinux { if C.IsLinux {
@@ -1189,3 +1227,19 @@ func (r *Router) updateWIFIState() {
} }
} }
} }
func (r *Router) notifyWindowsPowerEvent(event int) {
switch event {
case winpowrprof.EVENT_SUSPEND:
r.pauseManager.DevicePause()
_ = r.ResetNetwork()
case winpowrprof.EVENT_RESUME:
if !r.pauseManager.IsDevicePaused() {
return
}
fallthrough
case winpowrprof.EVENT_RESUME_AUTOMATIC:
r.pauseManager.DeviceWake()
_ = r.ResetNetwork()
}
}

View File

@@ -2,13 +2,13 @@ package route
import ( import (
"context" "context"
"errors"
"net/netip" "net/netip"
"strings" "strings"
"time" "time"
"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/log"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/cache" "github.com/sagernet/sing/common/cache"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@@ -37,41 +37,55 @@ func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) {
return domain, loaded return domain, loaded
} }
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool) (context.Context, dns.Transport, dns.DomainStrategy) { func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (context.Context, dns.Transport, dns.DomainStrategy, adapter.DNSRule, int) {
metadata := adapter.ContextFrom(ctx) metadata := adapter.ContextFrom(ctx)
if metadata == nil { if metadata == nil {
panic("no context") panic("no context")
} }
for i, rule := range r.dnsRules { if index < len(r.dnsRules) {
metadata.ResetRuleCache() dnsRules := r.dnsRules
if rule.Match(metadata) { if index != -1 {
detour := rule.Outbound() dnsRules = dnsRules[index+1:]
transport, loaded := r.transportMap[detour] }
if !loaded { for currentRuleIndex, rule := range dnsRules {
r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour) metadata.ResetRuleCache()
continue if rule.Match(metadata) {
} detour := rule.Outbound()
if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP { transport, loaded := r.transportMap[detour]
continue if !loaded {
} r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour) continue
if rule.DisableCache() { }
ctx = dns.ContextWithDisableCache(ctx, true) _, isFakeIP := transport.(adapter.FakeIPTransport)
} if isFakeIP && !allowFakeIP {
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { continue
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) }
} ruleIndex := currentRuleIndex
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { if index != -1 {
return ctx, transport, domainStrategy ruleIndex += index + 1
} else { }
return ctx, transport, r.defaultDomainStrategy r.dnsLogger.DebugContext(ctx, "match[", ruleIndex, "] ", rule.String(), " => ", detour)
if isFakeIP || rule.DisableCache() {
ctx = dns.ContextWithDisableCache(ctx, true)
}
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
}
if clientSubnet := rule.ClientSubnet(); clientSubnet != nil {
ctx = dns.ContextWithClientSubnet(ctx, *clientSubnet)
}
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
return ctx, transport, domainStrategy, rule, ruleIndex
} else {
return ctx, transport, r.defaultDomainStrategy, rule, ruleIndex
}
} }
} }
} }
if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded { if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded {
return ctx, r.defaultTransport, domainStrategy return ctx, r.defaultTransport, domainStrategy, nil, -1
} else { } else {
return ctx, r.defaultTransport, r.defaultDomainStrategy return ctx, r.defaultTransport, r.defaultDomainStrategy, nil, -1
} }
} }
@@ -80,13 +94,15 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String())) r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()))
} }
var ( var (
response *mDNS.Msg response *mDNS.Msg
cached bool cached bool
err error transport dns.Transport
err error
) )
response, cached = r.dnsClient.ExchangeCache(ctx, message) response, cached = r.dnsClient.ExchangeCache(ctx, message)
if !cached { if !cached {
ctx, metadata := adapter.AppendContext(ctx) var metadata *adapter.InboundContext
ctx, metadata = adapter.AppendContext(ctx)
if len(message.Question) > 0 { if len(message.Question) > 0 {
metadata.QueryType = message.Question[0].Qtype metadata.QueryType = message.Question[0].Qtype
switch metadata.QueryType { switch metadata.QueryType {
@@ -97,50 +113,134 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
} }
metadata.Domain = fqdnToDomain(message.Question[0].Name) metadata.Domain = fqdnToDomain(message.Question[0].Name)
} }
ctx, transport, strategy := r.matchDNS(ctx, true) var (
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout) strategy dns.DomainStrategy
defer cancel() rule adapter.DNSRule
response, err = r.dnsClient.Exchange(ctx, transport, message, strategy) ruleIndex int
if err != nil && len(message.Question) > 0 { )
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String()))) ruleIndex = -1
for {
var (
dnsCtx context.Context
cancel context.CancelFunc
addressLimit bool
)
dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex)
dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout)
if rule != nil && rule.WithAddressLimit() && isAddressQuery(message) {
addressLimit = true
response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool {
metadata.DestinationAddresses, _ = dns.MessageToAddresses(response)
return rule.MatchAddressLimit(metadata)
})
} else {
addressLimit = false
response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy)
}
cancel()
var rejected bool
if err != nil {
if errors.Is(err, dns.ErrResponseRejectedCached) {
rejected = true
r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String())), " (cached)")
} else if errors.Is(err, dns.ErrResponseRejected) {
rejected = true
r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String())))
} else if len(message.Question) > 0 {
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
} else {
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
}
}
if addressLimit && rejected {
continue
}
break
} }
} }
if len(message.Question) > 0 && response != nil { if err != nil {
LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer) return nil, err
} }
if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 {
for _, answer := range response.Answer { if _, isFakeIP := transport.(adapter.FakeIPTransport); !isFakeIP {
switch record := answer.(type) { for _, answer := range response.Answer {
case *mDNS.A: switch record := answer.(type) {
r.dnsReverseMapping.Save(M.AddrFromIP(record.A), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) case *mDNS.A:
case *mDNS.AAAA: r.dnsReverseMapping.Save(M.AddrFromIP(record.A), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl))
r.dnsReverseMapping.Save(M.AddrFromIP(record.AAAA), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) case *mDNS.AAAA:
r.dnsReverseMapping.Save(M.AddrFromIP(record.AAAA), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl))
}
} }
} }
} }
return response, err return response, nil
} }
func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
var (
responseAddrs []netip.Addr
cached bool
err error
)
responseAddrs, cached = r.dnsClient.LookupCache(ctx, domain, strategy)
if cached {
return responseAddrs, nil
}
r.dnsLogger.DebugContext(ctx, "lookup domain ", domain) r.dnsLogger.DebugContext(ctx, "lookup domain ", domain)
ctx, metadata := adapter.AppendContext(ctx) ctx, metadata := adapter.AppendContext(ctx)
metadata.Domain = domain metadata.Domain = domain
ctx, transport, transportStrategy := r.matchDNS(ctx, false) var (
if strategy == dns.DomainStrategyAsIS { transport dns.Transport
strategy = transportStrategy transportStrategy dns.DomainStrategy
rule adapter.DNSRule
ruleIndex int
)
ruleIndex = -1
for {
var (
dnsCtx context.Context
cancel context.CancelFunc
addressLimit bool
)
metadata.ResetRuleCache()
metadata.DestinationAddresses = nil
dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex)
if strategy == dns.DomainStrategyAsIS {
strategy = transportStrategy
}
dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout)
if rule != nil && rule.WithAddressLimit() {
addressLimit = true
responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool {
metadata.DestinationAddresses = responseAddrs
return rule.MatchAddressLimit(metadata)
})
} else {
addressLimit = false
responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy)
}
cancel()
if err != nil {
if errors.Is(err, dns.ErrResponseRejectedCached) {
r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
} else if errors.Is(err, dns.ErrResponseRejected) {
r.dnsLogger.DebugContext(ctx, "response rejected for ", domain)
} else {
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
}
} else if len(responseAddrs) == 0 {
r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
err = dns.RCodeNameError
}
if !addressLimit || err == nil {
break
}
} }
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout) if len(responseAddrs) > 0 {
defer cancel() r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " "))
addrs, err := r.dnsClient.Lookup(ctx, transport, domain, strategy)
if len(addrs) > 0 {
r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(addrs), " "))
} else if err != nil {
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
} else {
r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
err = dns.RCodeNameError
} }
return addrs, err return responseAddrs, err
} }
func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) { func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) {
@@ -154,10 +254,13 @@ func (r *Router) ClearDNSCache() {
} }
} }
func LogDNSAnswers(logger log.ContextLogger, ctx context.Context, domain string, answers []mDNS.RR) { func isAddressQuery(message *mDNS.Msg) bool {
for _, answer := range answers { for _, question := range message.Question {
logger.InfoContext(ctx, "exchanged ", domain, " ", mDNS.Type(answer.Header().Rrtype).String(), " ", formatQuestion(answer.String())) if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
return true
}
} }
return false
} }
func fqdnToDomain(fqdn string) string { func fqdnToDomain(fqdn string) string {

View File

@@ -59,7 +59,7 @@ func isGeoIPRule(rule option.DefaultRule) bool {
} }
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool { func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
} }
func isGeositeRule(rule option.DefaultRule) bool { func isGeositeRule(rule option.DefaultRule) bool {
@@ -97,3 +97,7 @@ func isWIFIDNSRule(rule option.DefaultDNSRule) bool {
func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {
return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0
} }
func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool {
return len(rule.IPCIDR) > 0 || rule.IPSet != nil
}

View File

@@ -15,6 +15,7 @@ type abstractDefaultRule struct {
sourceAddressItems []RuleItem sourceAddressItems []RuleItem
sourcePortItems []RuleItem sourcePortItems []RuleItem
destinationAddressItems []RuleItem destinationAddressItems []RuleItem
destinationIPCIDRItems []RuleItem
destinationPortItems []RuleItem destinationPortItems []RuleItem
allItems []RuleItem allItems []RuleItem
ruleSetItem RuleItem ruleSetItem RuleItem
@@ -64,6 +65,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
} }
if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch { if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {
metadata.DidMatch = true
for _, item := range r.sourceAddressItems { for _, item := range r.sourceAddressItems {
if item.Match(metadata) { if item.Match(metadata) {
metadata.SourceAddressMatch = true metadata.SourceAddressMatch = true
@@ -73,6 +75,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
} }
if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch { if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {
metadata.DidMatch = true
for _, item := range r.sourcePortItems { for _, item := range r.sourcePortItems {
if item.Match(metadata) { if item.Match(metadata) {
metadata.SourcePortMatch = true metadata.SourcePortMatch = true
@@ -82,6 +85,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
} }
if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch { if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch {
metadata.DidMatch = true
for _, item := range r.destinationAddressItems { for _, item := range r.destinationAddressItems {
if item.Match(metadata) { if item.Match(metadata) {
metadata.DestinationAddressMatch = true metadata.DestinationAddressMatch = true
@@ -90,7 +94,18 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
} }
} }
if !metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0 && !metadata.DestinationAddressMatch {
metadata.DidMatch = true
for _, item := range r.destinationIPCIDRItems {
if item.Match(metadata) {
metadata.DestinationAddressMatch = true
break
}
}
}
if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch { if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {
metadata.DidMatch = true
for _, item := range r.destinationPortItems { for _, item := range r.destinationPortItems {
if item.Match(metadata) { if item.Match(metadata) {
metadata.DestinationPortMatch = true metadata.DestinationPortMatch = true
@@ -100,6 +115,9 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
} }
for _, item := range r.items { for _, item := range r.items {
if _, isRuleSet := item.(*RuleSetItem); !isRuleSet {
metadata.DidMatch = true
}
if !item.Match(metadata) { if !item.Match(metadata) {
return r.invert return r.invert
} }
@@ -113,7 +131,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
return r.invert return r.invert
} }
if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch { if ((!metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0) || len(r.destinationAddressItems) > 0) && !metadata.DestinationAddressMatch {
return r.invert return r.invert
} }
@@ -121,6 +139,10 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
return r.invert return r.invert
} }
if !metadata.DidMatch {
return true
}
return !r.invert return !r.invert
} }

View File

@@ -109,7 +109,7 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
} }
if len(options.GeoIP) > 0 { if len(options.GeoIP) > 0 {
item := NewGeoIPItem(router, logger, false, options.GeoIP) item := NewGeoIPItem(router, logger, false, options.GeoIP)
rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.SourceIPCIDR) > 0 { if len(options.SourceIPCIDR) > 0 {
@@ -130,12 +130,12 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
if err != nil { if err != nil {
return nil, E.Cause(err, "ipcidr") return nil, E.Cause(err, "ipcidr")
} }
rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if options.IPIsPrivate { if options.IPIsPrivate {
item := NewIPIsPrivateItem(false) item := NewIPIsPrivateItem(false)
rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.SourcePort) > 0 { if len(options.SourcePort) > 0 {

View File

@@ -1,10 +1,13 @@
package route package route
import ( import (
"net/netip"
"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/log" "github.com/sagernet/sing-box/log"
"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"
) )
@@ -37,6 +40,7 @@ type DefaultDNSRule struct {
abstractDefaultRule abstractDefaultRule
disableCache bool disableCache bool
rewriteTTL *uint32 rewriteTTL *uint32
clientSubnet *netip.Prefix
} }
func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
@@ -47,6 +51,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
}, },
disableCache: options.DisableCache, disableCache: options.DisableCache,
rewriteTTL: options.RewriteTTL, rewriteTTL: options.RewriteTTL,
clientSubnet: (*netip.Prefix)(options.ClientSubnet),
} }
if len(options.Inbound) > 0 { if len(options.Inbound) > 0 {
item := NewInboundRule(options.Inbound) item := NewInboundRule(options.Inbound)
@@ -111,6 +116,11 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
rule.sourceAddressItems = append(rule.sourceAddressItems, item) rule.sourceAddressItems = append(rule.sourceAddressItems, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.GeoIP) > 0 {
item := NewGeoIPItem(router, logger, false, options.GeoIP)
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourceIPCIDR) > 0 { if len(options.SourceIPCIDR) > 0 {
item, err := NewIPCIDRItem(true, options.SourceIPCIDR) item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
if err != nil { if err != nil {
@@ -119,11 +129,24 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
rule.sourceAddressItems = append(rule.sourceAddressItems, item) rule.sourceAddressItems = append(rule.sourceAddressItems, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.IPCIDR) > 0 {
item, err := NewIPCIDRItem(false, options.IPCIDR)
if err != nil {
return nil, E.Cause(err, "ip_cidr")
}
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
rule.allItems = append(rule.allItems, item)
}
if options.SourceIPIsPrivate { if options.SourceIPIsPrivate {
item := NewIPIsPrivateItem(true) item := NewIPIsPrivateItem(true)
rule.sourceAddressItems = append(rule.sourceAddressItems, item) rule.sourceAddressItems = append(rule.sourceAddressItems, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if options.IPIsPrivate {
item := NewIPIsPrivateItem(false)
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourcePort) > 0 { if len(options.SourcePort) > 0 {
item := NewPortItem(true, options.SourcePort) item := NewPortItem(true, options.SourcePort)
rule.sourcePortItems = append(rule.sourcePortItems, item) rule.sourcePortItems = append(rule.sourcePortItems, item)
@@ -196,7 +219,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.RuleSet) > 0 { if len(options.RuleSet) > 0 {
item := NewRuleSetItem(router, options.RuleSet, false) item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource)
rule.items = append(rule.items, item) rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
@@ -211,12 +234,45 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 {
return r.rewriteTTL return r.rewriteTTL
} }
func (r *DefaultDNSRule) ClientSubnet() *netip.Prefix {
return r.clientSubnet
}
func (r *DefaultDNSRule) WithAddressLimit() bool {
if len(r.destinationIPCIDRItems) > 0 {
return true
}
for _, rawRule := range r.items {
ruleSet, isRuleSet := rawRule.(*RuleSetItem)
if !isRuleSet {
continue
}
if ruleSet.ContainsDestinationIPCIDRRule() {
return true
}
}
return false
}
func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
metadata.IgnoreDestinationIPCIDRMatch = true
defer func() {
metadata.IgnoreDestinationIPCIDRMatch = false
}()
return r.abstractDefaultRule.Match(metadata)
}
func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
return r.abstractDefaultRule.Match(metadata)
}
var _ adapter.DNSRule = (*LogicalDNSRule)(nil) var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
type LogicalDNSRule struct { type LogicalDNSRule struct {
abstractLogicalRule abstractLogicalRule
disableCache bool disableCache bool
rewriteTTL *uint32 rewriteTTL *uint32
clientSubnet *netip.Prefix
} }
func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
@@ -228,6 +284,7 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options
}, },
disableCache: options.DisableCache, disableCache: options.DisableCache,
rewriteTTL: options.RewriteTTL, rewriteTTL: options.RewriteTTL,
clientSubnet: (*netip.Prefix)(options.ClientSubnet),
} }
switch options.Mode { switch options.Mode {
case C.LogicalTypeAnd: case C.LogicalTypeAnd:
@@ -254,3 +311,51 @@ func (r *LogicalDNSRule) DisableCache() bool {
func (r *LogicalDNSRule) RewriteTTL() *uint32 { func (r *LogicalDNSRule) RewriteTTL() *uint32 {
return r.rewriteTTL return r.rewriteTTL
} }
func (r *LogicalDNSRule) ClientSubnet() *netip.Prefix {
return r.clientSubnet
}
func (r *LogicalDNSRule) WithAddressLimit() bool {
for _, rawRule := range r.rules {
switch rule := rawRule.(type) {
case *DefaultDNSRule:
if rule.WithAddressLimit() {
return true
}
case *LogicalDNSRule:
if rule.WithAddressLimit() {
return true
}
}
}
return false
}
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
if r.mode == C.LogicalTypeAnd {
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
metadata.ResetRuleCache()
return it.(adapter.DNSRule).Match(metadata)
}) != r.invert
} else {
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
metadata.ResetRuleCache()
return it.(adapter.DNSRule).Match(metadata)
}) != r.invert
}
}
func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
if r.mode == C.LogicalTypeAnd {
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
metadata.ResetRuleCache()
return it.(adapter.DNSRule).MatchAddressLimit(metadata)
}) != r.invert
} else {
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
metadata.ResetRuleCache()
return it.(adapter.DNSRule).MatchAddressLimit(metadata)
}) != r.invert
}
}

View File

@@ -80,11 +80,11 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles
if err != nil { if err != nil {
return nil, E.Cause(err, "ipcidr") return nil, E.Cause(err, "ipcidr")
} }
rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} else if options.IPSet != nil { } else if options.IPSet != nil {
item := NewRawIPCIDRItem(false, options.IPSet) item := NewRawIPCIDRItem(false, options.IPSet)
rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.SourcePort) > 0 { if len(options.SourcePort) > 0 {
@@ -129,14 +129,18 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.WIFISSID) > 0 { if len(options.WIFISSID) > 0 {
item := NewWIFISSIDItem(router, options.WIFISSID) if router != nil {
rule.items = append(rule.items, item) item := NewWIFISSIDItem(router, options.WIFISSID)
rule.allItems = append(rule.allItems, item) rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
} }
if len(options.WIFIBSSID) > 0 { if len(options.WIFIBSSID) > 0 {
item := NewWIFIBSSIDItem(router, options.WIFIBSSID) if router != nil {
rule.items = append(rule.items, item) item := NewWIFIBSSIDItem(router, options.WIFIBSSID)
rule.allItems = append(rule.allItems, item) rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
} }
return rule, nil return rule, nil
} }

View File

@@ -4,6 +4,7 @@ import (
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
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"
) )
@@ -13,7 +14,7 @@ var _ RuleItem = (*RuleSetItem)(nil)
type RuleSetItem struct { type RuleSetItem struct {
router adapter.Router router adapter.Router
tagList []string tagList []string
setList []adapter.HeadlessRule setList []adapter.RuleSet
ipcidrMatchSource bool ipcidrMatchSource bool
} }
@@ -46,6 +47,15 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
return false return false
} }
func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool {
if r.ipcidrMatchSource {
return false
}
return common.Any(r.setList, func(ruleSet adapter.RuleSet) bool {
return ruleSet.Metadata().ContainsIPCIDRRule
})
}
func (r *RuleSetItem) String() string { func (r *RuleSetItem) String() string {
if len(r.tagList) == 1 { if len(r.tagList) == 1 {
return F.ToString("rule_set=", r.tagList[0]) return F.ToString("rule_set=", r.tagList[0])

View File

@@ -3,12 +3,14 @@ package route
import ( import (
"context" "context"
"os" "os"
"strings"
"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"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
) )
@@ -55,6 +57,7 @@ func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleS
var metadata adapter.RuleSetMetadata var metadata adapter.RuleSetMetadata
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)
return &LocalRuleSet{rules, metadata}, nil return &LocalRuleSet{rules, metadata}, nil
} }
@@ -67,11 +70,11 @@ func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {
return false return false
} }
func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error { func (s *LocalRuleSet) String() string {
return nil return strings.Join(F.MapToString(s.rules), " ")
} }
func (s *LocalRuleSet) PostStart() error { func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error {
return nil return nil
} }

View File

@@ -7,6 +7,7 @@ import (
"net" "net"
"net/http" "net/http"
"runtime" "runtime"
"strings"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -14,6 +15,7 @@ import (
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"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"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"
@@ -68,6 +70,10 @@ func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
return false return false
} }
func (s *RemoteRuleSet) String() string {
return strings.Join(F.MapToString(s.rules), " ")
}
func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error { func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error {
var dialer N.Dialer var dialer N.Dialer
if s.options.RemoteOptions.DownloadDetour != "" { if s.options.RemoteOptions.DownloadDetour != "" {
@@ -106,16 +112,6 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.R
return nil return nil
} }
func (s *RemoteRuleSet) PostStart() error {
if s.lastUpdated.IsZero() {
err := s.fetchOnce(s.ctx, nil)
if err != nil {
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
}
}
return nil
}
func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata { func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
return s.metadata return s.metadata
} }
@@ -150,6 +146,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
} }
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
s.rules = rules s.rules = rules
return nil return nil
} }

View File

@@ -21,9 +21,6 @@ import (
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/task"
"github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/common/x/list"
@@ -32,14 +29,14 @@ import (
) )
func init() { func init() {
dns.RegisterTransport([]string{"dhcp"}, NewTransport) dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) {
return NewTransport(options)
})
} }
type Transport struct { type Transport struct {
name string options dns.TransportOptions
ctx context.Context
router adapter.Router router adapter.Router
logger logger.Logger
interfaceName string interfaceName string
autoInterface bool autoInterface bool
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
@@ -48,23 +45,21 @@ type Transport struct {
updatedAt time.Time updatedAt time.Time
} }
func NewTransport(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { func NewTransport(options dns.TransportOptions) (*Transport, error) {
linkURL, err := url.Parse(link) linkURL, err := url.Parse(options.Address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if linkURL.Host == "" { if linkURL.Host == "" {
return nil, E.New("missing interface name for DHCP") return nil, E.New("missing interface name for DHCP")
} }
router := adapter.RouterFromContext(ctx) router := adapter.RouterFromContext(options.Context)
if router == nil { if router == nil {
return nil, E.New("missing router in context") return nil, E.New("missing router in context")
} }
transport := &Transport{ transport := &Transport{
name: name, options: options,
ctx: ctx,
router: router, router: router,
logger: logger,
interfaceName: linkURL.Host, interfaceName: linkURL.Host,
autoInterface: linkURL.Host == "auto", autoInterface: linkURL.Host == "auto",
} }
@@ -72,7 +67,7 @@ func NewTransport(name string, ctx context.Context, logger logger.ContextLogger,
} }
func (t *Transport) Name() string { func (t *Transport) Name() string {
return t.name return t.options.Name
} }
func (t *Transport) Start() error { func (t *Transport) Start() error {
@@ -158,8 +153,8 @@ func (t *Transport) updateServers() error {
return E.Cause(err, "dhcp: prepare interface") return E.Cause(err, "dhcp: prepare interface")
} }
t.logger.Info("dhcp: query DNS servers on ", iface.Name) t.options.Logger.Info("dhcp: query DNS servers on ", iface.Name)
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout) fetchCtx, cancel := context.WithTimeout(t.options.Context, C.DHCPTimeout)
err = t.fetchServers0(fetchCtx, iface) err = t.fetchServers0(fetchCtx, iface)
cancel() cancel()
if err != nil { if err != nil {
@@ -175,7 +170,7 @@ func (t *Transport) updateServers() error {
func (t *Transport) interfaceUpdated(int) { func (t *Transport) interfaceUpdated(int) {
err := t.updateServers() err := t.updateServers()
if err != nil { if err != nil {
t.logger.Error("update servers: ", err) t.options.Logger.Error("update servers: ", err)
} }
} }
@@ -187,7 +182,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) err
if runtime.GOOS == "linux" || runtime.GOOS == "android" { if runtime.GOOS == "linux" || runtime.GOOS == "android" {
listenAddr = "255.255.255.255:68" listenAddr = "255.255.255.255:68"
} }
packetConn, err := listener.ListenPacket(t.ctx, "udp4", listenAddr) packetConn, err := listener.ListenPacket(t.options.Context, "udp4", listenAddr)
if err != nil { if err != nil {
return err return err
} }
@@ -225,17 +220,17 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa
dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes()) dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes())
if err != nil { if err != nil {
t.logger.Trace("dhcp: parse DHCP response: ", err) t.options.Logger.Trace("dhcp: parse DHCP response: ", err)
return err return err
} }
if dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer { if dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer {
t.logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType()) t.options.Logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType())
continue continue
} }
if dhcpPacket.TransactionID != transactionID { if dhcpPacket.TransactionID != transactionID {
t.logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID) t.options.Logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID)
continue continue
} }
@@ -255,20 +250,22 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa
func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error { func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error {
if len(serverAddrs) > 0 { if len(serverAddrs) > 0 {
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string { t.options.Logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string {
return it.String() return it.String()
}), ","), "]") }), ","), "]")
} }
serverDialer := common.Must1(dialer.NewDefault(t.router, option.DialerOptions{ serverDialer := common.Must1(dialer.NewDefault(t.router, option.DialerOptions{
BindInterface: iface.Name, BindInterface: iface.Name,
UDPFragmentDefault: true, UDPFragmentDefault: true,
})) }))
var transports []dns.Transport var transports []dns.Transport
for _, serverAddr := range serverAddrs { for _, serverAddr := range serverAddrs {
serverTransport, err := dns.NewUDPTransport(t.name, t.ctx, serverDialer, M.Socksaddr{Addr: serverAddr, Port: 53}) newOptions := t.options
newOptions.Address = serverAddr.String()
newOptions.Dialer = serverDialer
serverTransport, err := dns.NewUDPTransport(newOptions)
if err != nil { if err != nil {
return err return E.Cause(err, "create UDP transport from DHCP result: ", serverAddr)
} }
transports = append(transports, serverTransport) transports = append(transports, serverTransport)
} }

View File

@@ -1,55 +0,0 @@
package fakeip
import (
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var _ N.PacketConn = (*NATPacketConn)(nil)
type NATPacketConn struct {
N.PacketConn
origin M.Socksaddr
destination M.Socksaddr
}
func NewNATPacketConn(conn N.PacketConn, origin M.Socksaddr, destination M.Socksaddr) *NATPacketConn {
return &NATPacketConn{
PacketConn: conn,
origin: socksaddrWithoutPort(origin),
destination: socksaddrWithoutPort(destination),
}
}
func (c *NATPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = c.PacketConn.ReadPacket(buffer)
if socksaddrWithoutPort(destination) == c.origin {
destination = M.Socksaddr{
Addr: c.destination.Addr,
Fqdn: c.destination.Fqdn,
Port: destination.Port,
}
}
return
}
func (c *NATPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
if socksaddrWithoutPort(destination) == c.destination {
destination = M.Socksaddr{
Addr: c.origin.Addr,
Fqdn: c.origin.Fqdn,
Port: destination.Port,
}
}
return c.PacketConn.WritePacket(buffer, destination)
}
func (c *NATPacketConn) Upstream() any {
return c.PacketConn
}
func socksaddrWithoutPort(destination M.Socksaddr) M.Socksaddr {
destination.Port = 0
return destination
}

Some files were not shown because too many files have changed in this diff Show More