From daee0b154e618f4b6c7490cbfa6c919f68b60b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 27 Dec 2023 18:05:52 +0800 Subject: [PATCH 01/30] badtls: Support uTLS and TLS ECH for read waiter --- common/badtls/read_wait.go | 78 +++++++++++++++++++++++---------- common/badtls/read_wait_ech.go | 31 +++++++++++++ common/badtls/read_wait_utls.go | 31 +++++++++++++ 3 files changed, 117 insertions(+), 23 deletions(-) create mode 100644 common/badtls/read_wait_ech.go create mode 100644 common/badtls/read_wait_utls.go diff --git a/common/badtls/read_wait.go b/common/badtls/read_wait.go index fdae8a1c..334bcfa8 100644 --- a/common/badtls/read_wait.go +++ b/common/badtls/read_wait.go @@ -4,6 +4,8 @@ package badtls import ( "bytes" + "context" + "net" "os" "reflect" "sync" @@ -18,20 +20,32 @@ import ( var _ N.ReadWaiter = (*ReadWaitConn)(nil) type ReadWaitConn struct { - *tls.STDConn - halfAccess *sync.Mutex - rawInput *bytes.Buffer - input *bytes.Reader - hand *bytes.Buffer - readWaitOptions N.ReadWaitOptions + tls.Conn + halfAccess *sync.Mutex + rawInput *bytes.Buffer + input *bytes.Reader + hand *bytes.Buffer + readWaitOptions N.ReadWaitOptions + tlsReadRecord func() error + tlsHandlePostHandshakeMessage func() error } func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) { - stdConn, isSTDConn := conn.(*tls.STDConn) - if !isSTDConn { + var ( + 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 } - rawConn := reflect.Indirect(reflect.ValueOf(stdConn)) + rawConn := reflect.Indirect(reflect.ValueOf(conn)) rawHalfConn := rawConn.FieldByName("in") if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct { 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())) return &ReadWaitConn{ - STDConn: stdConn, - halfAccess: halfAccess, - rawInput: rawInput, - input: input, - hand: hand, + Conn: conn, + halfAccess: halfAccess, + rawInput: rawInput, + input: input, + hand: hand, + tlsReadRecord: tlsReadRecord, + tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage, }, nil } @@ -71,19 +87,19 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy } func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) { - err = c.Handshake() + err = c.HandshakeContext(context.Background()) if err != nil { return } c.halfAccess.Lock() defer c.halfAccess.Unlock() for c.input.Len() == 0 { - err = tlsReadRecord(c.STDConn) + err = c.tlsReadRecord() if err != nil { return } for c.hand.Len() > 0 { - err = tlsHandlePostHandshakeMessage(c.STDConn) + err = c.tlsHandlePostHandshakeMessage() if err != nil { 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 && // recordType(c.rawInput.Bytes()[0]) == recordTypeAlert { c.rawInput.Bytes()[0] == 21 { - _ = tlsReadRecord(c.STDConn) + _ = c.tlsReadRecord() // 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 { - return c.STDConn + return c.Conn } -//go:linkname tlsReadRecord crypto/tls.(*Conn).readRecord -func tlsReadRecord(c *tls.STDConn) error +var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) -//go:linkname tlsHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage -func tlsHandlePostHandshakeMessage(c *tls.STDConn) error +func init() { + 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 diff --git a/common/badtls/read_wait_ech.go b/common/badtls/read_wait_ech.go new file mode 100644 index 00000000..6a0d5b5f --- /dev/null +++ b/common/badtls/read_wait_ech.go @@ -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 diff --git a/common/badtls/read_wait_utls.go b/common/badtls/read_wait_utls.go new file mode 100644 index 00000000..ebdb2251 --- /dev/null +++ b/common/badtls/read_wait_utls.go @@ -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 From cbcf005f37af6c45f3bca0025718bdccd4f63e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 5 Jan 2024 14:06:15 +0800 Subject: [PATCH 02/30] 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. --- common/process/searcher_windows.go | 2 +- docs/migration.md | 15 +++++++++++++++ docs/migration.zh.md | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/common/process/searcher_windows.go b/common/process/searcher_windows.go index f13b440e..5b3d59b5 100644 --- a/common/process/searcher_windows.go +++ b/common/process/searcher_windows.go @@ -223,7 +223,7 @@ func getExecPathFromPID(pid uint32) (string, error) { r1, _, err := syscall.SyscallN( procQueryFullProcessImageNameW.Addr(), uintptr(h), - uintptr(1), + uintptr(0), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)), ) diff --git a/docs/migration.md b/docs/migration.md index 44ddd833..b6ac0d8a 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -2,6 +2,21 @@ 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. + +### `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 ### :material-close-box: Migrate cache file from Clash API to independent options diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 0422833d..9998349f 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -2,6 +2,21 @@ icon: material/arrange-bring-forward --- +## 1.9.0 + +!!! warning "不稳定的" + + 该版本仍在开发中,迁移指南可能将在未来更改。 + +### 对 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 ### :material-close-box: 将缓存文件从 Clash API 迁移到独立选项 From ee14135298cbfb7d1c59d7e1f469c40555796e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 5 Jan 2024 14:19:53 +0800 Subject: [PATCH 03/30] 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. --- docs/migration.md | 7 +++++++ docs/migration.zh.md | 6 ++++++ go.mod | 2 +- go.sum | 6 ++---- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/migration.md b/docs/migration.md index b6ac0d8a..b282a90f 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -8,6 +8,13 @@ icon: material/arrange-bring-forward 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, diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 9998349f..bd63bf17 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -8,6 +8,12 @@ icon: material/arrange-bring-forward 该版本仍在开发中,迁移指南可能将在未来更改。 +### `domain_suffix` 行为更新 + +由于历史原因,sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。 + +sing-box 1.9.0 修改了 `domain_suffix` 的行为:如果规则值以 `.` 为前缀则行为不变,否则改为匹配 `(domain|.+\.domain)`。 + ### 对 Windows 上 `process_path` 格式的更新 sing-box 的 `process_path` 规则继承自Clash, diff --git a/go.mod b/go.mod index af2b0d2e..f7a5d6a6 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e github.com/sagernet/quic-go v0.40.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.3.8 + github.com/sagernet/sing v0.4.0-beta.20 github.com/sagernet/sing-dns v0.1.14 github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-quic v0.1.15 diff --git a/go.sum b/go.sum index 09b6a53b..bf0d0c70 100644 --- a/go.sum +++ b/go.sum @@ -106,14 +106,12 @@ github.com/sagernet/quic-go v0.40.1/go.mod h1:CcKTpzTAISxrM4PA5M20/wYuz9Tj6Tx4Dw github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.3.8 h1:gm4JKalPhydMYX2zFOTnnd4TXtM/16WFRqSjMepYQQk= -github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= +github.com/sagernet/sing v0.4.0-beta.20 h1:8rEepj4LMcR0Wd389fJIziv/jr3MBtX5qXBHsfxJ+dY= +github.com/sagernet/sing v0.4.0-beta.20/go.mod h1:PFQKbElc2Pke7faBLv8oEba5ehtKO21Ho+TkYemTI3Y= github.com/sagernet/sing-dns v0.1.14 h1:kxE/Ik3jMXmD3sXsdt9MgrNzLFWt64mghV+MQqzyf40= github.com/sagernet/sing-dns v0.1.14/go.mod h1:AA+vZMNovuPN5i/sPnfF6756Nq94nzb5nXodMWbta5w= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= -github.com/sagernet/sing-quic v0.1.14 h1:gzQAuvxDyh9oz3J595KchYpi0HcHOvQWeUG20FWc45A= -github.com/sagernet/sing-quic v0.1.14/go.mod h1:L+VtzvuPbf8VW8F4R7KiygqpXY4lO7t2wwcQuHjh8Ew= 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= From 11c7b4a86664fb5867bb76ae901ed264a7096436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 22 Feb 2024 21:20:23 +0800 Subject: [PATCH 04/30] Handle Windows power events --- route/router.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/route/router.go b/route/router.go index 3f5d2f40..fa602989 100644 --- a/route/router.go +++ b/route/router.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "os/user" + "runtime" "strings" "time" @@ -42,6 +43,7 @@ import ( serviceNTP "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/uot" + "github.com/sagernet/sing/common/winpowrprof" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) @@ -85,6 +87,7 @@ type Router struct { networkMonitor tun.NetworkUpdateMonitor interfaceMonitor tun.DefaultInterfaceMonitor packageManager tun.PackageManager + powerListener winpowrprof.EventListener processSearcher process.Searcher timeService *ntp.Service pauseManager pause.Manager @@ -321,6 +324,14 @@ func NewRouter( router.interfaceMonitor = interfaceMonitor } + if runtime.GOOS == "windows" { + powerListener, err := winpowrprof.NewEventListener(router.notifyWindowsPowerEvent) + if err != nil { + return nil, E.Cause(err, "initialize power listener") + } + router.powerListener = powerListener + } + if ntpOptions.Enabled { timeService, err := ntp.NewService(ctx, router, logFactory.NewLogger("ntp"), ntpOptions) if err != nil { @@ -560,6 +571,16 @@ func (r *Router) Start() error { } } } + + if r.powerListener != nil { + monitor.Start("start power listener") + err := r.powerListener.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start power listener") + } + } + if (needWIFIStateFromRuleSet || r.needWIFIState) && r.platformInterface != nil { monitor.Start("initialize WIFI state") r.needWIFIState = true @@ -657,6 +678,13 @@ func (r *Router) Close() error { }) 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 { @@ -1189,3 +1217,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() + } +} From b5dcd6bf593b61f25292a25b29471364b8be61cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 26 Mar 2024 20:58:47 +0800 Subject: [PATCH 05/30] Migrate ntp service to library --- ntp/service.go | 112 ------------------------------------------------ option/ntp.go | 3 +- route/router.go | 17 +++++--- 3 files changed, 13 insertions(+), 119 deletions(-) delete mode 100644 ntp/service.go diff --git a/ntp/service.go b/ntp/service.go deleted file mode 100644 index 70a41c0e..00000000 --- a/ntp/service.go +++ /dev/null @@ -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 -} diff --git a/option/ntp.go b/option/ntp.go index 000a658c..0bd2489a 100644 --- a/option/ntp.go +++ b/option/ntp.go @@ -2,9 +2,8 @@ package option type NTPOptions struct { Enabled bool `json:"enabled,omitempty"` - Server string `json:"server,omitempty"` - ServerPort uint16 `json:"server_port,omitempty"` Interval Duration `json:"interval,omitempty"` WriteToSystem bool `json:"write_to_system,omitempty"` + ServerOptions DialerOptions } diff --git a/route/router.go b/route/router.go index fa602989..15d8ef10 100644 --- a/route/router.go +++ b/route/router.go @@ -23,7 +23,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/ntp" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/transport/fakeip" @@ -40,7 +39,7 @@ import ( F "github.com/sagernet/sing/common/format" M "github.com/sagernet/sing/common/metadata" 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/uot" "github.com/sagernet/sing/common/winpowrprof" @@ -333,11 +332,19 @@ func NewRouter( } if ntpOptions.Enabled { - timeService, err := ntp.NewService(ctx, router, logFactory.NewLogger("ntp"), ntpOptions) + ntpDialer, err := dialer.New(router, ntpOptions.DialerOptions) 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 } return router, nil From 71d92518c12996e25ea951adafe4ea4958449f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 8 Apr 2024 18:00:48 +0800 Subject: [PATCH 06/30] Set the default TCP keep alive period --- common/dialer/default.go | 3 +++ constant/timeout.go | 2 ++ inbound/default_tcp.go | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/common/dialer/default.go b/common/dialer/default.go index 0234b1b9..91af85c5 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -63,6 +63,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi } else { 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 if options.UDPFragment != nil { udpFragment = *options.UDPFragment diff --git a/constant/timeout.go b/constant/timeout.go index 0d7a0b7d..b270a050 100644 --- a/constant/timeout.go +++ b/constant/timeout.go @@ -3,6 +3,8 @@ package constant import "time" const ( + TCPKeepAliveInitial = 10 * time.Minute + TCPKeepAliveInterval = 75 * time.Second TCPTimeout = 5 * time.Second ReadPayloadTimeout = 300 * time.Millisecond DNSTimeout = 10 * time.Second diff --git a/inbound/default_tcp.go b/inbound/default_tcp.go index 69880183..d680c695 100644 --- a/inbound/default_tcp.go +++ b/inbound/default_tcp.go @@ -5,7 +5,9 @@ import ( "net" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -16,6 +18,9 @@ func (a *myInboundAdapter) ListenTCP() (net.Listener, error) { bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) var tcpListener net.Listener var listenConfig net.ListenConfig + // TODO: Add an option to customize the keep alive period + listenConfig.KeepAlive = C.TCPKeepAliveInitial + listenConfig.Control = control.Append(listenConfig.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval)) if a.listenOptions.TCPMultiPath { if !go121Available { return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") From 003176f069f70117584a5f378cbfffb8637e0133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 10 Apr 2024 20:27:43 +0800 Subject: [PATCH 07/30] Remove unused fakeip packet conn --- transport/fakeip/packet.go | 55 --------------------------------- transport/fakeip/packet_wait.go | 37 ---------------------- 2 files changed, 92 deletions(-) delete mode 100644 transport/fakeip/packet.go delete mode 100644 transport/fakeip/packet_wait.go diff --git a/transport/fakeip/packet.go b/transport/fakeip/packet.go deleted file mode 100644 index 620acb92..00000000 --- a/transport/fakeip/packet.go +++ /dev/null @@ -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 -} diff --git a/transport/fakeip/packet_wait.go b/transport/fakeip/packet_wait.go deleted file mode 100644 index 9fa4a5bd..00000000 --- a/transport/fakeip/packet_wait.go +++ /dev/null @@ -1,37 +0,0 @@ -package fakeip - -import ( - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func (c *NATPacketConn) CreatePacketReadWaiter() (N.PacketReadWaiter, bool) { - waiter, created := bufio.CreatePacketReadWaiter(c.PacketConn) - if !created { - return nil, false - } - return &waitNATPacketConn{c, waiter}, true -} - -type waitNATPacketConn struct { - *NATPacketConn - readWaiter N.PacketReadWaiter -} - -func (c *waitNATPacketConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) { - return c.readWaiter.InitializeReadWaiter(options) -} - -func (c *waitNATPacketConn) WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error) { - buffer, destination, err = c.readWaiter.WaitReadPacket() - if err == nil && socksaddrWithoutPort(destination) == c.origin { - destination = M.Socksaddr{ - Addr: c.destination.Addr, - Fqdn: c.destination.Fqdn, - Port: destination.Port, - } - } - return -} From cd0fcd5ddcf4cfd5e5677567f0b6ca23b862d59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 12 Apr 2024 09:24:49 +0800 Subject: [PATCH 08/30] Improve loopback detector --- experimental/libbox/config.go | 3 +- experimental/libbox/platform/interface.go | 10 +---- experimental/libbox/service.go | 6 +-- outbound/direct.go | 2 +- outbound/direct_loopback_detect.go | 21 ++++++--- route/interface_finder.go | 54 ----------------------- route/router.go | 17 +++---- 7 files changed, 26 insertions(+), 87 deletions(-) delete mode 100644 route/interface_finder.go diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index bad61dbc..3b1d9f1d 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" @@ -75,7 +74,7 @@ func (s *platformInterfaceStub) UsePlatformInterfaceGetter() bool { return true } -func (s *platformInterfaceStub) Interfaces() ([]platform.NetworkInterface, error) { +func (s *platformInterfaceStub) Interfaces() ([]control.Interface, error) { return nil, os.ErrInvalid } diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 54d35fa3..b250c8ae 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -2,7 +2,6 @@ package platform import ( "context" - "net/netip" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" @@ -20,16 +19,9 @@ type Interface interface { UsePlatformDefaultInterfaceMonitor() bool CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor UsePlatformInterfaceGetter() bool - Interfaces() ([]NetworkInterface, error) + Interfaces() ([]control.Interface, error) UnderNetworkExtension() bool ClearDNSCache() ReadWIFIState() adapter.WIFIState process.Searcher } - -type NetworkInterface struct { - Index int - MTU int - Name string - Addresses []netip.Prefix -} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 030aee8d..2d755d0d 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -192,14 +192,14 @@ func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool { return w.iif.UsePlatformInterfaceGetter() } -func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, error) { +func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) { interfaceIterator, err := w.iif.GetInterfaces() if err != nil { return nil, err } - var interfaces []platform.NetworkInterface + var interfaces []control.Interface for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) { - interfaces = append(interfaces, platform.NetworkInterface{ + interfaces = append(interfaces, control.Interface{ Index: int(netInterface.Index), MTU: int(netInterface.MTU), Name: netInterface.Name, diff --git a/outbound/direct.go b/outbound/direct.go index 49ac760e..11f650e4 100644 --- a/outbound/direct.go +++ b/outbound/direct.go @@ -51,7 +51,7 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), dialer: outboundDialer, - loopBack: newLoopBackDetector(), + loopBack: newLoopBackDetector(router), } if options.ProxyProtocol != 0 { return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") diff --git a/outbound/direct_loopback_detect.go b/outbound/direct_loopback_detect.go index 62cff876..1469b9d0 100644 --- a/outbound/direct_loopback_detect.go +++ b/outbound/direct_loopback_detect.go @@ -5,21 +5,22 @@ import ( "net/netip" "sync" + "github.com/sagernet/sing-box/adapter" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) type loopBackDetector struct { - // router adapter.Router + router adapter.Router connAccess sync.RWMutex packetConnAccess sync.RWMutex connMap map[netip.AddrPort]netip.AddrPort packetConnMap map[uint16]uint16 } -func newLoopBackDetector( /*router adapter.Router*/ ) *loopBackDetector { +func newLoopBackDetector(router adapter.Router) *loopBackDetector { return &loopBackDetector{ - // router: router, + router: router, connMap: make(map[netip.AddrPort]netip.AddrPort), packetConnMap: make(map[uint16]uint16), } @@ -31,12 +32,12 @@ func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn { return conn } if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn { - /*if !source.Addr().IsLoopback() { + if !source.Addr().IsLoopback() { _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) if err != nil { return conn } - }*/ + } if !N.IsPublicAddr(source.Addr()) { return conn } @@ -57,6 +58,12 @@ func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Soc if !source.IsValid() { return conn } + if !source.Addr().IsLoopback() { + _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) + if err != nil { + return conn + } + } l.packetConnAccess.Lock() l.packetConnMap[source.Port()] = destination.AddrPort().Port() l.packetConnAccess.Unlock() @@ -74,12 +81,12 @@ func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.Ad if !source.IsValid() { return false } - /*if !source.Addr().IsLoopback() { + if !source.Addr().IsLoopback() { _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) if err != nil { return false } - }*/ + } if N.IsPublicAddr(source.Addr()) { return false } diff --git a/route/interface_finder.go b/route/interface_finder.go deleted file mode 100644 index 850f091f..00000000 --- a/route/interface_finder.go +++ /dev/null @@ -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 -} diff --git a/route/router.go b/route/router.go index 15d8ef10..f2fe1a6d 100644 --- a/route/router.go +++ b/route/router.go @@ -79,7 +79,7 @@ type Router struct { transportDomainStrategy map[dns.Transport]dns.DomainStrategy dnsReverseMapping *DNSReverseMapping fakeIPStore adapter.FakeIPStore - interfaceFinder myInterfaceFinder + interfaceFinder *control.DefaultInterfaceFinder autoDetectInterface bool defaultInterface string defaultMark int @@ -124,6 +124,7 @@ func NewRouter( needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, defaultDetour: options.Final, defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy), + interfaceFinder: control.NewDefaultInterfaceFinder(), autoDetectInterface: options.AutoDetectInterface, defaultInterface: options.DefaultInterface, defaultMark: options.DefaultMark, @@ -305,7 +306,7 @@ func NewRouter( } router.networkMonitor = networkMonitor networkMonitor.RegisterCallback(func() { - _ = router.interfaceFinder.update() + _ = router.interfaceFinder.Update() }) interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ OverrideAndroidVPN: options.OverrideAndroidVPN, @@ -1063,24 +1064,18 @@ func (r *Router) match0(ctx context.Context, metadata *adapter.InboundContext, d } func (r *Router) InterfaceFinder() control.InterfaceFinder { - return &r.interfaceFinder + return r.interfaceFinder } func (r *Router) UpdateInterfaces() error { if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { - return r.interfaceFinder.update() + return r.interfaceFinder.Update() } else { interfaces, err := r.platformInterface.Interfaces() if err != nil { return err } - r.interfaceFinder.updateInterfaces(common.Map(interfaces, func(it platform.NetworkInterface) net.Interface { - return net.Interface{ - Name: it.Name, - Index: it.Index, - MTU: it.MTU, - } - })) + r.interfaceFinder.UpdateInterfaces(interfaces) return nil } } From 830ea46932132fda2e29fccbbb56f56ab3ad08bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 17 Jan 2024 05:48:33 +0800 Subject: [PATCH 09/30] Fix timezone for Android and iOS --- constant/quic.go | 5 +++++ constant/quic_stub.go | 5 +++++ experimental/libbox/setup.go | 1 + inbound/naive.go | 5 ++--- include/quic.go | 2 -- include/quic_stub.go | 2 -- include/tz_android.go | 21 +++++++++++++++++++++ include/tz_ios.go | 30 ++++++++++++++++++++++++++++++ 8 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 constant/quic.go create mode 100644 constant/quic_stub.go create mode 100644 include/tz_android.go create mode 100644 include/tz_ios.go diff --git a/constant/quic.go b/constant/quic.go new file mode 100644 index 00000000..50bddf88 --- /dev/null +++ b/constant/quic.go @@ -0,0 +1,5 @@ +//go:build with_quic + +package constant + +const WithQUIC = true diff --git a/constant/quic_stub.go b/constant/quic_stub.go new file mode 100644 index 00000000..95b47fef --- /dev/null +++ b/constant/quic_stub.go @@ -0,0 +1,5 @@ +//go:build !with_quic + +package constant + +const WithQUIC = false diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index a4514dfe..ea468f39 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/common/humanize" C "github.com/sagernet/sing-box/constant" + _ "github.com/sagernet/sing-box/include" ) var ( diff --git a/inbound/naive.go b/inbound/naive.go index 36bda492..07328c09 100644 --- a/inbound/naive.go +++ b/inbound/naive.go @@ -15,7 +15,6 @@ import ( "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" @@ -109,8 +108,8 @@ func (n *Naive) Start() error { if common.Contains(n.network, N.NetworkUDP) { err := n.configureHTTP3Listener() - if !include.WithQUIC && len(n.network) > 1 { - log.Warn(E.Cause(err, "naive http3 disabled")) + if !C.WithQUIC && len(n.network) > 1 { + n.logger.Warn(E.Cause(err, "naive http3 disabled")) } else if err != nil { return err } diff --git a/include/quic.go b/include/quic.go index 1e507f7b..1bcc0fbc 100644 --- a/include/quic.go +++ b/include/quic.go @@ -6,5 +6,3 @@ import ( _ "github.com/sagernet/sing-box/transport/v2rayquic" _ "github.com/sagernet/sing-dns/quic" ) - -const WithQUIC = true diff --git a/include/quic_stub.go b/include/quic_stub.go index 682eb536..17b502a7 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -16,8 +16,6 @@ import ( N "github.com/sagernet/sing/common/network" ) -const WithQUIC = false - func init() { dns.RegisterTransport([]string{"quic", "h3"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { return nil, C.ErrQUICNotIncluded diff --git a/include/tz_android.go b/include/tz_android.go new file mode 100644 index 00000000..7be1c2da --- /dev/null +++ b/include/tz_android.go @@ -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 +import "C" +import "time" + +func init() { + var currentT C.time_t + var currentTM C.struct_tm + C.time(¤tT) + C.localtime_r(¤tT, ¤tTM) + tzOffset := int(currentTM.tm_gmtoff) + tz := C.GoString(currentTM.tm_zone) + time.Local = time.FixedZone(tz, tzOffset) +} diff --git a/include/tz_ios.go b/include/tz_ios.go new file mode 100644 index 00000000..fc30479c --- /dev/null +++ b/include/tz_ios.go @@ -0,0 +1,30 @@ +package include + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation +#import +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 +} From 0517ceef767b0e47cfc9bd6e68a8f47a928cb6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 3 Feb 2024 17:45:27 +0800 Subject: [PATCH 10/30] Add address filter support for DNS rules --- adapter/inbound.go | 13 +- adapter/router.go | 3 + docs/configuration/dns/index.md | 4 +- docs/configuration/dns/rule.md | 42 +++- docs/configuration/dns/rule.zh.md | 47 ++++- docs/configuration/experimental/cache-file.md | 4 - .../experimental/cache-file.zh.md | 4 - docs/configuration/experimental/clash-api.md | 4 - .../experimental/clash-api.zh.md | 4 - docs/configuration/experimental/index.md | 4 - docs/configuration/experimental/index.zh.md | 4 - docs/configuration/inbound/tun.md | 4 - docs/configuration/inbound/tun.zh.md | 4 - docs/configuration/outbound/wireguard.md | 4 - docs/configuration/outbound/wireguard.zh.md | 4 - docs/configuration/route/index.md | 4 - docs/configuration/route/index.zh.md | 4 - docs/configuration/route/rule.md | 4 - docs/configuration/route/rule.zh.md | 4 - docs/configuration/rule-set/headless-rule.md | 4 - docs/configuration/rule-set/index.md | 4 - docs/configuration/rule-set/source-format.md | 4 - docs/configuration/shared/tls.md | 5 - docs/configuration/shared/tls.zh.md | 4 - docs/manual/proxy/client.md | 142 +++++-------- go.mod | 4 +- go.sum | 8 +- option/rule_dns.go | 3 + route/router_dns.go | 187 +++++++++++++----- route/router_rule.go | 6 +- route/rule_abstract.go | 24 ++- route/rule_default.go | 6 +- route/rule_dns.go | 91 +++++++++ route/rule_headless.go | 4 +- route/rule_item_rule_set.go | 9 +- route/rule_set_local.go | 7 + route/rule_set_remote.go | 7 + 37 files changed, 436 insertions(+), 248 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index bcf3ea5f..063671c1 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -51,11 +51,13 @@ type InboundContext struct { // rule cache - IPCIDRMatchSource bool - SourceAddressMatch bool - SourcePortMatch bool - DestinationAddressMatch bool - DestinationPortMatch bool + IPCIDRMatchSource bool + SourceAddressMatch bool + SourcePortMatch bool + DestinationAddressMatch bool + DestinationPortMatch bool + DidMatch bool + IgnoreDestinationIPCIDRMatch bool } func (c *InboundContext) ResetRuleCache() { @@ -64,6 +66,7 @@ func (c *InboundContext) ResetRuleCache() { c.SourcePortMatch = false c.DestinationAddressMatch = false c.DestinationPortMatch = false + c.DidMatch = false } type inboundContextKey struct{} diff --git a/adapter/router.go b/adapter/router.go index 9d8bcb3e..b5eceb1f 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -86,6 +86,8 @@ type DNSRule interface { Rule DisableCache() bool RewriteTTL() *uint32 + WithAddressLimit() bool + MatchAddressLimit(metadata *InboundContext) bool } type RuleSet interface { @@ -99,6 +101,7 @@ type RuleSet interface { type RuleSetMetadata struct { ContainsProcessRule bool ContainsWIFIRule bool + ContainsIPCIDRRule bool } type RuleSetStartContext interface { diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index e2832c42..cfb6bc6b 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -21,8 +21,8 @@ ### Fields -| Key | Format | -|----------|--------------------------------| +| Key | Format | +|----------|---------------------------------| | `server` | List of [DNS Server](./server/) | | `rules` | List of [DNS Rule](./rule/) | | `fakeip` | [FakeIP](./fakeip/) | diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 68cc32cf..26b86d95 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -1,7 +1,13 @@ --- -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) + !!! quote "Changes in sing-box 1.8.0" :material-plus: [rule_set](#rule_set) @@ -53,11 +59,19 @@ icon: material/alert-decagram "source_geoip": [ "private" ], + "geoip": [ + "cn" + ], "source_ip_cidr": [ "10.0.0.0/24", "192.168.0.1" ], "source_ip_is_private": false, + "ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "ip_is_private": false, "source_port": [ 12345 ], @@ -312,6 +326,32 @@ Disable cache and save cache in this query. Rewrite TTL in DNS responses. +### 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. + +!!! note "" + + `ip_cidr` items in included rule sets also takes effect as an address filtering field. + +#### 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 #### type diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 5b1d7501..ebc81c0f 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -1,7 +1,13 @@ --- -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) + !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) @@ -53,10 +59,19 @@ icon: material/alert-decagram "source_geoip": [ "private" ], + "geoip": [ + "cn" + ], "source_ip_cidr": [ - "10.0.0.0/24" + "10.0.0.0/24", + "192.168.0.1" ], "source_ip_is_private": false, + "ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "ip_is_private": false, "source_port": [ 12345 ], @@ -307,6 +322,32 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 重写 DNS 回应中的 TTL。 +### 地址筛选字段 + +仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。 + +!!! note "" + + 引用的规则集中的 `ip_cidr` 项也作为地址筛选字段生效。 + +#### 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 @@ -319,4 +360,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 #### rules -包括的规则。 \ No newline at end of file +包括的规则。 diff --git a/docs/configuration/experimental/cache-file.md b/docs/configuration/experimental/cache-file.md index 66e30ef9..ca3f62e5 100644 --- a/docs/configuration/experimental/cache-file.md +++ b/docs/configuration/experimental/cache-file.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.8.0" ### Structure diff --git a/docs/configuration/experimental/cache-file.zh.md b/docs/configuration/experimental/cache-file.zh.md index f4417ede..da0ce39b 100644 --- a/docs/configuration/experimental/cache-file.zh.md +++ b/docs/configuration/experimental/cache-file.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.8.0 起" ### 结构 diff --git a/docs/configuration/experimental/clash-api.md b/docs/configuration/experimental/clash-api.md index 0525d14d..e1ca9815 100644 --- a/docs/configuration/experimental/clash-api.md +++ b/docs/configuration/experimental/clash-api.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - !!! quote "Changes in sing-box 1.8.0" :material-delete-alert: [store_mode](#store_mode) diff --git a/docs/configuration/experimental/clash-api.zh.md b/docs/configuration/experimental/clash-api.zh.md index 5a490e58..092769ac 100644 --- a/docs/configuration/experimental/clash-api.zh.md +++ b/docs/configuration/experimental/clash-api.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - !!! quote "sing-box 1.8.0 中的更改" :material-delete-alert: [store_mode](#store_mode) diff --git a/docs/configuration/experimental/index.md b/docs/configuration/experimental/index.md index 4ddcc41a..a1a515cf 100644 --- a/docs/configuration/experimental/index.md +++ b/docs/configuration/experimental/index.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - # Experimental !!! quote "Changes in sing-box 1.8.0" diff --git a/docs/configuration/experimental/index.zh.md b/docs/configuration/experimental/index.zh.md index 4be70aa7..01246c44 100644 --- a/docs/configuration/experimental/index.zh.md +++ b/docs/configuration/experimental/index.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - # 实验性 !!! quote "sing-box 1.8.0 中的更改" diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 6e6c2ae0..2eed4553 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - !!! quote "Changes in sing-box 1.8.0" :material-plus: [gso](#gso) diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 71c66704..05c7c314 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - !!! quote "sing-box 1.8.0 中的更改" :material-plus: [gso](#gso) diff --git a/docs/configuration/outbound/wireguard.md b/docs/configuration/outbound/wireguard.md index 4cd91d22..c3f51f1f 100644 --- a/docs/configuration/outbound/wireguard.md +++ b/docs/configuration/outbound/wireguard.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.8.0" :material-plus: [gso](#gso) diff --git a/docs/configuration/outbound/wireguard.zh.md b/docs/configuration/outbound/wireguard.zh.md index e853d72e..5de28132 100644 --- a/docs/configuration/outbound/wireguard.zh.md +++ b/docs/configuration/outbound/wireguard.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.8.0 中的更改" :material-plus: [gso](#gso) diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 5deb44f5..7b2a7e7e 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - # Route !!! quote "Changes in sing-box 1.8.0" diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md index 290268f4..68d4f66d 100644 --- a/docs/configuration/route/index.zh.md +++ b/docs/configuration/route/index.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - # 路由 !!! quote "sing-box 1.8.0 中的更改" diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 9bedef86..b21bf658 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - !!! quote "Changes in sing-box 1.8.0" :material-plus: [rule_set](#rule_set) diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 0e6f9896..3f8b4715 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index 6ab62eb2..99984899 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - ### Structure !!! question "Since sing-box 1.8.0" diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index 5aff55b3..ba2f741e 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - # Rule Set !!! question "Since sing-box 1.8.0" diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index 8e1934ae..ee5e48e0 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - # Source Format !!! question "Since sing-box 1.8.0" diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index a5c7bec4..b1441a8a 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -1,8 +1,3 @@ ---- -icon: material/alert-decagram ---- - - !!! quote "Changes in sing-box 1.8.0" :material-alert-decagram: [utls](#utls) diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 5a75945d..360c4536 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/alert-decagram ---- - !!! quote "sing-box 1.8.0 中的更改" :material-alert-decagram: [utls](#utls) diff --git a/docs/manual/proxy/client.md b/docs/manual/proxy/client.md index 3ba7eacc..41755cca 100644 --- a/docs/manual/proxy/client.md +++ b/docs/manual/proxy/client.md @@ -290,52 +290,6 @@ flowchart TB === ":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 { "dns": { @@ -382,74 +336,78 @@ flowchart TB } ``` -=== ":material-router-network: Route rules" +=== ":material-dns: DNS rules (1.9.0+)" + + !!! warning "DNS leaks" + + 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. ```json { - "outbounds": [ - { - "type": "direct", - "tag": "direct" - }, - { - "type": "block", - "tag": "block" - } - ], - "route": { - "rules": [ + "dns": { + "servers": [ { - "type": "logical", - "mode": "or", - "rules": [ - { - "protocol": "dns" - }, - { - "port": 53 - } - ], - "outbound": "dns" + "tag": "google", + "address": "tls://8.8.8.8" }, { - "geoip": "private", - "outbound": "direct" + "tag": "local", + "address": "https://223.5.5.5/dns-query", + "detour": "direct" + } + ], + "rules": [ + { + "outbound": "any", + "server": "local" }, { "clash_mode": "Direct", - "outbound": "direct" + "server": "local" }, { "clash_mode": "Global", - "outbound": "default" + "server": "google" }, { - "type": "logical", - "mode": "or", - "rules": [ - { - "port": 853 - }, - { - "network": "udp", - "port": 443 - }, - { - "protocol": "stun" - } - ], - "outbound": "block" + "rule_set": "geosite-geolocation-cn", + "server": "local" }, { - "geosite": "geolocation-cn", - "outbound": "direct" + "clash_mode": "Default", + "server": "google" + }, + { + "rule_set": "geoip-cn", + "server": "local" } ] + }, + "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": "geoip-cn", + "format": "binary", + "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs" + } + ] + }, + "experimental": { + "clash_api": { + "default_mode": "Leak" + } } } ``` -=== ":material-router-network: Route rules (1.8.0+)" +=== ":material-router-network: Route rules" ```json { diff --git a/go.mod b/go.mod index f7a5d6a6..1f501d02 100644 --- a/go.mod +++ b/go.mod @@ -24,10 +24,10 @@ require ( github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/gomobile v0.1.3 github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e - github.com/sagernet/quic-go v0.40.1 + github.com/sagernet/quic-go v0.43.0-beta.3 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.4.0-beta.20 - github.com/sagernet/sing-dns v0.1.14 + github.com/sagernet/sing-dns v0.2.0-beta.18 github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-quic v0.1.15 github.com/sagernet/sing-shadowsocks v0.2.6 diff --git a/go.sum b/go.sum index bf0d0c70..a6bb20cf 100644 --- a/go.sum +++ b/go.sum @@ -101,15 +101,15 @@ github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e h1:DOkjByVeAR56dks github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.40.1 h1:qLeTIJR0d0JWRmDWo346nLsVN6EWihd1kalJYPEd0TM= -github.com/sagernet/quic-go v0.40.1/go.mod h1:CcKTpzTAISxrM4PA5M20/wYuz9Tj6Tx4DwGbNl9UQrU= +github.com/sagernet/quic-go v0.43.0-beta.3 h1:qclJbbpgZe76EH62Bdu3LfDSC2zmuxj7zXCpdQBbe7c= +github.com/sagernet/quic-go v0.43.0-beta.3/go.mod h1:3EtxR1Yaa1FZu6jFPiBHpOAdhOxL4A3EPxmiVgjJvVM= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.4.0-beta.20 h1:8rEepj4LMcR0Wd389fJIziv/jr3MBtX5qXBHsfxJ+dY= github.com/sagernet/sing v0.4.0-beta.20/go.mod h1:PFQKbElc2Pke7faBLv8oEba5ehtKO21Ho+TkYemTI3Y= -github.com/sagernet/sing-dns v0.1.14 h1:kxE/Ik3jMXmD3sXsdt9MgrNzLFWt64mghV+MQqzyf40= -github.com/sagernet/sing-dns v0.1.14/go.mod h1:AA+vZMNovuPN5i/sPnfF6756Nq94nzb5nXodMWbta5w= +github.com/sagernet/sing-dns v0.2.0-beta.18 h1:6vzXZThRdA7YUzBOpSbUT48XRumtl/KIpIHFSOP0za8= +github.com/sagernet/sing-dns v0.2.0-beta.18/go.mod h1:k/dmFcQpg6+m08gC1yQBy+13+QkuLqpKr4bIreq4U24= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-quic v0.1.15 h1:LGWPxQEeg89+68RHP7HtAV0RZeEWQikUqOfE9nYmr2A= diff --git a/option/rule_dns.go b/option/rule_dns.go index 443f9314..d148e264 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -77,6 +77,9 @@ type DefaultDNSRule struct { DomainRegex Listable[string] `json:"domain_regex,omitempty"` Geosite Listable[string] `json:"geosite,omitempty"` SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` + GeoIP Listable[string] `json:"geoip,omitempty"` + IPCIDR Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` SourcePort Listable[uint16] `json:"source_port,omitempty"` diff --git a/route/router_dns.go b/route/router_dns.go index 8ae91710..ee767e9e 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -2,13 +2,13 @@ package route import ( "context" + "errors" "net/netip" "strings" "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/cache" E "github.com/sagernet/sing/common/exceptions" @@ -37,41 +37,51 @@ func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) { 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) if metadata == nil { panic("no context") } - for i, rule := range r.dnsRules { - metadata.ResetRuleCache() - if rule.Match(metadata) { - detour := rule.Outbound() - transport, loaded := r.transportMap[detour] - if !loaded { - r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour) - continue - } - if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP { - continue - } - r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour) - if rule.DisableCache() { - ctx = dns.ContextWithDisableCache(ctx, true) - } - if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { - ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) - } - if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { - return ctx, transport, domainStrategy - } else { - return ctx, transport, r.defaultDomainStrategy + if index < len(r.dnsRules) { + dnsRules := r.dnsRules + if index != -1 { + dnsRules = dnsRules[index+1:] + } + for ruleIndex, rule := range dnsRules { + metadata.ResetRuleCache() + if rule.Match(metadata) { + detour := rule.Outbound() + transport, loaded := r.transportMap[detour] + if !loaded { + r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour) + continue + } + if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP { + continue + } + displayRuleIndex := ruleIndex + if index != -1 { + displayRuleIndex += index + 1 + } + r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", detour) + if rule.DisableCache() { + ctx = dns.ContextWithDisableCache(ctx, true) + } + if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { + ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) + } + 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 { - return ctx, r.defaultTransport, domainStrategy + return ctx, r.defaultTransport, domainStrategy, nil, -1 } else { - return ctx, r.defaultTransport, r.defaultDomainStrategy + return ctx, r.defaultTransport, r.defaultDomainStrategy, nil, -1 } } @@ -86,7 +96,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er ) response, cached = r.dnsClient.ExchangeCache(ctx, message) if !cached { - ctx, metadata := adapter.AppendContext(ctx) + var metadata *adapter.InboundContext + ctx, metadata = adapter.AppendContext(ctx) if len(message.Question) > 0 { metadata.QueryType = message.Question[0].Qtype switch metadata.QueryType { @@ -97,17 +108,47 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er } metadata.Domain = fqdnToDomain(message.Question[0].Name) } - ctx, transport, strategy := r.matchDNS(ctx, true) - ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout) - defer cancel() - response, err = r.dnsClient.Exchange(ctx, transport, message, strategy) - if err != nil && len(message.Question) > 0 { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String()))) + var ( + transport dns.Transport + strategy dns.DomainStrategy + rule adapter.DNSRule + ruleIndex int + ) + 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() + if err != nil { + if errors.Is(err, dns.ErrResponseRejected) { + r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String()))) + } else if len(message.Question) > 0 { + r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String()))) + } else { + r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ")) + } + } + if !addressLimit || err == nil { + break + } } } - if len(message.Question) > 0 && response != nil { - LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer) - } if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { for _, answer := range response.Answer { switch record := answer.(type) { @@ -125,22 +166,57 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS r.dnsLogger.DebugContext(ctx, "lookup domain ", domain) ctx, metadata := adapter.AppendContext(ctx) metadata.Domain = domain - ctx, transport, transportStrategy := r.matchDNS(ctx, false) - if strategy == dns.DomainStrategyAsIS { - strategy = transportStrategy + var ( + transport dns.Transport + transportStrategy dns.DomainStrategy + rule adapter.DNSRule + ruleIndex int + resultAddrs []netip.Addr + err error + ) + 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 + resultAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool { + metadata.DestinationAddresses = responseAddrs + return rule.MatchAddressLimit(metadata) + }) + } else { + addressLimit = false + resultAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy) + } + cancel() + if err != nil { + 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(resultAddrs) == 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) - defer cancel() - 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 + if len(resultAddrs) > 0 { + r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(resultAddrs), " ")) } - return addrs, err + return resultAddrs, err } func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) { @@ -154,10 +230,13 @@ func (r *Router) ClearDNSCache() { } } -func LogDNSAnswers(logger log.ContextLogger, ctx context.Context, domain string, answers []mDNS.RR) { - for _, answer := range answers { - logger.InfoContext(ctx, "exchanged ", domain, " ", mDNS.Type(answer.Header().Rrtype).String(), " ", formatQuestion(answer.String())) +func isAddressQuery(message *mDNS.Msg) bool { + for _, question := range message.Question { + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { + return true + } } + return false } func fqdnToDomain(fqdn string) string { diff --git a/route/router_rule.go b/route/router_rule.go index 9850b5bc..4a99a31c 100644 --- a/route/router_rule.go +++ b/route/router_rule.go @@ -59,7 +59,7 @@ func isGeoIPRule(rule option.DefaultRule) 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 { @@ -97,3 +97,7 @@ func isWIFIDNSRule(rule option.DefaultDNSRule) bool { func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 } + +func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.IPCIDR) > 0 || rule.IPSet != nil +} diff --git a/route/rule_abstract.go b/route/rule_abstract.go index 6decb9f3..c13bdd8d 100644 --- a/route/rule_abstract.go +++ b/route/rule_abstract.go @@ -15,6 +15,7 @@ type abstractDefaultRule struct { sourceAddressItems []RuleItem sourcePortItems []RuleItem destinationAddressItems []RuleItem + destinationIPCIDRItems []RuleItem destinationPortItems []RuleItem allItems []RuleItem ruleSetItem RuleItem @@ -64,6 +65,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch { + metadata.DidMatch = true for _, item := range r.sourceAddressItems { if item.Match(metadata) { metadata.SourceAddressMatch = true @@ -73,6 +75,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch { + metadata.DidMatch = true for _, item := range r.sourcePortItems { if item.Match(metadata) { metadata.SourcePortMatch = true @@ -82,6 +85,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch { + metadata.DidMatch = true for _, item := range r.destinationAddressItems { if item.Match(metadata) { 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 { + metadata.DidMatch = true for _, item := range r.destinationPortItems { if item.Match(metadata) { metadata.DestinationPortMatch = true @@ -100,6 +115,9 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } for _, item := range r.items { + if _, isRuleSet := item.(*RuleSetItem); !isRuleSet { + metadata.DidMatch = true + } if !item.Match(metadata) { return r.invert } @@ -113,7 +131,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { 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 } @@ -121,6 +139,10 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { return r.invert } + if !metadata.DidMatch { + return true + } + return !r.invert } diff --git a/route/rule_default.go b/route/rule_default.go index d2227bb3..d1d13f7d 100644 --- a/route/rule_default.go +++ b/route/rule_default.go @@ -109,7 +109,7 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt } if len(options.GeoIP) > 0 { 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) } if len(options.SourceIPCIDR) > 0 { @@ -130,12 +130,12 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt if err != nil { return nil, E.Cause(err, "ipcidr") } - rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } if options.IPIsPrivate { item := NewIPIsPrivateItem(false) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } if len(options.SourcePort) > 0 { diff --git a/route/rule_dns.go b/route/rule_dns.go index c43f6290..3eab61f8 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -5,6 +5,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" ) @@ -111,6 +112,11 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.sourceAddressItems = append(rule.sourceAddressItems, 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 { item, err := NewIPCIDRItem(true, options.SourceIPCIDR) if err != nil { @@ -119,11 +125,24 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.sourceAddressItems = append(rule.sourceAddressItems, 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 { item := NewIPIsPrivateItem(true) rule.sourceAddressItems = append(rule.sourceAddressItems, 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 { item := NewPortItem(true, options.SourcePort) rule.sourcePortItems = append(rule.sourcePortItems, item) @@ -211,6 +230,34 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } +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.ContainsIPCIDRRule() { + 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) type LogicalDNSRule struct { @@ -254,3 +301,47 @@ func (r *LogicalDNSRule) DisableCache() bool { func (r *LogicalDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } + +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 + } +} diff --git a/route/rule_headless.go b/route/rule_headless.go index 82c07d31..92b6720c 100644 --- a/route/rule_headless.go +++ b/route/rule_headless.go @@ -80,11 +80,11 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles if err != nil { return nil, E.Cause(err, "ipcidr") } - rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } else if options.IPSet != nil { item := NewRawIPCIDRItem(false, options.IPSet) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } if len(options.SourcePort) > 0 { diff --git a/route/rule_item_rule_set.go b/route/rule_item_rule_set.go index 959b2f61..8354e421 100644 --- a/route/rule_item_rule_set.go +++ b/route/rule_item_rule_set.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" ) @@ -13,7 +14,7 @@ var _ RuleItem = (*RuleSetItem)(nil) type RuleSetItem struct { router adapter.Router tagList []string - setList []adapter.HeadlessRule + setList []adapter.RuleSet ipcidrMatchSource bool } @@ -46,6 +47,12 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool { return false } +func (r *RuleSetItem) ContainsIPCIDRRule() bool { + return common.Any(r.setList, func(ruleSet adapter.RuleSet) bool { + return ruleSet.Metadata().ContainsIPCIDRRule + }) +} + func (r *RuleSetItem) String() string { if len(r.tagList) == 1 { return F.ToString("rule_set=", r.tagList[0]) diff --git a/route/rule_set_local.go b/route/rule_set_local.go index 635f22ed..1fd09246 100644 --- a/route/rule_set_local.go +++ b/route/rule_set_local.go @@ -3,12 +3,14 @@ package route import ( "context" "os" + "strings" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/srs" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" ) @@ -55,6 +57,7 @@ func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleS var metadata adapter.RuleSetMetadata metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) + metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) return &LocalRuleSet{rules, metadata}, nil } @@ -67,6 +70,10 @@ func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool { return false } +func (s *LocalRuleSet) String() string { + return strings.Join(F.MapToString(s.rules), " ") +} + func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error { return nil } diff --git a/route/rule_set_remote.go b/route/rule_set_remote.go index 595e328c..a14c6fe5 100644 --- a/route/rule_set_remote.go +++ b/route/rule_set_remote.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "runtime" + "strings" "time" "github.com/sagernet/sing-box/adapter" @@ -14,6 +15,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" @@ -68,6 +70,10 @@ func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool { 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 { var dialer N.Dialer if s.options.RemoteOptions.DownloadDetour != "" { @@ -150,6 +156,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { } s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) + s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) s.rules = rules return nil } From f24a2aed7dac1dc94e18f6e8d8c1161a221aeb7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 9 Feb 2024 18:37:25 +0800 Subject: [PATCH 11/30] Add support for `client-subnet` DNS options --- adapter/router.go | 1 + docs/configuration/dns/index.md | 17 ++- docs/configuration/dns/index.zh.md | 17 +++ docs/configuration/dns/rule.md | 19 ++- docs/configuration/dns/rule.zh.md | 17 ++- docs/configuration/dns/server.md | 34 +++-- docs/configuration/dns/server.zh.md | 32 +++-- docs/manual/proxy/client.md | 187 ++++++++++++++++++---------- experimental/libbox/dns.go | 8 +- include/dhcp_stub.go | 6 +- include/quic_stub.go | 3 +- option/dns.go | 2 + option/rule_dns.go | 15 ++- route/router.go | 22 +++- route/router_dns.go | 3 + route/rule_dns.go | 14 +++ transport/dhcp/server.go | 46 ++++--- transport/fakeip/server.go | 13 +- 18 files changed, 316 insertions(+), 140 deletions(-) diff --git a/adapter/router.go b/adapter/router.go index b5eceb1f..0d771dee 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -86,6 +86,7 @@ type DNSRule interface { Rule DisableCache() bool RewriteTTL() *uint32 + ClientSubnet() *netip.Addr WithAddressLimit() bool MatchAddressLimit(metadata *InboundContext) bool } diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index cfb6bc6b..71219dbb 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.9.0" + + :material-plus: [client_subnet](#client_subnet) + # DNS ### Structure @@ -13,6 +21,7 @@ "disable_expire": false, "independent_cache": false, "reverse_mapping": false, + "client_subnet": "", "fakeip": {} } } @@ -60,6 +69,10 @@ 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 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 address to every query by default. + +Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`. diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index afc6e931..164c37cd 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.9.0 中的更改" + + :material-plus: [client_subnet](#client_subnet) + # DNS ### 结构 @@ -13,6 +21,7 @@ "disable_expire": false, "independent_cache": false, "reverse_mapping": false, + "client_subnet": "", "fakeip": {} } } @@ -58,6 +67,14 @@ 由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。 +#### client_subnet + +!!! question "自 sing-box 1.9.0 起" + +默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。 + #### fakeip [FakeIP](./fakeip/) 设置。 diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 26b86d95..5b42f20c 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) - :material-plus: [ip_is_private](#ip_is_private) + :material-plus: [ip_is_private](#ip_is_private) + :material-plus: [client_subnet](#client_subnet) !!! quote "Changes in sing-box 1.8.0" @@ -121,7 +122,8 @@ icon: material/new-box ], "server": "local", "disable_cache": false, - "rewrite_ttl": 100 + "rewrite_ttl": 100, + "client_subnet": "127.0.0.1" }, { "type": "logical", @@ -129,7 +131,8 @@ icon: material/new-box "rules": [], "server": "local", "disable_cache": false, - "rewrite_ttl": 100 + "rewrite_ttl": 100, + "client_subnet": "127.0.0.1" } ] } @@ -280,8 +283,6 @@ Match Clash mode. #### wifi_ssid - - !!! quote "" Only supported in graphical clients on Android and iOS. @@ -326,6 +327,14 @@ Disable cache and save cache in this query. 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 address to every query by default. + +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. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index ebc81c0f..eaeb8e68 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) - :material-plus: [ip_is_private](#ip_is_private) + :material-plus: [ip_is_private](#ip_is_private) + :material-plus: [client_subnet](#client_subnet) !!! quote "sing-box 1.8.0 中的更改" @@ -120,14 +121,16 @@ icon: material/new-box "direct" ], "server": "local", - "disable_cache": false + "disable_cache": false, + "client_subnet": "127.0.0.1" }, { "type": "logical", "mode": "and", "rules": [], "server": "local", - "disable_cache": false + "disable_cache": false, + "client_subnet": "127.0.0.1" } ] } @@ -322,6 +325,14 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 重写 DNS 回应中的 TTL。 +#### client_subnet + +!!! question "自 sing-box 1.9.0 起" + +默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 + ### 地址筛选字段 仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。 diff --git a/docs/configuration/dns/server.md b/docs/configuration/dns/server.md index 545810bf..e4d93544 100644 --- a/docs/configuration/dns/server.md +++ b/docs/configuration/dns/server.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.9.0" + + :material-plus: [client_subnet](#client_subnet) + ### Structure ```json @@ -5,17 +13,17 @@ "dns": { "servers": [ { - "tag": "google", - "address": "tls://dns.google", - "address_resolver": "local", - "address_strategy": "prefer_ipv4", - "strategy": "ipv4_only", - "detour": "direct" + "tag": "", + "address": "", + "address_resolver": "", + "address_strategy": "", + "strategy": "", + "detour": "", + "client_subnet": "" } ] } } - ``` ### Fields @@ -80,10 +88,20 @@ Default domain strategy for resolving the domain names. 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 Tag of an outbound for connecting to the dns server. 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 address to every query by default. + +Can be overrides by `rules.[].client_subnet`. + +Will overrides `dns.client_subnet`. diff --git a/docs/configuration/dns/server.zh.md b/docs/configuration/dns/server.zh.md index 36bcde5d..a15fdfd3 100644 --- a/docs/configuration/dns/server.zh.md +++ b/docs/configuration/dns/server.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.9.0 中的更改" + + :material-plus: [client_subnet](#client_subnet) + ### 结构 ```json @@ -5,17 +13,17 @@ "dns": { "servers": [ { - "tag": "google", - "address": "tls://dns.google", - "address_resolver": "local", - "address_strategy": "prefer_ipv4", - "strategy": "ipv4_only", - "detour": "direct" + "tag": "", + "address": "", + "address_resolver": "", + "address_strategy": "", + "strategy": "", + "detour": "", + "client_subnet": "" } ] } } - ``` ### 字段 @@ -87,3 +95,13 @@ DNS 服务器的地址。 用于连接到 DNS 服务器的出站的标签。 如果为空,将使用默认出站。 + +#### client_subnet + +!!! question "自 sing-box 1.9.0 起" + +默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +可以被 `rules.[].client_subnet` 覆盖。 + +将覆盖 `dns.client_subnet`。 diff --git a/docs/manual/proxy/client.md b/docs/manual/proxy/client.md index 41755cca..12a83039 100644 --- a/docs/manual/proxy/client.md +++ b/docs/manual/proxy/client.md @@ -338,74 +338,131 @@ flowchart TB === ":material-dns: DNS rules (1.9.0+)" - !!! warning "DNS leaks" - - 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. - - ```json - { - "dns": { - "servers": [ - { - "tag": "google", - "address": "tls://8.8.8.8" + === ":material-shield-off: With DNS Leaks" + + ```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" + }, + { + "clash_mode": "Default", + "server": "google" + }, + { + "rule_set": "geoip-cn", + "server": "local" + } + ] }, - { - "tag": "local", - "address": "https://223.5.5.5/dns-query", - "detour": "direct" + "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": "geoip-cn", + "format": "binary", + "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs" + } + ] + }, + "experimental": { + "clash_api": { + "default_mode": "Leak" + } } - ], - "rules": [ - { - "outbound": "any", - "server": "local" - }, - { - "clash_mode": "Direct", - "server": "local" - }, - { - "clash_mode": "Global", - "server": "google" - }, - { - "rule_set": "geosite-geolocation-cn", - "server": "local" - }, - { - "clash_mode": "Default", - "server": "google" - }, - { - "rule_set": "geoip-cn", - "server": "local" - } - ] - }, - "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": "geoip-cn", - "format": "binary", - "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs" - } - ] - }, - "experimental": { - "clash_api": { - "default_mode": "Leak" } - } - } - ``` + ``` + + === ":material-security: Without DNS Leaks (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" + }, + { + "rule_set": "geoip-cn", + "server": "google", + "client_subnet": "114.114.114.114" // 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": "geoip-cn", + "format": "binary", + "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs" + } + ] + } + } + ``` === ":material-router-network: Route rules" diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index fcdaaa92..e1f8bcc3 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -9,9 +9,7 @@ import ( "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/task" mDNS "github.com/miekg/dns" @@ -25,9 +23,11 @@ type LocalDNSTransport interface { func RegisterLocalDNSTransport(transport LocalDNSTransport) { 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 { - 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{ iif: transport, }, nil diff --git a/include/dhcp_stub.go b/include/dhcp_stub.go index c57aa430..47a19d2e 100644 --- a/include/dhcp_stub.go +++ b/include/dhcp_stub.go @@ -3,16 +3,12 @@ package include import ( - "context" - "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" - N "github.com/sagernet/sing/common/network" ) 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`) }) } diff --git a/include/quic_stub.go b/include/quic_stub.go index 17b502a7..ddf9723f 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -11,13 +11,12 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-dns" - "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) 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 }) v2ray.RegisterQUICConstructor( diff --git a/option/dns.go b/option/dns.go index e0d237b7..15201343 100644 --- a/option/dns.go +++ b/option/dns.go @@ -19,6 +19,7 @@ type DNSServerOptions struct { AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"` Detour string `json:"detour,omitempty"` + ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` } type DNSClientOptions struct { @@ -26,6 +27,7 @@ type DNSClientOptions struct { DisableCache bool `json:"disable_cache,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"` IndependentCache bool `json:"independent_cache,omitempty"` + ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` } type DNSFakeIPOptions struct { diff --git a/option/rule_dns.go b/option/rule_dns.go index d148e264..dc5e5c2b 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -100,6 +100,7 @@ type DefaultDNSRule struct { Server string `json:"server,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` } func (r DefaultDNSRule) IsValid() bool { @@ -108,16 +109,18 @@ func (r DefaultDNSRule) IsValid() bool { defaultValue.Server = r.Server defaultValue.DisableCache = r.DisableCache defaultValue.RewriteTTL = r.RewriteTTL + defaultValue.ClientSubnet = r.ClientSubnet return !reflect.DeepEqual(r, defaultValue) } type LogicalDNSRule struct { - Mode string `json:"mode"` - Rules []DNSRule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + Mode string `json:"mode"` + Rules []DNSRule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` + Server string `json:"server,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` } func (r LogicalDNSRule) IsValid() bool { diff --git a/route/router.go b/route/router.go index f2fe1a6d..ae57fc6b 100644 --- a/route/router.go +++ b/route/router.go @@ -225,7 +225,20 @@ func NewRouter( 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.Addr + 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 { return nil, E.Cause(err, "parse dns server[", tag, "]") } @@ -265,7 +278,12 @@ func NewRouter( } if defaultTransport == nil { 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] } diff --git a/route/router_dns.go b/route/router_dns.go index ee767e9e..7114882b 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -70,6 +70,9 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (con 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 { diff --git a/route/rule_dns.go b/route/rule_dns.go index 3eab61f8..760ff910 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -1,6 +1,8 @@ package route import ( + "net/netip" + "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -38,6 +40,7 @@ type DefaultDNSRule struct { abstractDefaultRule disableCache bool rewriteTTL *uint32 + clientSubnet *netip.Addr } func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { @@ -48,6 +51,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options }, disableCache: options.DisableCache, rewriteTTL: options.RewriteTTL, + clientSubnet: (*netip.Addr)(options.ClientSubnet), } if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) @@ -230,6 +234,10 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } +func (r *DefaultDNSRule) ClientSubnet() *netip.Addr { + return r.clientSubnet +} + func (r *DefaultDNSRule) WithAddressLimit() bool { if len(r.destinationIPCIDRItems) > 0 { return true @@ -264,6 +272,7 @@ type LogicalDNSRule struct { abstractLogicalRule disableCache bool rewriteTTL *uint32 + clientSubnet *netip.Addr } func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { @@ -275,6 +284,7 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options }, disableCache: options.DisableCache, rewriteTTL: options.RewriteTTL, + clientSubnet: (*netip.Addr)(options.ClientSubnet), } switch options.Mode { case C.LogicalTypeAnd: @@ -302,6 +312,10 @@ func (r *LogicalDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } +func (r *LogicalDNSRule) ClientSubnet() *netip.Addr { + return r.clientSubnet +} + func (r *LogicalDNSRule) WithAddressLimit() bool { for _, rawRule := range r.rules { switch rule := rawRule.(type) { diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index 1a2c2938..2b7346c6 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -21,9 +21,6 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/x/list" @@ -32,14 +29,14 @@ import ( ) func init() { - dns.RegisterTransport([]string{"dhcp"}, NewTransport) + dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) { + return NewTransport(options) + }) } type Transport struct { - name string - ctx context.Context + options dns.TransportOptions router adapter.Router - logger logger.Logger interfaceName string autoInterface bool interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] @@ -48,23 +45,20 @@ type Transport struct { updatedAt time.Time } -func NewTransport(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { - linkURL, err := url.Parse(link) +func NewTransport(options dns.TransportOptions) (*Transport, error) { + linkURL, err := url.Parse(options.Address) if err != nil { return nil, err } if linkURL.Host == "" { return nil, E.New("missing interface name for DHCP") } - router := adapter.RouterFromContext(ctx) + router := adapter.RouterFromContext(options.Context) if router == nil { return nil, E.New("missing router in context") } transport := &Transport{ - name: name, - ctx: ctx, router: router, - logger: logger, interfaceName: linkURL.Host, autoInterface: linkURL.Host == "auto", } @@ -72,7 +66,7 @@ func NewTransport(name string, ctx context.Context, logger logger.ContextLogger, } func (t *Transport) Name() string { - return t.name + return t.options.Name } func (t *Transport) Start() error { @@ -158,8 +152,8 @@ func (t *Transport) updateServers() error { return E.Cause(err, "dhcp: prepare interface") } - t.logger.Info("dhcp: query DNS servers on ", iface.Name) - fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout) + t.options.Logger.Info("dhcp: query DNS servers on ", iface.Name) + fetchCtx, cancel := context.WithTimeout(t.options.Context, C.DHCPTimeout) err = t.fetchServers0(fetchCtx, iface) cancel() if err != nil { @@ -175,7 +169,7 @@ func (t *Transport) updateServers() error { func (t *Transport) interfaceUpdated(int) { err := t.updateServers() if err != nil { - t.logger.Error("update servers: ", err) + t.options.Logger.Error("update servers: ", err) } } @@ -187,7 +181,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) err if runtime.GOOS == "linux" || runtime.GOOS == "android" { 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 { return err } @@ -225,17 +219,17 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes()) if err != nil { - t.logger.Trace("dhcp: parse DHCP response: ", err) + t.options.Logger.Trace("dhcp: parse DHCP response: ", err) return err } 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 } 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 } @@ -255,20 +249,22 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error { 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() }), ","), "]") } - serverDialer := common.Must1(dialer.NewDefault(t.router, option.DialerOptions{ BindInterface: iface.Name, UDPFragmentDefault: true, })) var transports []dns.Transport 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 { - return err + return E.Cause(err, "create UDP transport from DHCP result: ", serverAddr) } transports = append(transports, serverTransport) } diff --git a/transport/fakeip/server.go b/transport/fakeip/server.go index 40149aa4..5e0c7eef 100644 --- a/transport/fakeip/server.go +++ b/transport/fakeip/server.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" - N "github.com/sagernet/sing/common/network" mDNS "github.com/miekg/dns" ) @@ -20,7 +19,9 @@ var ( ) func init() { - dns.RegisterTransport([]string{"fakeip"}, NewTransport) + dns.RegisterTransport([]string{"fakeip"}, func(options dns.TransportOptions) (dns.Transport, error) { + return NewTransport(options) + }) } type Transport struct { @@ -30,15 +31,15 @@ type Transport struct { logger logger.ContextLogger } -func NewTransport(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { - router := adapter.RouterFromContext(ctx) +func NewTransport(options dns.TransportOptions) (*Transport, error) { + router := adapter.RouterFromContext(options.Context) if router == nil { return nil, E.New("missing router in context") } return &Transport{ - name: name, + name: options.Name, router: router, - logger: logger, + logger: options.Logger, }, nil } From 93ae3f7a1e110a1aabe717b9941eec7b41e0ecf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 14 Feb 2024 20:42:58 +0800 Subject: [PATCH 12/30] Add rejected DNS response cache support --- adapter/experimental.go | 4 + docs/configuration/dns/rule.md | 6 +- docs/configuration/dns/rule.zh.md | 6 +- docs/configuration/experimental/cache-file.md | 32 +++++- .../experimental/cache-file.zh.md | 29 ++++- experimental/cachefile/cache.go | 34 ++++-- experimental/cachefile/fakeip.go | 18 ++-- experimental/cachefile/rdrc.go | 101 ++++++++++++++++++ option/experimental.go | 10 +- route/router.go | 17 ++- route/router_dns.go | 31 ++++-- transport/dhcp/server.go | 1 + 12 files changed, 253 insertions(+), 36 deletions(-) create mode 100644 experimental/cachefile/rdrc.go diff --git a/adapter/experimental.go b/adapter/experimental.go index 2a6776cd..5e1cbd9d 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -9,6 +9,7 @@ import ( "time" "github.com/sagernet/sing-box/common/urltest" + "github.com/sagernet/sing-dns" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" ) @@ -30,6 +31,9 @@ type CacheFile interface { StoreFakeIP() bool FakeIPStorage + StoreRDRC() bool + dns.RDRCStore + LoadMode() string StoreMode(mode string) error LoadSelected(group string) string diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 5b42f20c..84b9b669 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -339,10 +339,14 @@ Will overrides `dns.client_subnet` and `servers.[].client_subnet`. 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. -!!! note "" +!!! 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" diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index eaeb8e68..c7977bc1 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -337,10 +337,14 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。 -!!! note "" +!!! info "" 引用的规则集中的 `ip_cidr` 项也作为地址筛选字段生效。 +!!! note "" + + 启用 `experimental.cache_file.store_rdrc` 以缓存结果。 + #### geoip !!! question "自 sing-box 1.9.0 起" diff --git a/docs/configuration/experimental/cache-file.md b/docs/configuration/experimental/cache-file.md index ca3f62e5..b30538e5 100644 --- a/docs/configuration/experimental/cache-file.md +++ b/docs/configuration/experimental/cache-file.md @@ -1,5 +1,14 @@ +--- +icon: material/new-box +--- + !!! 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 ```json @@ -7,7 +16,9 @@ "enabled": true, "path": "", "cache_id": "", - "store_fakeip": false + "store_fakeip": false, + "store_rdrc": false, + "rdrc_timeout": "" } ``` @@ -25,6 +36,23 @@ Path to the cache file. #### 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. + +#### 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. diff --git a/docs/configuration/experimental/cache-file.zh.md b/docs/configuration/experimental/cache-file.zh.md index da0ce39b..6d86dc84 100644 --- a/docs/configuration/experimental/cache-file.zh.md +++ b/docs/configuration/experimental/cache-file.zh.md @@ -1,5 +1,14 @@ +--- +icon: material/new-box +--- + !!! 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 @@ -7,7 +16,9 @@ "enabled": true, "path": "", "cache_id": "", - "store_fakeip": false + "store_fakeip": false, + "store_rdrc": false, + "rdrc_timeout": "" } ``` @@ -26,3 +37,19 @@ 缓存文件中的标识符。 如果不为空,配置特定的数据将使用由其键控的单独存储。 + +#### store_fakeip + +将 fakeip 存储在缓存文件中。 + +#### store_rdrc + +将拒绝的 DNS 响应缓存存储在缓存文件中。 + +[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#_3) 的检查结果将被缓存至过期。 + +#### rdrc_timeout + +拒绝的 DNS 响应缓存超时。 + +默认使用 `7d`。 diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 43b84562..9d45ea8e 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -29,6 +29,7 @@ var ( string(bucketExpand), string(bucketMode), string(bucketRuleSet), + string(bucketRDRC), } cacheIDDefault = []byte("default") @@ -37,17 +38,25 @@ var ( var _ adapter.CacheFile = (*CacheFile)(nil) type CacheFile struct { - ctx context.Context - path string - cacheID []byte - storeFakeIP bool - + ctx context.Context + path string + cacheID []byte + storeFakeIP bool + storeRDRC bool + rdrcTimeout time.Duration DB *bbolt.DB - saveAccess sync.RWMutex + saveMetadataTimer *time.Timer + saveFakeIPAccess sync.RWMutex saveDomain map[netip.Addr]string saveAddress4 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 } func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { @@ -61,14 +70,25 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { if 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{ ctx: ctx, path: filemanager.BasePath(ctx, path), cacheID: cacheIDBytes, storeFakeIP: options.StoreFakeIP, + storeRDRC: options.StoreRDRC, + rdrcTimeout: rdrcTimeout, saveDomain: make(map[netip.Addr]string), saveAddress4: make(map[string]netip.Addr), saveAddress6: make(map[string]netip.Addr), + saveRDRC: make(map[saveRDRCCacheKey]bool), } } diff --git a/experimental/cachefile/fakeip.go b/experimental/cachefile/fakeip.go index 41c1dee6..8fe0f113 100644 --- a/experimental/cachefile/fakeip.go +++ b/experimental/cachefile/fakeip.go @@ -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) { - c.saveAccess.Lock() + c.saveFakeIPAccess.Lock() if oldDomain, loaded := c.saveDomain[address]; loaded { if address.Is4() { delete(c.saveAddress4, oldDomain) @@ -111,27 +111,27 @@ func (c *CacheFile) FakeIPStoreAsync(address netip.Addr, domain string, logger l } else { c.saveAddress6[domain] = address } - c.saveAccess.Unlock() + c.saveFakeIPAccess.Unlock() go func() { err := c.FakeIPStore(address, domain) 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) if address.Is4() { delete(c.saveAddress4, domain) } else { delete(c.saveAddress6, domain) } - c.saveAccess.Unlock() + c.saveFakeIPAccess.Unlock() }() } func (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) { - c.saveAccess.RLock() + c.saveFakeIPAccess.RLock() cachedDomain, cached := c.saveDomain[address] - c.saveAccess.RUnlock() + c.saveFakeIPAccess.RUnlock() if cached { return cachedDomain, true } @@ -152,13 +152,13 @@ func (c *CacheFile) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bo cachedAddress netip.Addr cached bool ) - c.saveAccess.RLock() + c.saveFakeIPAccess.RLock() if !isIPv6 { cachedAddress, cached = c.saveAddress4[domain] } else { cachedAddress, cached = c.saveAddress6[domain] } - c.saveAccess.RUnlock() + c.saveFakeIPAccess.RUnlock() if cached { return cachedAddress, true } diff --git a/experimental/cachefile/rdrc.go b/experimental/cachefile/rdrc.go new file mode 100644 index 00000000..836beba1 --- /dev/null +++ b/experimental/cachefile/rdrc.go @@ -0,0 +1,101 @@ +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("rdrc") + +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) (rejected bool) { + c.saveRDRCAccess.RLock() + rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName}] + c.saveRDRCAccess.RUnlock() + if cached { + return + } + 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([]byte(qName)) + 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([]byte(qName)) + }) + } + return +} + +func (c *CacheFile) SaveRDRC(transportName string, qName string) 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 + } + expiresAt := buf.Get(8) + defer buf.Put(expiresAt) + binary.BigEndian.PutUint64(expiresAt, uint64(time.Now().Add(c.rdrcTimeout).Unix())) + return bucket.Put([]byte(qName), expiresAt) + }) +} + +func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, logger logger.Logger) { + saveKey := saveRDRCCacheKey{transportName, qName} + c.saveRDRCAccess.Lock() + c.saveRDRC[saveKey] = true + c.saveRDRCAccess.Unlock() + go func() { + err := c.SaveRDRC(transportName, qName) + if err != nil { + logger.Warn("save RDRC: ", err) + } + c.saveRDRCAccess.Lock() + delete(c.saveRDRC, saveKey) + c.saveRDRCAccess.Unlock() + }() +} diff --git a/option/experimental.go b/option/experimental.go index c685f51f..9f6071ba 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -8,10 +8,12 @@ type ExperimentalOptions struct { } type CacheFileOptions struct { - Enabled bool `json:"enabled,omitempty"` - Path string `json:"path,omitempty"` - CacheID string `json:"cache_id,omitempty"` - StoreFakeIP bool `json:"store_fakeip,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Path string `json:"path,omitempty"` + CacheID string `json:"cache_id,omitempty"` + StoreFakeIP bool `json:"store_fakeip,omitempty"` + StoreRDRC bool `json:"store_rdrc,omitempty"` + RDRCTimeout Duration `json:"rdrc_timeout,omitempty"` } type ClashAPIOptions struct { diff --git a/route/router.go b/route/router.go index ae57fc6b..e9807bd4 100644 --- a/route/router.go +++ b/route/router.go @@ -139,7 +139,17 @@ func NewRouter( DisableCache: dnsOptions.DNSClientOptions.DisableCache, DisableExpire: dnsOptions.DNSClientOptions.DisableExpire, 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 { routeRule, err := NewRule(router, router.logger, ruleOptions, true) @@ -625,6 +635,11 @@ func (r *Router) Start() error { return E.Cause(err, "initialize rule[", i, "]") } } + + 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() diff --git a/route/router_dns.go b/route/router_dns.go index 7114882b..4bcc4f23 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -139,7 +139,9 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er } cancel() if err != nil { - if errors.Is(err, dns.ErrResponseRejected) { + if errors.Is(err, dns.ErrResponseRejectedCached) { + r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String())), " (cached)") + } else if errors.Is(err, dns.ErrResponseRejected) { 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()))) @@ -166,6 +168,15 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er } 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) ctx, metadata := adapter.AppendContext(ctx) metadata.Domain = domain @@ -174,8 +185,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS transportStrategy dns.DomainStrategy rule adapter.DNSRule ruleIndex int - resultAddrs []netip.Addr - err error ) ruleIndex = -1 for { @@ -193,22 +202,24 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout) if rule != nil && rule.WithAddressLimit() { addressLimit = true - resultAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool { + responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool { metadata.DestinationAddresses = responseAddrs return rule.MatchAddressLimit(metadata) }) } else { addressLimit = false - resultAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy) + responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy) } cancel() if err != nil { - if errors.Is(err, dns.ErrResponseRejected) { + 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(resultAddrs) == 0 { + } else if len(responseAddrs) == 0 { r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") err = dns.RCodeNameError } @@ -216,10 +227,10 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS break } } - if len(resultAddrs) > 0 { - r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(resultAddrs), " ")) + if len(responseAddrs) > 0 { + r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " ")) } - return resultAddrs, err + return responseAddrs, err } func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) { diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index 2b7346c6..8325c37b 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -58,6 +58,7 @@ func NewTransport(options dns.TransportOptions) (*Transport, error) { return nil, E.New("missing router in context") } transport := &Transport{ + options: options, router: router, interfaceName: linkURL.Host, autoInterface: linkURL.Host == "auto", From 5327aeaea4fdc6d42c5aa6574c5b795e9f7cc8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 15 Mar 2024 17:21:52 +0800 Subject: [PATCH 13/30] Fix DNS fallthrough incorrectly --- route/router_dns.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/route/router_dns.go b/route/router_dns.go index 4bcc4f23..21beca97 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -138,10 +138,13 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er 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()))) @@ -149,9 +152,10 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ")) } } - if !addressLimit || err == nil { - break + if addressLimit && rejected { + continue } + break } } if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { From 917514e09f1b18c09ea486fe331203c5cf8f2223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 14 Feb 2024 20:42:58 +0800 Subject: [PATCH 14/30] Improve DNS truncate behavior --- outbound/dns.go | 40 +++++----------------------------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/outbound/dns.go b/outbound/dns.go index df32a019..b18b901e 100644 --- a/outbound/dns.go +++ b/outbound/dns.go @@ -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 { + metadata.Destination = M.Socksaddr{} defer conn.Close() - ctx = adapter.WithContext(ctx, &metadata) for { err := d.handleConnection(ctx, conn, metadata) 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 { + metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn var counters []N.CountFunc 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 { - readWaiter.InitializeReadWaiter(N.ReadWaitOptions{ - MTU: dns.FixedPacketSize, - }) + readWaiter.InitializeReadWaiter(N.ReadWaitOptions{}) return d.newPacketConnection(ctx, conn, readWaiter, counters, cachedPackets, metadata) } break } - ctx = adapter.WithContext(ctx, &metadata) fastClose, cancel := common.ContextWithCancelCause(ctx) timeout := canceler.New(fastClose, cancel, C.DNSTimeout) var group task.Group @@ -167,15 +165,11 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada return err } timeout.Update() - responseBuffer := buf.NewPacket() - responseBuffer.Resize(1024, 0) - n, err := response.PackBuffer(responseBuffer.FreeBytes()) + responseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024) if err != nil { cancel(err) - responseBuffer.Release() return err } - responseBuffer.Truncate(len(n)) err = conn.WritePacket(responseBuffer, destination) if err != nil { cancel(err) @@ -241,16 +235,11 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa return err } timeout.Update() - response = truncateDNSMessage(response, 512) // TODO: add an option to custom UDP buffer size - responseBuffer := buf.NewSize(dns.FixedPacketSize) - responseBuffer.Resize(1024, 0) - n, err := response.PackBuffer(responseBuffer.FreeBytes()) + responseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024) if err != nil { cancel(err) - responseBuffer.Release() return err } - responseBuffer.Truncate(len(n)) err = conn.WritePacket(responseBuffer, destination) if err != nil { cancel(err) @@ -264,22 +253,3 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa }) 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 -} From 71d1879bd6c4c1868a25e4be6d6897febc23861d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 21 Feb 2024 14:27:55 +0800 Subject: [PATCH 15/30] Fix missing `rule_set_ipcidr_match_source` item in DNS rules --- docs/configuration/dns/rule.md | 8 +++ docs/configuration/dns/rule.zh.md | 8 +++ docs/configuration/route/rule.md | 1 + docs/configuration/route/rule.zh.md | 1 + docs/configuration/rule-set/headless-rule.md | 2 +- option/rule_dns.go | 73 ++++++++++---------- route/rule_dns.go | 4 +- route/rule_item_rule_set.go | 5 +- 8 files changed, 62 insertions(+), 40 deletions(-) diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 84b9b669..40dce7fd 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -8,6 +8,7 @@ icon: material/new-box :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" @@ -116,6 +117,7 @@ icon: material/new-box "geoip-cn", "geosite-cn" ], + "rule_set_ipcidr_match_source": false, "invert": false, "outbound": [ "direct" @@ -303,6 +305,12 @@ Match WiFi BSSID. 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 match result. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index c7977bc1..f27aac9a 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -8,6 +8,7 @@ icon: material/new-box :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 中的更改" @@ -116,6 +117,7 @@ icon: material/new-box "geoip-cn", "geosite-cn" ], + "rule_set_ipcidr_match_source": false, "invert": false, "outbound": [ "direct" @@ -301,6 +303,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 匹配[规则集](/zh/configuration/route/#rule_set)。 +#### rule_set_ipcidr_match_source + +!!! question "自 sing-box 1.9.0 起" + +使规则集中的 `ipcidr` 规则匹配源 IP。 + #### invert 反选匹配结果。 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index b21bf658..be9ee4cc 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -105,6 +105,7 @@ "geoip-cn", "geosite-cn" ], + "rule_set_ipcidr_match_source": false, "invert": false, "outbound": "direct" }, diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 3f8b4715..881f97b0 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -103,6 +103,7 @@ "geoip-cn", "geosite-cn" ], + "rule_set_ipcidr_match_source": false, "invert": false, "outbound": "direct" }, diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index 99984899..9109841f 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -124,7 +124,7 @@ Match source IP CIDR. !!! 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. diff --git a/option/rule_dns.go b/option/rule_dns.go index dc5e5c2b..ababea41 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -65,42 +65,43 @@ func (r DNSRule) IsValid() bool { } type DefaultDNSRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network Listable[string] `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - GeoIP Listable[string] `json:"geoip,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - Outbound Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet Listable[string] `json:"rule_set,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` + Inbound Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` + Network Listable[string] `json:"network,omitempty"` + AuthUser Listable[string] `json:"auth_user,omitempty"` + Protocol Listable[string] `json:"protocol,omitempty"` + Domain Listable[string] `json:"domain,omitempty"` + DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex Listable[string] `json:"domain_regex,omitempty"` + Geosite Listable[string] `json:"geosite,omitempty"` + SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` + GeoIP Listable[string] `json:"geoip,omitempty"` + IPCIDR Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + SourcePort Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange Listable[string] `json:"source_port_range,omitempty"` + Port Listable[uint16] `json:"port,omitempty"` + PortRange Listable[string] `json:"port_range,omitempty"` + ProcessName Listable[string] `json:"process_name,omitempty"` + ProcessPath Listable[string] `json:"process_path,omitempty"` + PackageName Listable[string] `json:"package_name,omitempty"` + User Listable[string] `json:"user,omitempty"` + UserID Listable[int32] `json:"user_id,omitempty"` + Outbound Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` + RuleSet Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_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 *ListenAddress `json:"client_subnet,omitempty"` } func (r DefaultDNSRule) IsValid() bool { diff --git a/route/rule_dns.go b/route/rule_dns.go index 760ff910..7501349f 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -219,7 +219,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.allItems = append(rule.allItems, item) } if len(options.RuleSet) > 0 { - item := NewRuleSetItem(router, options.RuleSet, false) + item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } @@ -247,7 +247,7 @@ func (r *DefaultDNSRule) WithAddressLimit() bool { if !isRuleSet { continue } - if ruleSet.ContainsIPCIDRRule() { + if ruleSet.ContainsDestinationIPCIDRRule() { return true } } diff --git a/route/rule_item_rule_set.go b/route/rule_item_rule_set.go index 8354e421..482a9c7b 100644 --- a/route/rule_item_rule_set.go +++ b/route/rule_item_rule_set.go @@ -47,7 +47,10 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool { return false } -func (r *RuleSetItem) ContainsIPCIDRRule() bool { +func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool { + if r.ipcidrMatchSource { + return false + } return common.Any(r.setList, func(ruleSet adapter.RuleSet) bool { return ruleSet.Metadata().ContainsIPCIDRRule }) From 2ae192305caa9f2301c5ad7de4ce264c6ae6e611 Mon Sep 17 00:00:00 2001 From: PuerNya Date: Mon, 5 Feb 2024 02:42:15 +0800 Subject: [PATCH 16/30] Always disable cache for fake-ip DNS transport if `independent_cache` disabled --- route/router.go | 2 ++ route/router_dns.go | 32 +++++++++++++++++++------------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/route/router.go b/route/router.go index e9807bd4..5e00d2c9 100644 --- a/route/router.go +++ b/route/router.go @@ -69,6 +69,7 @@ type Router struct { geositeCache map[string]adapter.Rule needFindProcess bool dnsClient *dns.Client + dnsIndependentCache bool defaultDomainStrategy dns.DomainStrategy dnsRules []adapter.DNSRule ruleSets []adapter.RuleSet @@ -122,6 +123,7 @@ func NewRouter( geositeOptions: common.PtrValueOrDefault(options.Geosite), geositeCache: make(map[string]adapter.Rule), needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, + dnsIndependentCache: dnsOptions.IndependentCache, defaultDetour: options.Final, defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy), interfaceFinder: control.NewDefaultInterfaceFinder(), diff --git a/route/router_dns.go b/route/router_dns.go index 21beca97..2ac439d7 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -56,7 +56,8 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (con r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour) continue } - if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP { + _, isFakeIP := transport.(adapter.FakeIPTransport) + if isFakeIP && !allowFakeIP { continue } displayRuleIndex := ruleIndex @@ -64,7 +65,7 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (con displayRuleIndex += index + 1 } r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", detour) - if rule.DisableCache() { + if (isFakeIP && !r.dnsIndependentCache) || rule.DisableCache() { ctx = dns.ContextWithDisableCache(ctx, true) } if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { @@ -93,9 +94,10 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String())) } var ( - response *mDNS.Msg - cached bool - err error + response *mDNS.Msg + cached bool + transport dns.Transport + err error ) response, cached = r.dnsClient.ExchangeCache(ctx, message) if !cached { @@ -112,7 +114,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er metadata.Domain = fqdnToDomain(message.Question[0].Name) } var ( - transport dns.Transport strategy dns.DomainStrategy rule adapter.DNSRule ruleIndex int @@ -158,17 +159,22 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er break } } + if err != nil { + return nil, err + } if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { - for _, answer := range response.Answer { - switch record := answer.(type) { - case *mDNS.A: - r.dnsReverseMapping.Save(M.AddrFromIP(record.A), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) - case *mDNS.AAAA: - r.dnsReverseMapping.Save(M.AddrFromIP(record.AAAA), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) + if _, isFakeIP := transport.(adapter.FakeIPTransport); !isFakeIP { + for _, answer := range response.Answer { + switch record := answer.(type) { + case *mDNS.A: + r.dnsReverseMapping.Save(M.AddrFromIP(record.A), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) + case *mDNS.AAAA: + r.dnsReverseMapping.Save(M.AddrFromIP(record.AAAA), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) + } } } } - return response, err + return response, nil } func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { From d918863ac5c45d73e64a92a74a577383041aac02 Mon Sep 17 00:00:00 2001 From: dyhkwong <50692134+dyhkwong@users.noreply.github.com> Date: Sun, 28 Apr 2024 18:43:27 +0800 Subject: [PATCH 17/30] Always disable cache for fake-ip servers --- route/router.go | 2 -- route/router_dns.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/route/router.go b/route/router.go index 5e00d2c9..e9807bd4 100644 --- a/route/router.go +++ b/route/router.go @@ -69,7 +69,6 @@ type Router struct { geositeCache map[string]adapter.Rule needFindProcess bool dnsClient *dns.Client - dnsIndependentCache bool defaultDomainStrategy dns.DomainStrategy dnsRules []adapter.DNSRule ruleSets []adapter.RuleSet @@ -123,7 +122,6 @@ func NewRouter( geositeOptions: common.PtrValueOrDefault(options.Geosite), geositeCache: make(map[string]adapter.Rule), needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, - dnsIndependentCache: dnsOptions.IndependentCache, defaultDetour: options.Final, defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy), interfaceFinder: control.NewDefaultInterfaceFinder(), diff --git a/route/router_dns.go b/route/router_dns.go index 2ac439d7..88c129df 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -65,7 +65,7 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (con displayRuleIndex += index + 1 } r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", detour) - if (isFakeIP && !r.dnsIndependentCache) || rule.DisableCache() { + if isFakeIP || rule.DisableCache() { ctx = dns.ContextWithDisableCache(ctx, true) } if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { From 0e120f8a444cab6e788c340e854aeb8be096f982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B0=94=E6=81=AF?= Date: Tue, 19 Mar 2024 12:04:16 +0800 Subject: [PATCH 18/30] Fix DNS exchange index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 气息 --- route/router_dns.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/route/router_dns.go b/route/router_dns.go index 88c129df..c3383e8b 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -47,7 +47,7 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (con if index != -1 { dnsRules = dnsRules[index+1:] } - for ruleIndex, rule := range dnsRules { + for currentRuleIndex, rule := range dnsRules { metadata.ResetRuleCache() if rule.Match(metadata) { detour := rule.Outbound() @@ -60,11 +60,11 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (con if isFakeIP && !allowFakeIP { continue } - displayRuleIndex := ruleIndex + ruleIndex := currentRuleIndex if index != -1 { - displayRuleIndex += index + 1 + ruleIndex += index + 1 } - r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", detour) + r.dnsLogger.DebugContext(ctx, "match[", ruleIndex, "] ", rule.String(), " => ", detour) if isFakeIP || rule.DisableCache() { ctx = dns.ContextWithDisableCache(ctx, true) } From da9e22b4e6d822e4d425664ba2c1535f3b12a916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 12 May 2024 15:06:21 +0800 Subject: [PATCH 19/30] Add custom prefix support in EDNS0 client subnet options --- adapter/router.go | 2 +- docs/configuration/dns/index.md | 4 +++- docs/configuration/dns/index.zh.md | 6 +++-- docs/configuration/dns/rule.md | 8 ++++--- docs/configuration/dns/rule.zh.md | 8 ++++--- docs/configuration/dns/server.md | 4 +++- docs/configuration/dns/server.zh.md | 4 +++- docs/manual/proxy/client.md | 2 +- experimental/cachefile/cache.go | 1 + experimental/cachefile/rdrc.go | 28 +++++++++++++++--------- option/dns.go | 4 ++-- option/rule_dns.go | 16 +++++++------- option/types.go | 34 +++++++++++++++++++++++++++++ route/router.go | 4 ++-- route/rule_dns.go | 12 +++++----- 15 files changed, 96 insertions(+), 41 deletions(-) diff --git a/adapter/router.go b/adapter/router.go index 0d771dee..73849b97 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -86,7 +86,7 @@ type DNSRule interface { Rule DisableCache() bool RewriteTTL() *uint32 - ClientSubnet() *netip.Addr + ClientSubnet() *netip.Prefix WithAddressLimit() bool MatchAddressLimit(metadata *InboundContext) bool } diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index 71219dbb..c0eafccc 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -73,6 +73,8 @@ problematic in environments such as macOS, where DNS is proxied and cached by th !!! question "Since sing-box 1.9.0" -Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default. +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`. diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index 164c37cd..ba390cef 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -71,8 +71,10 @@ icon: material/new-box !!! question "自 sing-box 1.9.0 起" -默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 - +默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 + 可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。 #### fakeip diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 40dce7fd..22b5d872 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -125,7 +125,7 @@ icon: material/new-box "server": "local", "disable_cache": false, "rewrite_ttl": 100, - "client_subnet": "127.0.0.1" + "client_subnet": "127.0.0.1/24" }, { "type": "logical", @@ -134,7 +134,7 @@ icon: material/new-box "server": "local", "disable_cache": false, "rewrite_ttl": 100, - "client_subnet": "127.0.0.1" + "client_subnet": "127.0.0.1/24" } ] } @@ -339,7 +339,9 @@ Rewrite TTL in DNS responses. !!! question "Since sing-box 1.9.0" -Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default. +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`. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index f27aac9a..9b77bd17 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -124,7 +124,7 @@ icon: material/new-box ], "server": "local", "disable_cache": false, - "client_subnet": "127.0.0.1" + "client_subnet": "127.0.0.1/24" }, { "type": "logical", @@ -132,7 +132,7 @@ icon: material/new-box "rules": [], "server": "local", "disable_cache": false, - "client_subnet": "127.0.0.1" + "client_subnet": "127.0.0.1/24" } ] } @@ -337,7 +337,9 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 !!! question "自 sing-box 1.9.0 起" -默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 +默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 diff --git a/docs/configuration/dns/server.md b/docs/configuration/dns/server.md index e4d93544..3c524581 100644 --- a/docs/configuration/dns/server.md +++ b/docs/configuration/dns/server.md @@ -100,7 +100,9 @@ Default outbound will be used if empty. !!! question "Since sing-box 1.9.0" -Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default. +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`. diff --git a/docs/configuration/dns/server.zh.md b/docs/configuration/dns/server.zh.md index a15fdfd3..baa11751 100644 --- a/docs/configuration/dns/server.zh.md +++ b/docs/configuration/dns/server.zh.md @@ -100,7 +100,9 @@ DNS 服务器的地址。 !!! question "自 sing-box 1.9.0 起" -默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 +默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 可以被 `rules.[].client_subnet` 覆盖。 diff --git a/docs/manual/proxy/client.md b/docs/manual/proxy/client.md index 12a83039..7a65248f 100644 --- a/docs/manual/proxy/client.md +++ b/docs/manual/proxy/client.md @@ -441,7 +441,7 @@ flowchart TB { "rule_set": "geoip-cn", "server": "google", - "client_subnet": "114.114.114.114" // Any China client IP address + "client_subnet": "114.114.114.114/24" // Any China client IP address } ] }, diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 9d45ea8e..1027588f 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -57,6 +57,7 @@ type CacheFile struct { type saveRDRCCacheKey struct { TransportName string QuestionName string + QType uint16 } func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { diff --git a/experimental/cachefile/rdrc.go b/experimental/cachefile/rdrc.go index 836beba1..c4800951 100644 --- a/experimental/cachefile/rdrc.go +++ b/experimental/cachefile/rdrc.go @@ -9,7 +9,7 @@ import ( "github.com/sagernet/sing/common/logger" ) -var bucketRDRC = []byte("rdrc") +var bucketRDRC = []byte("rdrc2") func (c *CacheFile) StoreRDRC() bool { return c.storeRDRC @@ -19,13 +19,17 @@ func (c *CacheFile) RDRCTimeout() time.Duration { return c.rdrcTimeout } -func (c *CacheFile) LoadRDRC(transportName string, qName string) (rejected bool) { +func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) { c.saveRDRCAccess.RLock() - rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName}] + 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) @@ -36,7 +40,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string) (rejected bool) if bucket == nil { return nil } - content := bucket.Get([]byte(qName)) + content := bucket.Get(key) if content == nil { return nil } @@ -61,13 +65,13 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string) (rejected bool) if bucket == nil { return nil } - return bucket.Delete([]byte(qName)) + return bucket.Delete(key) }) } return } -func (c *CacheFile) SaveRDRC(transportName string, qName string) error { +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 { @@ -77,20 +81,24 @@ func (c *CacheFile) SaveRDRC(transportName string, qName string) error { 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([]byte(qName), expiresAt) + return bucket.Put(key, expiresAt) }) } -func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, logger logger.Logger) { - saveKey := saveRDRCCacheKey{transportName, qName} +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) + err := c.SaveRDRC(transportName, qName, qType) if err != nil { logger.Warn("save RDRC: ", err) } diff --git a/option/dns.go b/option/dns.go index 15201343..be947583 100644 --- a/option/dns.go +++ b/option/dns.go @@ -19,7 +19,7 @@ type DNSServerOptions struct { AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"` Detour string `json:"detour,omitempty"` - ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` + ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` } type DNSClientOptions struct { @@ -27,7 +27,7 @@ type DNSClientOptions struct { DisableCache bool `json:"disable_cache,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"` IndependentCache bool `json:"independent_cache,omitempty"` - ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` + ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` } type DNSFakeIPOptions struct { diff --git a/option/rule_dns.go b/option/rule_dns.go index ababea41..c5994e1c 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -101,7 +101,7 @@ type DefaultDNSRule struct { Server string `json:"server,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` + ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` } func (r DefaultDNSRule) IsValid() bool { @@ -115,13 +115,13 @@ func (r DefaultDNSRule) IsValid() bool { } type LogicalDNSRule struct { - Mode string `json:"mode"` - Rules []DNSRule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` + Mode string `json:"mode"` + Rules []DNSRule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` + Server string `json:"server,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` } func (r LogicalDNSRule) IsValid() bool { diff --git a/option/types.go b/option/types.go index aba445ee..83fee8f0 100644 --- a/option/types.go +++ b/option/types.go @@ -51,6 +51,40 @@ func (a *ListenAddress) Build() netip.Addr { 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 func (v *NetworkList) UnmarshalJSON(content []byte) error { diff --git a/route/router.go b/route/router.go index e9807bd4..484216ee 100644 --- a/route/router.go +++ b/route/router.go @@ -27,7 +27,7 @@ import ( "github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/transport/fakeip" "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-vmess" "github.com/sagernet/sing/common" @@ -235,7 +235,7 @@ func NewRouter( return nil, E.New("parse dns server[", tag, "]: missing address_resolver") } } - var clientSubnet netip.Addr + var clientSubnet netip.Prefix if server.ClientSubnet != nil { clientSubnet = server.ClientSubnet.Build() } else if dnsOptions.ClientSubnet != nil { diff --git a/route/rule_dns.go b/route/rule_dns.go index 7501349f..955526fc 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -40,7 +40,7 @@ type DefaultDNSRule struct { abstractDefaultRule disableCache bool rewriteTTL *uint32 - clientSubnet *netip.Addr + clientSubnet *netip.Prefix } func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { @@ -51,7 +51,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options }, disableCache: options.DisableCache, rewriteTTL: options.RewriteTTL, - clientSubnet: (*netip.Addr)(options.ClientSubnet), + clientSubnet: (*netip.Prefix)(options.ClientSubnet), } if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) @@ -234,7 +234,7 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } -func (r *DefaultDNSRule) ClientSubnet() *netip.Addr { +func (r *DefaultDNSRule) ClientSubnet() *netip.Prefix { return r.clientSubnet } @@ -272,7 +272,7 @@ type LogicalDNSRule struct { abstractLogicalRule disableCache bool rewriteTTL *uint32 - clientSubnet *netip.Addr + clientSubnet *netip.Prefix } func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { @@ -284,7 +284,7 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options }, disableCache: options.DisableCache, rewriteTTL: options.RewriteTTL, - clientSubnet: (*netip.Addr)(options.ClientSubnet), + clientSubnet: (*netip.Prefix)(options.ClientSubnet), } switch options.Mode { case C.LogicalTypeAnd: @@ -312,7 +312,7 @@ func (r *LogicalDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } -func (r *LogicalDNSRule) ClientSubnet() *netip.Addr { +func (r *LogicalDNSRule) ClientSubnet() *netip.Prefix { return r.clientSubnet } From d7160c19cf44a97d6395dec343018e06b79ec0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 6 Feb 2024 18:52:45 +0800 Subject: [PATCH 20/30] Fixed order for Clash modes --- experimental/clashapi.go | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/experimental/clashapi.go b/experimental/clashapi.go index 805fbd5b..872d9b99 100644 --- a/experimental/clashapi.go +++ b/experimental/clashapi.go @@ -3,6 +3,7 @@ package experimental import ( "context" "os" + "sort" "github.com/sagernet/sing-box/adapter" 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 { - var clashMode []string - clashMode = append(clashMode, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...) - clashMode = append(clashMode, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...) - clashMode = common.FilterNotDefault(common.Uniq(clashMode)) - return clashMode + var clashModes []string + clashModes = append(clashModes, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...) + clashModes = append(clashModes, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...) + clashModes = common.FilterNotDefault(common.Uniq(clashModes)) + 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 { From 5899e95ff15dd51ba0b29462e5be285c4db76e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 14 Feb 2024 13:08:08 +0800 Subject: [PATCH 21/30] Update quic-go to v0.43.1 --- common/tls/ech_quic.go | 5 ++--- go.mod | 18 +++++++++--------- go.sum | 38 +++++++++++++++++++------------------- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/common/tls/ech_quic.go b/common/tls/ech_quic.go index b9cc5ede..fef506db 100644 --- a/common/tls/ech_quic.go +++ b/common/tls/ech_quic.go @@ -27,11 +27,10 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad 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{ TLSClientConfig: c.config, - QuicConfig: quicConfig, - EnableDatagrams: enableDatagrams, + QUICConfig: quicConfig, Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg) if err != nil { diff --git a/go.mod b/go.mod index 1f501d02..1e6430ea 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 - github.com/gofrs/uuid/v5 v5.1.0 + github.com/gofrs/uuid/v5 v5.2.0 github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/libdns/alidns v1.0.3 github.com/libdns/cloudflare v0.1.1 @@ -24,12 +24,12 @@ require ( github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/gomobile v0.1.3 github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e - github.com/sagernet/quic-go v0.43.0-beta.3 + github.com/sagernet/quic-go v0.43.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.4.0-beta.20 github.com/sagernet/sing-dns v0.2.0-beta.18 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-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 @@ -44,9 +44,9 @@ require ( github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.22.0 - golang.org/x/net v0.24.0 - golang.org/x/sys v0.19.0 + golang.org/x/crypto v0.23.0 + golang.org/x/net v0.25.0 + golang.org/x/sys v0.20.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 @@ -84,12 +84,12 @@ require ( github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.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/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 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index a6bb20cf..1cd456be 100644 --- a/go.sum +++ b/go.sum @@ -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/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gofrs/uuid/v5 v5.1.0 h1:S5rqVKIigghZTCBKPCw0Y+bXkn26K3TB5mvQq2Ix8dk= -github.com/gofrs/uuid/v5 v5.1.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= +github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= @@ -101,8 +101,8 @@ github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e h1:DOkjByVeAR56dks github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.43.0-beta.3 h1:qclJbbpgZe76EH62Bdu3LfDSC2zmuxj7zXCpdQBbe7c= -github.com/sagernet/quic-go v0.43.0-beta.3/go.mod h1:3EtxR1Yaa1FZu6jFPiBHpOAdhOxL4A3EPxmiVgjJvVM= +github.com/sagernet/quic-go v0.43.1-beta.1 h1:alizUjpvWYcz08dBCQsULOd+1xu0o7UtlyYf6SLbRNg= +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/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= @@ -112,8 +112,8 @@ github.com/sagernet/sing-dns v0.2.0-beta.18 h1:6vzXZThRdA7YUzBOpSbUT48XRumtl/KIp github.com/sagernet/sing-dns v0.2.0-beta.18/go.mod h1:k/dmFcQpg6+m08gC1yQBy+13+QkuLqpKr4bIreq4U24= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= -github.com/sagernet/sing-quic v0.1.15 h1:LGWPxQEeg89+68RHP7HtAV0RZeEWQikUqOfE9nYmr2A= -github.com/sagernet/sing-quic v0.1.15/go.mod h1:L+VtzvuPbf8VW8F4R7KiygqpXY4lO7t2wwcQuHjh8Ew= +github.com/sagernet/sing-quic v0.2.0-beta.5 h1:ceKFLd1iS5AtM+pScKmcDp5k7R6WgYIe8vl6nB0aVsE= +github.com/sagernet/sing-quic v0.2.0-beta.5/go.mod h1:lfad61lScAZhAxZ0DHZWvEIcAaT38O6zPTR4vLsHeP0= 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-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= @@ -163,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= 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +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/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -184,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.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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.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.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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +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/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.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +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/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= From cf2181dd3a1878c456b2f156addd6cd02bdea6ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 14 Feb 2024 13:08:08 +0800 Subject: [PATCH 22/30] Update gVisor to 20240422.0 --- go.mod | 6 +++--- go.sum | 12 ++++++------ transport/wireguard/device_stack.go | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 1e6430ea..81993d51 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/gomobile v0.1.3 - github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e + github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f github.com/sagernet/quic-go v0.43.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.4.0-beta.20 @@ -33,7 +33,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.6 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.2.7 + github.com/sagernet/sing-tun v0.3.0-beta.6 github.com/sagernet/sing-vmess v0.1.8 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 @@ -78,7 +78,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect - github.com/sagernet/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/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect diff --git a/go.sum b/go.sum index 1cd456be..672d3469 100644 --- a/go.sum +++ b/go.sum @@ -97,10 +97,10 @@ 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/gomobile v0.1.3 h1:ohjIb1Ou2+1558PnZour3od69suSuvkdSVOlO1tC4B8= 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-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw= -github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= -github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= +github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= +github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= +github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/quic-go v0.43.1-beta.1 h1:alizUjpvWYcz08dBCQsULOd+1xu0o7UtlyYf6SLbRNg= 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= @@ -120,8 +120,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.2.7 h1:6QtJkeSj6BTTQPGxbbiuV8eh7GdV46w2G0N8CmISwdc= -github.com/sagernet/sing-tun v0.2.7/go.mod h1:MKAAHUzVfj7d9zos4lsz6wjXu86/mJyd/gejiAnWj/w= +github.com/sagernet/sing-tun v0.3.0-beta.6 h1:L11kMrM7UfUW0pzQiU66Fffh4o86KZc1SFGbkYi8Ma8= +github.com/sagernet/sing-tun v0.3.0-beta.6/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= 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/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index 9d9b4549..7f57b7c7 100644 --- a/transport/wireguard/device_stack.go +++ b/transport/wireguard/device_stack.go @@ -34,7 +34,7 @@ type StackDevice struct { stack *stack.Stack mtu uint32 events chan wgTun.Event - outbound chan stack.PacketBufferPtr + outbound chan *stack.PacketBuffer packetOutbound chan *buf.Buffer done chan struct{} dispatcher stack.NetworkDispatcher @@ -52,7 +52,7 @@ func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, er stack: ipStack, mtu: mtu, events: make(chan wgTun.Event, 1), - outbound: make(chan stack.PacketBufferPtr, 256), + outbound: make(chan *stack.PacketBuffer, 256), packetOutbound: make(chan *buf.Buffer, 256), done: make(chan struct{}), } @@ -283,10 +283,10 @@ func (ep *wireEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone } -func (ep *wireEndpoint) AddHeader(buffer stack.PacketBufferPtr) { +func (ep *wireEndpoint) AddHeader(buffer *stack.PacketBuffer) { } -func (ep *wireEndpoint) ParseHeader(ptr stack.PacketBufferPtr) bool { +func (ep *wireEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool { return true } From a2098c18e1bfdd5197a534eb596c36685cee57cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 7 May 2024 20:34:24 +0800 Subject: [PATCH 23/30] Handle `includeAllNetworks` --- experimental/libbox/config.go | 4 ++++ experimental/libbox/platform.go | 1 + experimental/libbox/platform/interface.go | 1 + experimental/libbox/service.go | 4 ++++ inbound/tun.go | 11 ++++++++++- 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 3b1d9f1d..b7731143 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -82,6 +82,10 @@ func (s *platformInterfaceStub) UnderNetworkExtension() bool { return false } +func (s *platformInterfaceStub) IncludeAllNetworks() bool { + return false +} + func (s *platformInterfaceStub) ClearDNSCache() { } diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 451a72a9..4078140f 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -19,6 +19,7 @@ type PlatformInterface interface { UsePlatformInterfaceGetter() bool GetInterfaces() (NetworkInterfaceIterator, error) UnderNetworkExtension() bool + IncludeAllNetworks() bool ReadWIFIState() *WIFIState ClearDNSCache() } diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index b250c8ae..3bec13fa 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -21,6 +21,7 @@ type Interface interface { UsePlatformInterfaceGetter() bool Interfaces() ([]control.Interface, error) UnderNetworkExtension() bool + IncludeAllNetworks() bool ClearDNSCache() ReadWIFIState() adapter.WIFIState process.Searcher diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 2d755d0d..0a54d7ab 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -213,6 +213,10 @@ func (w *platformInterfaceWrapper) UnderNetworkExtension() bool { return w.iif.UnderNetworkExtension() } +func (w *platformInterfaceWrapper) IncludeAllNetworks() bool { + return w.iif.IncludeAllNetworks() +} + func (w *platformInterfaceWrapper) ClearDNSCache() { w.iif.ClearDNSCache() } diff --git a/inbound/tun.go b/inbound/tun.go index c86273d8..e82ea122 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -166,6 +166,14 @@ func (t *Tun) Start() error { } t.logger.Trace("creating stack") 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{ Context: t.ctx, Tun: tunInterface, @@ -174,8 +182,9 @@ func (t *Tun) Start() error { UDPTimeout: t.udpTimeout, Handler: t, Logger: t.logger, - ForwarderBindInterface: t.platformInterface != nil, + ForwarderBindInterface: forwarderBindInterface, InterfaceFinder: t.router.InterfaceFinder(), + IncludeAllNetworks: includeAllNetworks, }) if err != nil { return err From 8a9a77a4382e5eb1a628d2d66a85afb072babafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 19 Feb 2024 00:19:33 +0800 Subject: [PATCH 24/30] Add `bypass_domain` and `search_domain` platform HTTP proxy options --- docs/changelog.md | 4 +- docs/configuration/dns/rule.md | 4 +- docs/configuration/dns/rule.zh.md | 4 +- docs/configuration/inbound/http.md | 2 +- docs/configuration/inbound/mixed.md | 2 +- docs/configuration/inbound/tun.md | 45 +++++++++++++++++++- docs/configuration/inbound/tun.zh.md | 45 +++++++++++++++++++- docs/configuration/route/rule.md | 4 +- docs/configuration/route/rule.zh.md | 4 +- docs/configuration/rule-set/headless-rule.md | 4 +- experimental/libbox/tun.go | 10 +++++ option/tun_platform.go | 2 + 12 files changed, 114 insertions(+), 16 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 9a6d0ef1..d3b19a4b 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -395,7 +395,7 @@ see [TCP Brutal](/configuration/shared/tcp-brutal/) for details. **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 @@ -432,7 +432,7 @@ Only supported in graphical clients on Android and iOS. **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 diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 22b5d872..4c4abacb 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -287,7 +287,7 @@ Match Clash mode. !!! quote "" - Only supported in graphical clients on Android and iOS. + Only supported in graphical clients on Android and Apple platforms. Match WiFi SSID. @@ -295,7 +295,7 @@ Match WiFi SSID. !!! quote "" - Only supported in graphical clients on Android and iOS. + Only supported in graphical clients on Android and Apple platforms. Match WiFi BSSID. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 9b77bd17..796d29e8 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -285,7 +285,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 !!! quote "" - 仅在 Android 与 iOS 的图形客户端中支持。 + 仅在 Android 与 Apple 平台图形客户端中支持。 匹配 WiFi SSID。 @@ -293,7 +293,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 !!! quote "" - 仅在 Android 与 iOS 的图形客户端中支持。 + 仅在 Android 与 Apple 平台图形客户端中支持。 匹配 WiFi BSSID。 diff --git a/docs/configuration/inbound/http.md b/docs/configuration/inbound/http.md index cd2ec35d..00343e22 100644 --- a/docs/configuration/inbound/http.md +++ b/docs/configuration/inbound/http.md @@ -42,6 +42,6 @@ No authentication required if empty. !!! 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. diff --git a/docs/configuration/inbound/mixed.md b/docs/configuration/inbound/mixed.md index 1f5bf0ac..e9deec75 100644 --- a/docs/configuration/inbound/mixed.md +++ b/docs/configuration/inbound/mixed.md @@ -39,6 +39,6 @@ No authentication required if empty. !!! 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. diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 2eed4553..1d5d8d0f 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -1,3 +1,12 @@ +--- +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" :material-plus: [gso](#gso) @@ -69,7 +78,9 @@ "http_proxy": { "enabled": false, "server": "127.0.0.1", - "server_port": 8080 + "server_port": 8080, + "bypass_domain": [], + "match_domain": [] } }, @@ -256,6 +267,38 @@ Platform-specific settings, provided by client applications. 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 See [Listen Fields](/configuration/shared/listen/) for details. diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 05c7c314..73d31d64 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -1,3 +1,12 @@ +--- +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 中的更改" :material-plus: [gso](#gso) @@ -69,7 +78,9 @@ "http_proxy": { "enabled": false, "server": "127.0.0.1", - "server_port": 8080 + "server_port": 8080, + "bypass_domain": [], + "match_domain": [] } }, @@ -253,6 +264,38 @@ TCP/IP 栈。 系统 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/)。 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index be9ee4cc..62d33c6c 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -281,7 +281,7 @@ Match Clash mode. !!! quote "" - Only supported in graphical clients on Android and iOS. + Only supported in graphical clients on Android and Apple platforms. Match WiFi SSID. @@ -289,7 +289,7 @@ Match WiFi SSID. !!! quote "" - Only supported in graphical clients on Android and iOS. + Only supported in graphical clients on Android and Apple platforms. Match WiFi BSSID. diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 881f97b0..cba35bc5 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -279,7 +279,7 @@ !!! quote "" - 仅在 Android 与 iOS 的图形客户端中支持。 + 仅在 Android 与 Apple 平台图形客户端中支持。 匹配 WiFi SSID。 @@ -287,7 +287,7 @@ !!! quote "" - 仅在 Android 与 iOS 的图形客户端中支持。 + 仅在 Android 与 Apple 平台图形客户端中支持。 匹配 WiFi BSSID。 diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index 9109841f..e766904b 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -168,7 +168,7 @@ Match android package name. !!! quote "" - Only supported in graphical clients on Android and iOS. + Only supported in graphical clients on Android and Apple platforms. Match WiFi SSID. @@ -176,7 +176,7 @@ Match WiFi SSID. !!! quote "" - Only supported in graphical clients on Android and iOS. + Only supported in graphical clients on Android and Apple platforms. Match WiFi BSSID. diff --git a/experimental/libbox/tun.go b/experimental/libbox/tun.go index 53add3ce..5c6e3370 100644 --- a/experimental/libbox/tun.go +++ b/experimental/libbox/tun.go @@ -28,6 +28,8 @@ type TunOptions interface { IsHTTPProxyEnabled() bool GetHTTPProxyServer() string GetHTTPProxyServerPort() int32 + GetHTTPProxyBypassDomain() StringIterator + GetHTTPProxyMatchDomain() StringIterator } type RoutePrefix struct { @@ -156,3 +158,11 @@ func (o *tunOptions) GetHTTPProxyServer() string { func (o *tunOptions) GetHTTPProxyServerPort() int32 { 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) +} diff --git a/option/tun_platform.go b/option/tun_platform.go index 873d788a..a0a54eed 100644 --- a/option/tun_platform.go +++ b/option/tun_platform.go @@ -7,4 +7,6 @@ type TunPlatformOptions struct { type HTTPProxyOptions struct { Enabled bool `json:"enabled,omitempty"` ServerOptions + BypassDomain Listable[string] `json:"bypass_domain,omitempty"` + MatchDomain Listable[string] `json:"match_domain,omitempty"` } From d612620c5d4302623e7a5228cbc89c4b049a01c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 24 Mar 2024 19:23:11 +0800 Subject: [PATCH 25/30] Add `rule-set match` command --- adapter/router.go | 2 +- cmd/sing-box/cmd_rule_set_match.go | 86 ++++++++++++++++++++++++++++++ route/rule_headless.go | 16 +++--- 3 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 cmd/sing-box/cmd_rule_set_match.go diff --git a/adapter/router.go b/adapter/router.go index 73849b97..786a777e 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -71,6 +71,7 @@ func RouterFromContext(ctx context.Context) Router { type HeadlessRule interface { Match(metadata *InboundContext) bool + String() string } type Rule interface { @@ -79,7 +80,6 @@ type Rule interface { Type() string UpdateGeosite() error Outbound() string - String() string } type DNSRule interface { diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go new file mode 100644 index 00000000..473c8222 --- /dev/null +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -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 ", + 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 +} diff --git a/route/rule_headless.go b/route/rule_headless.go index 92b6720c..67ac3a1e 100644 --- a/route/rule_headless.go +++ b/route/rule_headless.go @@ -129,14 +129,18 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles rule.allItems = append(rule.allItems, item) } if len(options.WIFISSID) > 0 { - item := NewWIFISSIDItem(router, options.WIFISSID) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) + if router != nil { + item := NewWIFISSIDItem(router, options.WIFISSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } } if len(options.WIFIBSSID) > 0 { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) + if router != nil { + item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } } return rule, nil } From 7d4e6a7f4edec653688dd33db12fbda93aa716ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 25 Apr 2024 22:16:13 +0800 Subject: [PATCH 26/30] dialer: Allow nil router --- common/dialer/default.go | 14 ++++++++++---- common/dialer/dialer.go | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index 91af85c5..4fbad07d 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -32,14 +32,20 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi var dialer net.Dialer var listener net.ListenConfig 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) listener.Control = control.Append(listener.Control, bindFunc) - } else if router.AutoDetectInterface() { + } else if router != nil && router.AutoDetectInterface() { bindFunc := router.AutoDetectInterfaceFunc() dialer.Control = control.Append(dialer.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) dialer.Control = control.Append(dialer.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 { dialer.Control = control.Append(dialer.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())) listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark())) } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index bbb4b3a9..a1721b28 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -13,6 +13,9 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) if options.IsWireGuardListener { return NewDefault(router, options) } + if router == nil { + return NewDefault(nil, options) + } var ( dialer N.Dialer err error From 65c71049eaae69de253e939681148f3521cd6b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 15 Mar 2024 17:15:26 +0800 Subject: [PATCH 27/30] documentation: Update DNS manual --- docs/manual/proxy/client.md | 59 ++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/docs/manual/proxy/client.md b/docs/manual/proxy/client.md index 7a65248f..1cf5a1ce 100644 --- a/docs/manual/proxy/client.md +++ b/docs/manual/proxy/client.md @@ -336,10 +336,10 @@ flowchart TB } ``` -=== ":material-dns: DNS rules (1.9.0+)" +=== ":material-dns: DNS rules (Enhanced, but slower) (1.9.0+)" + + === ":material-shield-off: With DNS leaks" - === ":material-shield-off: With DNS Leaks" - ```json { "dns": { @@ -376,7 +376,17 @@ flowchart TB "server": "google" }, { - "rule_set": "geoip-cn", + "type": "logical", + "mode": "and", + "rules": [ + { + "rule_set": "geosite-geolocation-!cn", + "invert": true + }, + { + "rule_set": "geoip-cn" + } + ], "server": "local" } ] @@ -389,6 +399,12 @@ flowchart TB "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", @@ -398,14 +414,18 @@ flowchart TB ] }, "experimental": { + "cache_file": { + "enabled": true, + "store_rdrc": true + }, "clash_api": { - "default_mode": "Leak" + "default_mode": "Enhanced" } } } ``` - === ":material-security: Without DNS Leaks (1.9.0-alpha.2+)" + === ":material-security: Without DNS leaks, but slower (1.9.0-alpha.2+)" ```json { @@ -439,7 +459,17 @@ flowchart TB "server": "local" }, { - "rule_set": "geoip-cn", + "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 } @@ -453,6 +483,12 @@ flowchart TB "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", @@ -460,6 +496,15 @@ flowchart TB "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" + } } } ``` From 9ffdbba2ed65b7badf882075b521bdc827b2a9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 7 May 2024 21:44:31 +0800 Subject: [PATCH 28/30] documentation: Add manuel for mitigating tunnelvision attacks --- docs/manual/misc/tunnelvision.md | 38 ++++++ docs/manual/proxy-protocol/tuic.md | 208 ----------------------------- mkdocs.yml | 3 +- 3 files changed, 40 insertions(+), 209 deletions(-) create mode 100644 docs/manual/misc/tunnelvision.md delete mode 100644 docs/manual/proxy-protocol/tuic.md diff --git a/docs/manual/misc/tunnelvision.md b/docs/manual/misc/tunnelvision.md new file mode 100644 index 00000000..0d6caf76 --- /dev/null +++ b/docs/manual/misc/tunnelvision.md @@ -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 diff --git a/docs/manual/proxy-protocol/tuic.md b/docs/manual/proxy-protocol/tuic.md deleted file mode 100644 index a2e01d88..00000000 --- a/docs/manual/proxy-protocol/tuic.md +++ /dev/null @@ -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 | -|------------------------|----------------------------|-----------------------------------------------------------------| -| | | | - - - -## :material-server: Server Example - -=== ":material-harddisk: With local certificate" - - ```json - { - "inbounds": [ - { - "type": "tuic", - "listen": "::", - "listen_port": 8080, - "users": [ - { - "name": "sekai", - "uuid": "", - "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": "", - "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": "", - "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": "", - "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": "", - "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": "", - "password": "", - "congestion_control": "bbr", - "tls": { - "enabled": true, - "server_name": "example.org", - "insecure": true - } - } - ] - } - ``` - diff --git a/mkdocs.yml b/mkdocs.yml index 877d73c4..d5218f4d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,8 +66,9 @@ nav: - Proxy Protocol: - Shadowsocks: manual/proxy-protocol/shadowsocks.md - Trojan: manual/proxy-protocol/trojan.md - - TUIC: manual/proxy-protocol/tuic.md - Hysteria 2: manual/proxy-protocol/hysteria2.md + - Misc: + - TunnelVision: manual/misc/tunnelvision.md - Configuration: - configuration/index.md - Log: From a89107ea9dc9863acfdf8e5a560e8d26332a8fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 26 Apr 2024 20:37:25 +0800 Subject: [PATCH 29/30] documentation: Bump version --- docs/changelog.md | 174 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index d3b19a4b..6ecfa745 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,16 +2,57 @@ icon: material/alert-decagram --- +#### 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 * Fix hysteria2 panic * 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 * Fix fake-ip mapping * 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 * Now we have official APT and DNF repositories **1** @@ -22,6 +63,10 @@ icon: material/alert-decagram 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 * Fixes and improvements @@ -30,6 +75,24 @@ Including stable and beta versions, see https://sing-box.sagernet.org/installati * 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 * Fixes and improvements @@ -38,14 +101,125 @@ Including stable and beta versions, see https://sing-box.sagernet.org/installati * 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 * 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 * 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 * Fixes and improvements From 5ff7006326e8a876d33d92b26ebd2671cdd48b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 25 May 2024 10:08:18 +0800 Subject: [PATCH 30/30] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 76 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/clients/android b/clients/android index 9d463b26..e20fa632 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 9d463b269a2af428e5c3d0e94bab260f45721fab +Subproject commit e20fa632f6fd89f7e9411ebab21fbce52b89120e diff --git a/clients/apple b/clients/apple index 17eec086..0cbe335c 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 17eec0861543489ad4519eef974c65d2a159244d +Subproject commit 0cbe335cbbaef9747171c44b169c23daf2d89261 diff --git a/docs/changelog.md b/docs/changelog.md index 6ecfa745..1b7ab6ad 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,68 @@ icon: material/alert-decagram --- +### 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 @@ -236,7 +298,7 @@ See [Address Filter Fields](/configuration/dns/rule#address-filter-fields). * Fixes and improvements -#### 1.8.0 +### 1.8.0 * Fixes and improvements @@ -527,7 +589,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. -#### 1.7.0 +### 1.7.0 * Fixes and improvements @@ -687,7 +749,7 @@ Introduced in V2Ray 5.10.0. 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 @@ -866,7 +928,7 @@ introduce new issues. 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. -#### 1.5.0 +### 1.5.0 * Fixes and improvements @@ -1060,7 +1122,7 @@ All inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria`, `T * Fixes and improvements -#### 1.4.0 +### 1.4.0 * Fix bugs and update dependencies @@ -1202,7 +1264,7 @@ The old testflight link and app are no longer valid. * Fixes and improvements -#### 1.3.0 +### 1.3.0 * Fix bugs and update dependencies @@ -1394,7 +1456,7 @@ to `domain` rule. * Flush DNS cache for macOS when tun start/close * Fix tun's DNS hijacking compatibility with systemd-resolved -#### 1.2.0 +### 1.2.0 * Fix bugs and update dependencies