世界 2 роки тому
батько
коміт
12dd1ac87f

+ 1 - 1
common/dialer/conntrack/conn.go → common/conntrack/conn.go

@@ -17,7 +17,7 @@ func NewConn(conn net.Conn) (net.Conn, error) {
 	element := openConnection.PushBack(conn)
 	connAccess.Unlock()
 	if KillerEnabled {
-		err := killerCheck()
+		err := KillerCheck()
 		if err != nil {
 			conn.Close()
 			return nil, err

+ 4 - 7
common/dialer/conntrack/killer.go → common/conntrack/killer.go

@@ -1,20 +1,20 @@
 package conntrack
 
 import (
-	"runtime"
 	runtimeDebug "runtime/debug"
 	"time"
 
 	E "github.com/sagernet/sing/common/exceptions"
+	"github.com/sagernet/sing/common/memory"
 )
 
 var (
 	KillerEnabled   bool
-	MemoryLimit     int64
+	MemoryLimit     uint64
 	killerLastCheck time.Time
 )
 
-func killerCheck() error {
+func KillerCheck() error {
 	if !KillerEnabled {
 		return nil
 	}
@@ -23,10 +23,7 @@ func killerCheck() error {
 		return nil
 	}
 	killerLastCheck = nowTime
-	var memStats runtime.MemStats
-	runtime.ReadMemStats(&memStats)
-	inuseMemory := int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
-	if inuseMemory > MemoryLimit {
+	if memory.Total() > MemoryLimit {
 		Close()
 		go func() {
 			time.Sleep(time.Second)

+ 1 - 1
common/dialer/conntrack/packet_conn.go → common/conntrack/packet_conn.go

@@ -18,7 +18,7 @@ func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
 	element := openConnection.PushBack(conn)
 	connAccess.Unlock()
 	if KillerEnabled {
-		err := killerCheck()
+		err := KillerCheck()
 		if err != nil {
 			conn.Close()
 			return nil, err

+ 0 - 0
common/dialer/conntrack/track.go → common/conntrack/track.go


+ 0 - 0
common/dialer/conntrack/track_disable.go → common/conntrack/track_disable.go


+ 0 - 0
common/dialer/conntrack/track_enable.go → common/conntrack/track_enable.go


+ 1 - 1
common/dialer/default.go

@@ -6,7 +6,7 @@ import (
 	"time"
 
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/common/dialer/conntrack"
+	"github.com/sagernet/sing-box/common/conntrack"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common/control"

+ 158 - 0
common/humanize/bytes.go

@@ -0,0 +1,158 @@
+package humanize
+
+import (
+	"fmt"
+	"math"
+	"strconv"
+	"strings"
+	"unicode"
+)
+
+// IEC Sizes.
+// kibis of bits
+const (
+	Byte = 1 << (iota * 10)
+	KiByte
+	MiByte
+	GiByte
+	TiByte
+	PiByte
+	EiByte
+)
+
+// SI Sizes.
+const (
+	IByte = 1
+	KByte = IByte * 1000
+	MByte = KByte * 1000
+	GByte = MByte * 1000
+	TByte = GByte * 1000
+	PByte = TByte * 1000
+	EByte = PByte * 1000
+)
+
+var defaultSizeTable = map[string]uint64{
+	"b":   Byte,
+	"kib": KiByte,
+	"kb":  KByte,
+	"mib": MiByte,
+	"mb":  MByte,
+	"gib": GiByte,
+	"gb":  GByte,
+	"tib": TiByte,
+	"tb":  TByte,
+	"pib": PiByte,
+	"pb":  PByte,
+	"eib": EiByte,
+	"eb":  EByte,
+	// Without suffix
+	"":   Byte,
+	"ki": KiByte,
+	"k":  KByte,
+	"mi": MiByte,
+	"m":  MByte,
+	"gi": GiByte,
+	"g":  GByte,
+	"ti": TiByte,
+	"t":  TByte,
+	"pi": PiByte,
+	"p":  PByte,
+	"ei": EiByte,
+	"e":  EByte,
+}
+
+var memorysSizeTable = map[string]uint64{
+	"b":  Byte,
+	"kb": KiByte,
+	"mb": MiByte,
+	"gb": GiByte,
+	"tb": TiByte,
+	"pb": PiByte,
+	"eb": EiByte,
+	"":   Byte,
+	"k":  KiByte,
+	"m":  MiByte,
+	"g":  GiByte,
+	"t":  TiByte,
+	"p":  PiByte,
+	"e":  EiByte,
+}
+
+var (
+	defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
+	iSizes       = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
+)
+
+func Bytes(s uint64) string {
+	return humanateBytes(s, 1000, defaultSizes)
+}
+
+func MemoryBytes(s uint64) string {
+	return humanateBytes(s, 1024, defaultSizes)
+}
+
+func IBytes(s uint64) string {
+	return humanateBytes(s, 1024, iSizes)
+}
+
+func logn(n, b float64) float64 {
+	return math.Log(n) / math.Log(b)
+}
+
+func humanateBytes(s uint64, base float64, sizes []string) string {
+	if s < 10 {
+		return fmt.Sprintf("%d B", s)
+	}
+	e := math.Floor(logn(float64(s), base))
+	suffix := sizes[int(e)]
+	val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
+	f := "%.0f %s"
+	if val < 10 {
+		f = "%.1f %s"
+	}
+
+	return fmt.Sprintf(f, val, suffix)
+}
+
+func ParseBytes(s string) (uint64, error) {
+	return parseBytes0(s, defaultSizeTable)
+}
+
+func ParseMemoryBytes(s string) (uint64, error) {
+	return parseBytes0(s, memorysSizeTable)
+}
+
+func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
+	lastDigit := 0
+	hasComma := false
+	for _, r := range s {
+		if !(unicode.IsDigit(r) || r == '.' || r == ',') {
+			break
+		}
+		if r == ',' {
+			hasComma = true
+		}
+		lastDigit++
+	}
+
+	num := s[:lastDigit]
+	if hasComma {
+		num = strings.Replace(num, ",", "", -1)
+	}
+
+	f, err := strconv.ParseFloat(num, 64)
+	if err != nil {
+		return 0, err
+	}
+
+	extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
+	if m, ok := sizeTable[extra]; ok {
+		f *= float64(m)
+		if f >= math.MaxUint64 {
+			return 0, fmt.Errorf("too large: %v", s)
+		}
+		return uint64(f), nil
+	}
+
+	return 0, fmt.Errorf("unhandled size name: %v", extra)
+}

+ 2 - 2
debug_go118.go

@@ -5,7 +5,7 @@ package box
 import (
 	"runtime/debug"
 
-	"github.com/sagernet/sing-box/common/dialer/conntrack"
+	"github.com/sagernet/sing-box/common/conntrack"
 	"github.com/sagernet/sing-box/option"
 )
 
@@ -28,7 +28,7 @@ func applyDebugOptions(options option.DebugOptions) {
 	}
 	if options.MemoryLimit != 0 {
 		// debug.SetMemoryLimit(int64(options.MemoryLimit))
-		conntrack.MemoryLimit = int64(options.MemoryLimit)
+		conntrack.MemoryLimit = uint64(options.MemoryLimit)
 	}
 	if options.OOMKiller != nil {
 		conntrack.KillerEnabled = *options.OOMKiller

+ 2 - 2
debug_go119.go

@@ -5,7 +5,7 @@ package box
 import (
 	"runtime/debug"
 
-	"github.com/sagernet/sing-box/common/dialer/conntrack"
+	"github.com/sagernet/sing-box/common/conntrack"
 	"github.com/sagernet/sing-box/option"
 )
 
@@ -28,7 +28,7 @@ func applyDebugOptions(options option.DebugOptions) {
 	}
 	if options.MemoryLimit != 0 {
 		debug.SetMemoryLimit(int64(options.MemoryLimit))
-		conntrack.MemoryLimit = int64(options.MemoryLimit)
+		conntrack.MemoryLimit = uint64(options.MemoryLimit)
 	}
 	if options.OOMKiller != nil {
 		conntrack.KillerEnabled = *options.OOMKiller

+ 4 - 4
debug_http.go

@@ -7,12 +7,12 @@ import (
 	"runtime/debug"
 
 	"github.com/sagernet/sing-box/common/badjson"
+	"github.com/sagernet/sing-box/common/humanize"
 	"github.com/sagernet/sing-box/common/json"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
 	E "github.com/sagernet/sing/common/exceptions"
 
-	"github.com/dustin/go-humanize"
 	"github.com/go-chi/chi/v5"
 )
 
@@ -37,9 +37,9 @@ func applyDebugListenOption(options option.DebugOptions) {
 			runtime.ReadMemStats(&memStats)
 
 			var memObject badjson.JSONObject
-			memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
-			memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
-			memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
+			memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
+			memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
+			memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
 			memObject.Put("goroutines", runtime.NumGoroutine())
 			memObject.Put("rss", rusageMaxRSS())
 

+ 1 - 1
experimental/libbox/command_conntrack.go

@@ -6,7 +6,7 @@ import (
 	runtimeDebug "runtime/debug"
 	"time"
 
-	"github.com/sagernet/sing-box/common/dialer/conntrack"
+	"github.com/sagernet/sing-box/common/conntrack"
 )
 
 func (c *CommandClient) CloseConnections() error {

+ 4 - 4
experimental/libbox/command_status.go

@@ -6,13 +6,15 @@ import (
 	"runtime"
 	"time"
 
-	"github.com/sagernet/sing-box/common/dialer/conntrack"
+	"github.com/sagernet/sing-box/common/conntrack"
 	"github.com/sagernet/sing-box/experimental/clashapi"
 	E "github.com/sagernet/sing/common/exceptions"
+	"github.com/sagernet/sing/common/memory"
 )
 
 type StatusMessage struct {
 	Memory           int64
+	MemoryInuse      int64
 	Goroutines       int32
 	ConnectionsIn    int32
 	ConnectionsOut   int32
@@ -24,10 +26,8 @@ type StatusMessage struct {
 }
 
 func (s *CommandServer) readStatus() StatusMessage {
-	var memStats runtime.MemStats
-	runtime.ReadMemStats(&memStats)
 	var message StatusMessage
-	message.Memory = int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
+	message.Memory = int64(memory.Inuse())
 	message.Goroutines = int32(runtime.NumGoroutine())
 	message.ConnectionsOut = int32(conntrack.Count())
 

+ 4 - 3
experimental/libbox/memory.go

@@ -4,14 +4,15 @@ import (
 	"math"
 	runtimeDebug "runtime/debug"
 
-	"github.com/sagernet/sing-box/common/dialer/conntrack"
+	"github.com/sagernet/sing-box/common/conntrack"
 )
 
 func SetMemoryLimit(enabled bool) {
-	const memoryLimit = 30 * 1024 * 1024
+	const memoryLimit = 45 * 1024 * 1024
+	const memoryLimitGo = memoryLimit / 1.5
 	if enabled {
 		runtimeDebug.SetGCPercent(10)
-		runtimeDebug.SetMemoryLimit(memoryLimit)
+		runtimeDebug.SetMemoryLimit(memoryLimitGo)
 		conntrack.KillerEnabled = true
 		conntrack.MemoryLimit = memoryLimit
 	} else {

+ 6 - 3
experimental/libbox/setup.go

@@ -5,9 +5,8 @@ import (
 	"os/user"
 	"strconv"
 
+	"github.com/sagernet/sing-box/common/humanize"
 	C "github.com/sagernet/sing-box/constant"
-
-	"github.com/dustin/go-humanize"
 )
 
 var (
@@ -46,7 +45,11 @@ func Version() string {
 }
 
 func FormatBytes(length int64) string {
-	return humanize.IBytes(uint64(length))
+	return humanize.Bytes(uint64(length))
+}
+
+func FormatMemoryBytes(length int64) string {
+	return humanize.MemoryBytes(uint64(length))
 }
 
 func ProxyDisplayType(proxyType string) string {

+ 2 - 4
go.mod

@@ -8,7 +8,6 @@ require (
 	github.com/caddyserver/certmagic v0.19.2
 	github.com/cloudflare/circl v1.3.3
 	github.com/cretz/bine v0.2.0
-	github.com/dustin/go-humanize v1.0.1
 	github.com/fsnotify/fsnotify v1.6.0
 	github.com/go-chi/chi/v5 v5.0.10
 	github.com/go-chi/cors v1.2.1
@@ -28,14 +27,14 @@ require (
 	github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
 	github.com/sagernet/quic-go v0.0.0-20230919101909-0cc6c5dcecee
 	github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
-	github.com/sagernet/sing v0.2.10-0.20230912050851-1453c7c8c20d
+	github.com/sagernet/sing v0.2.10-0.20230920060554-3c4a2b06a988
 	github.com/sagernet/sing-dns v0.1.9-0.20230919110447-d24aeae07601
 	github.com/sagernet/sing-mux v0.1.3-0.20230908032617-759a1886a400
 	github.com/sagernet/sing-quic v0.0.0-20230919102644-5874c56aae1c
 	github.com/sagernet/sing-shadowsocks v0.2.5-0.20230907005610-126234728ca0
 	github.com/sagernet/sing-shadowsocks2 v0.1.4-0.20230907005906-5d2917b29248
 	github.com/sagernet/sing-shadowtls v0.1.4
-	github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641
+	github.com/sagernet/sing-tun v0.1.12-0.20230920060816-9c933ea55308
 	github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b
 	github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
 	github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
@@ -58,7 +57,6 @@ require (
 )
 
 //replace github.com/sagernet/sing => ../sing
-
 require (
 	github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158 // indirect
 	github.com/ajg/form v1.5.1 // indirect

+ 4 - 6
go.sum

@@ -22,8 +22,6 @@ github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbe
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
-github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
@@ -116,8 +114,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
 github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
-github.com/sagernet/sing v0.2.10-0.20230912050851-1453c7c8c20d h1:dWUNsHDX8EMeGj1XCnrVtydy5C+5D+Orc5JjZP7myHg=
-github.com/sagernet/sing v0.2.10-0.20230912050851-1453c7c8c20d/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
+github.com/sagernet/sing v0.2.10-0.20230920060554-3c4a2b06a988 h1:Z2jW5Y03JBDgXHkCoxlO4+kDfHKMKjVVV8wu8BmNKEM=
+github.com/sagernet/sing v0.2.10-0.20230920060554-3c4a2b06a988/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
 github.com/sagernet/sing-dns v0.1.9-0.20230919110447-d24aeae07601 h1:UJYRkncWVpNdNIXeJV/eBilTc3Rga9G1SDBWjKSxL4w=
 github.com/sagernet/sing-dns v0.1.9-0.20230919110447-d24aeae07601/go.mod h1:Kg98PBJEg/08jsNFtmZWmPomhskn9Ausn50ecNm4M+8=
 github.com/sagernet/sing-mux v0.1.3-0.20230908032617-759a1886a400 h1:LtpYd5c5AJtUSxjyH4KjUS8HT+2XgilyozjbCq/x3EM=
@@ -130,8 +128,8 @@ github.com/sagernet/sing-shadowsocks2 v0.1.4-0.20230907005906-5d2917b29248 h1:JT
 github.com/sagernet/sing-shadowsocks2 v0.1.4-0.20230907005906-5d2917b29248/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw=
 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.1.12-0.20230821065522-7545dc2d5641 h1:a8lktNrCWZJisB+nPraW+qB73ZofgPtGmlfqNYcO79g=
-github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
+github.com/sagernet/sing-tun v0.1.12-0.20230920060816-9c933ea55308 h1:v94hR0DSnqvkk8GYOgN3UytXZGA2KSpkNrt3y3CBWsA=
+github.com/sagernet/sing-tun v0.1.12-0.20230920060816-9c933ea55308/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
 github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b h1:2ezfJtH5JosiEwJhVa+rimQ6ps/t2+7h+mOzMoiaZnA=
 github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss=
 github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=

+ 9 - 9
option/debug.go

@@ -3,7 +3,7 @@ package option
 import (
 	"encoding/json"
 
-	"github.com/dustin/go-humanize"
+	"github.com/sagernet/sing-box/common/humanize"
 )
 
 type DebugOptions struct {
@@ -13,21 +13,21 @@ type DebugOptions struct {
 	MaxThreads   *int        `json:"max_threads,omitempty"`
 	PanicOnFault *bool       `json:"panic_on_fault,omitempty"`
 	TraceBack    string      `json:"trace_back,omitempty"`
-	MemoryLimit  BytesLength `json:"memory_limit,omitempty"`
+	MemoryLimit  MemoryBytes `json:"memory_limit,omitempty"`
 	OOMKiller    *bool       `json:"oom_killer,omitempty"`
 }
 
-type BytesLength int64
+type MemoryBytes uint64
 
-func (l BytesLength) MarshalJSON() ([]byte, error) {
-	return json.Marshal(humanize.IBytes(uint64(l)))
+func (l MemoryBytes) MarshalJSON() ([]byte, error) {
+	return json.Marshal(humanize.MemoryBytes(uint64(l)))
 }
 
-func (l *BytesLength) UnmarshalJSON(bytes []byte) error {
+func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error {
 	var valueInteger int64
 	err := json.Unmarshal(bytes, &valueInteger)
 	if err == nil {
-		*l = BytesLength(valueInteger)
+		*l = MemoryBytes(valueInteger)
 		return nil
 	}
 	var valueString string
@@ -35,10 +35,10 @@ func (l *BytesLength) UnmarshalJSON(bytes []byte) error {
 	if err != nil {
 		return err
 	}
-	parsedValue, err := humanize.ParseBytes(valueString)
+	parsedValue, err := humanize.ParseMemoryBytes(valueString)
 	if err != nil {
 		return err
 	}
-	*l = BytesLength(parsedValue)
+	*l = MemoryBytes(parsedValue)
 	return nil
 }

+ 14 - 4
route/router.go

@@ -12,8 +12,8 @@ import (
 	"time"
 
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/conntrack"
 	"github.com/sagernet/sing-box/common/dialer"
-	"github.com/sagernet/sing-box/common/dialer/conntrack"
 	"github.com/sagernet/sing-box/common/geoip"
 	"github.com/sagernet/sing-box/common/geosite"
 	"github.com/sagernet/sing-box/common/mux"
@@ -602,6 +602,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
 		}
 		return nil
 	}
+	conntrack.KillerCheck()
 	metadata.Network = N.NetworkTCP
 	switch metadata.Destination.Fqdn {
 	case mux.Destination.Fqdn:
@@ -738,6 +739,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
 		}
 		return nil
 	}
+	conntrack.KillerCheck()
 	metadata.Network = N.NetworkUDP
 
 	if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
@@ -915,13 +917,21 @@ func (r *Router) AutoDetectInterfaceFunc() control.Func {
 	if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() {
 		return r.platformInterface.AutoDetectInterfaceControl()
 	} else {
-		return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
+		return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
 			remoteAddr := M.ParseSocksaddr(address).Addr
 			if C.IsLinux {
-				return r.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
+				interfaceName = r.InterfaceMonitor().DefaultInterfaceName(remoteAddr)
+				interfaceIndex = -1
+				if interfaceName == "" {
+					err = tun.ErrNoRoute
+				}
 			} else {
-				return "", r.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
+				interfaceIndex = r.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
+				if interfaceIndex == -1 {
+					err = tun.ErrNoRoute
+				}
 			}
+			return
 		})
 	}
 }

+ 2 - 2
test/go.mod

@@ -12,7 +12,7 @@ require (
 	github.com/docker/docker v24.0.5+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/gofrs/uuid/v5 v5.0.0
-	github.com/sagernet/sing v0.2.10-0.20230912050851-1453c7c8c20d
+	github.com/sagernet/sing v0.2.10-0.20230920060554-3c4a2b06a988
 	github.com/sagernet/sing-quic v0.0.0-20230919102644-5874c56aae1c
 	github.com/sagernet/sing-shadowsocks v0.2.5-0.20230907005610-126234728ca0
 	github.com/sagernet/sing-shadowsocks2 v0.1.4-0.20230907005906-5d2917b29248
@@ -79,7 +79,7 @@ require (
 	github.com/sagernet/sing-dns v0.1.9-0.20230919110447-d24aeae07601 // indirect
 	github.com/sagernet/sing-mux v0.1.3-0.20230908032617-759a1886a400 // indirect
 	github.com/sagernet/sing-shadowtls v0.1.4 // indirect
-	github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 // indirect
+	github.com/sagernet/sing-tun v0.1.12-0.20230920060816-9c933ea55308 // indirect
 	github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b // indirect
 	github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
 	github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect

+ 3 - 0
test/go.sum

@@ -133,6 +133,8 @@ github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2
 github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
 github.com/sagernet/sing v0.2.10-0.20230912050851-1453c7c8c20d h1:dWUNsHDX8EMeGj1XCnrVtydy5C+5D+Orc5JjZP7myHg=
 github.com/sagernet/sing v0.2.10-0.20230912050851-1453c7c8c20d/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
+github.com/sagernet/sing v0.2.10-0.20230920054954-313025d13dea/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
+github.com/sagernet/sing v0.2.10-0.20230920060554-3c4a2b06a988/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
 github.com/sagernet/sing-dns v0.1.9-0.20230919110447-d24aeae07601 h1:UJYRkncWVpNdNIXeJV/eBilTc3Rga9G1SDBWjKSxL4w=
 github.com/sagernet/sing-dns v0.1.9-0.20230919110447-d24aeae07601/go.mod h1:Kg98PBJEg/08jsNFtmZWmPomhskn9Ausn50ecNm4M+8=
 github.com/sagernet/sing-mux v0.1.3-0.20230908032617-759a1886a400 h1:LtpYd5c5AJtUSxjyH4KjUS8HT+2XgilyozjbCq/x3EM=
@@ -145,6 +147,7 @@ github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnV
 github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
 github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 h1:a8lktNrCWZJisB+nPraW+qB73ZofgPtGmlfqNYcO79g=
 github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
+github.com/sagernet/sing-tun v0.1.12-0.20230920060816-9c933ea55308/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
 github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b h1:2ezfJtH5JosiEwJhVa+rimQ6ps/t2+7h+mOzMoiaZnA=
 github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss=
 github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=

+ 1 - 3
transport/dhcp/server.go

@@ -174,9 +174,7 @@ func (t *Transport) interfaceUpdated(int) {
 
 func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error {
 	var listener net.ListenConfig
-	listener.Control = control.Append(listener.Control, control.BindToInterfaceFunc(t.router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
-		return iface.Name, iface.Index
-	}))
+	listener.Control = control.Append(listener.Control, control.BindToInterface(t.router.InterfaceFinder(), iface.Name, iface.Index))
 	listener.Control = control.Append(listener.Control, control.ReuseAddr())
 	packetConn, err := listener.ListenPacket(t.ctx, "udp4", "0.0.0.0:68")
 	if err != nil {