1
0
世界 3 жил өмнө
parent
commit
32e2730ec6

+ 44 - 2
common/dialer/default.go

@@ -3,10 +3,10 @@ package dialer
 import (
 	"context"
 	"net"
-	"runtime"
 	"time"
 
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/warning"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common"
@@ -16,6 +16,41 @@ import (
 	"github.com/database64128/tfo-go"
 )
 
+var warnBindInterfaceOnUnsupportedPlatform = warning.New(
+	func() bool {
+		return !(C.IsLinux || C.IsWindows)
+	},
+	"outbound option `bind_interface` is only supported on Linux and Windows",
+)
+
+var warnRoutingMarkOnUnsupportedPlatform = warning.New(
+	func() bool {
+		return !C.IsLinux
+	},
+	"outbound option `routing_mark` is only supported on Linux",
+)
+
+var warnReuseAdderOnUnsupportedPlatform = warning.New(
+	func() bool {
+		return !(C.IsDarwin || C.IsDragonfly || C.IsFreebsd || C.IsLinux || C.IsNetbsd || C.IsOpenbsd || C.IsSolaris || C.IsWindows)
+	},
+	"outbound option `reuse_addr` is unsupported on current platform",
+)
+
+var warnProtectPathOnNonAndroid = warning.New(
+	func() bool {
+		return !C.IsAndroid
+	},
+	"outbound option `protect_path` is only supported on Android",
+)
+
+var warnTFOOnUnsupportedPlatform = warning.New(
+	func() bool {
+		return !(C.IsDarwin || C.IsFreebsd || C.IsLinux || C.IsWindows)
+	},
+	"outbound option `tcp_fast_open` is unsupported on current platform",
+)
+
 type DefaultDialer struct {
 	tfo.Dialer
 	net.ListenConfig
@@ -25,10 +60,11 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
 	var dialer net.Dialer
 	var listener net.ListenConfig
 	if options.BindInterface != "" {
+		warnBindInterfaceOnUnsupportedPlatform.Check()
 		dialer.Control = control.Append(dialer.Control, control.BindToInterface(router.InterfaceBindManager(), options.BindInterface))
 		listener.Control = control.Append(listener.Control, control.BindToInterface(router.InterfaceBindManager(), options.BindInterface))
 	} else if router.AutoDetectInterface() {
-		if runtime.GOOS == "windows" {
+		if C.IsWindows {
 			dialer.Control = control.Append(dialer.Control, control.BindToInterfaceIndexFunc(func() int {
 				return router.AutoDetectInterfaceIndex()
 			}))
@@ -48,6 +84,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
 		listener.Control = control.Append(listener.Control, control.BindToInterface(router.InterfaceBindManager(), router.DefaultInterface()))
 	}
 	if options.RoutingMark != 0 {
+		warnRoutingMarkOnUnsupportedPlatform.Check()
 		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 {
@@ -55,9 +92,11 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
 		listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
 	}
 	if options.ReuseAddr {
+		warnReuseAdderOnUnsupportedPlatform.Check()
 		listener.Control = control.Append(listener.Control, control.ReuseAddr())
 	}
 	if options.ProtectPath != "" {
+		warnProtectPathOnNonAndroid.Check()
 		dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
 		listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
 	}
@@ -66,6 +105,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
 	} else {
 		dialer.Timeout = C.DefaultTCPTimeout
 	}
+	if options.TCPFastOpen {
+		warnTFOOnUnsupportedPlatform.Check()
+	}
 	return &DefaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, listener}
 }
 

+ 31 - 0
common/warning/warning.go

@@ -0,0 +1,31 @@
+package warning
+
+import (
+	"sync"
+
+	"github.com/sagernet/sing-box/log"
+)
+
+type Warning struct {
+	logger    log.Logger
+	check     CheckFunc
+	message   string
+	checkOnce sync.Once
+}
+
+type CheckFunc = func() bool
+
+func New(checkFunc CheckFunc, message string) Warning {
+	return Warning{
+		check:   checkFunc,
+		message: message,
+	}
+}
+
+func (w *Warning) Check() {
+	w.checkOnce.Do(func() {
+		if w.check() {
+			log.Warn(w.message)
+		}
+	})
+}

+ 68 - 0
constant/goos/gengoos.go

@@ -0,0 +1,68 @@
+// 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.
+
+//go:build ignore
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"log"
+	"os"
+	"strconv"
+	"strings"
+)
+
+var gooses []string
+
+func main() {
+	data, err := os.ReadFile("../../go/build/syslist.go")
+	if err != nil {
+		log.Fatal(err)
+	}
+	const goosPrefix = `const goosList = `
+	for _, line := range strings.Split(string(data), "\n") {
+		if strings.HasPrefix(line, goosPrefix) {
+			text, err := strconv.Unquote(strings.TrimPrefix(line, goosPrefix))
+			if err != nil {
+				log.Fatalf("parsing goosList: %v", err)
+			}
+			gooses = strings.Fields(text)
+		}
+	}
+
+	for _, target := range gooses {
+		if target == "nacl" {
+			continue
+		}
+		var tags []string
+		if target == "linux" {
+			tags = append(tags, "!android") // must explicitly exclude android for linux
+		}
+		if target == "solaris" {
+			tags = append(tags, "!illumos") // must explicitly exclude illumos for solaris
+		}
+		if target == "darwin" {
+			tags = append(tags, "!ios") // must explicitly exclude ios for darwin
+		}
+		tags = append(tags, target) // must explicitly include target for bootstrapping purposes
+		var buf bytes.Buffer
+		fmt.Fprintf(&buf, "// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.\n\n")
+		fmt.Fprintf(&buf, "//go:build %s\n", strings.Join(tags, " && "))
+		fmt.Fprintf(&buf, "package goos\n\n")
+		fmt.Fprintf(&buf, "const GOOS = `%s`\n\n", target)
+		for _, goos := range gooses {
+			value := 0
+			if goos == target {
+				value = 1
+			}
+			fmt.Fprintf(&buf, "const Is%s = %d\n", strings.Title(goos), value)
+		}
+		err := os.WriteFile("zgoos_"+target+".go", buf.Bytes(), 0o666)
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+}

+ 12 - 0
constant/goos/goos.go

@@ -0,0 +1,12 @@
+// Copyright 2015 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.
+
+// package goos contains GOOS-specific constants.
+package goos
+
+// The next line makes 'go generate' write the zgoos*.go files with
+// per-OS information, including constants named Is$GOOS for every
+// known GOOS. The constant is 1 on the current system, 0 otherwise;
+// multiplying by them is useful for defining GOOS-specific constants.
+//go:generate go run gengoos.go

+ 25 - 0
constant/goos/zgoos_aix.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build aix
+
+package goos
+
+const GOOS = `aix`
+
+const IsAix = 1
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_android.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build android
+
+package goos
+
+const GOOS = `android`
+
+const IsAix = 0
+const IsAndroid = 1
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_darwin.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build !ios && darwin
+
+package goos
+
+const GOOS = `darwin`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 1
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_dragonfly.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build dragonfly
+
+package goos
+
+const GOOS = `dragonfly`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 1
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_freebsd.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build freebsd
+
+package goos
+
+const GOOS = `freebsd`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 1
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_hurd.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build hurd
+
+package goos
+
+const GOOS = `hurd`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 1
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_illumos.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build illumos
+
+package goos
+
+const GOOS = `illumos`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 1
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_ios.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build ios
+
+package goos
+
+const GOOS = `ios`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 1
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_js.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build js
+
+package goos
+
+const GOOS = `js`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 1
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_linux.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build !android && linux
+
+package goos
+
+const GOOS = `linux`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 1
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_netbsd.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build netbsd
+
+package goos
+
+const GOOS = `netbsd`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 1
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_openbsd.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build openbsd
+
+package goos
+
+const GOOS = `openbsd`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 1
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_plan9.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build plan9
+
+package goos
+
+const GOOS = `plan9`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 1
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_solaris.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build !illumos && solaris
+
+package goos
+
+const GOOS = `solaris`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 1
+const IsWindows = 0
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_windows.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build windows
+
+package goos
+
+const GOOS = `windows`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 1
+const IsZos = 0

+ 25 - 0
constant/goos/zgoos_zos.go

@@ -0,0 +1,25 @@
+// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
+
+//go:build zos
+
+package goos
+
+const GOOS = `zos`
+
+const IsAix = 0
+const IsAndroid = 0
+const IsDarwin = 0
+const IsDragonfly = 0
+const IsFreebsd = 0
+const IsHurd = 0
+const IsIllumos = 0
+const IsIos = 0
+const IsJs = 0
+const IsLinux = 0
+const IsNacl = 0
+const IsNetbsd = 0
+const IsOpenbsd = 0
+const IsPlan9 = 0
+const IsSolaris = 0
+const IsWindows = 0
+const IsZos = 1

+ 37 - 0
constant/os.go

@@ -0,0 +1,37 @@
+package constant
+
+import (
+	"github.com/sagernet/sing-box/constant/goos"
+)
+
+const IsAndroid = goos.IsAndroid == 1
+
+const IsDarwin = goos.IsDarwin == 1
+
+const IsDragonfly = goos.IsDragonfly == 1
+
+const IsFreebsd = goos.IsFreebsd == 1
+
+const IsHurd = goos.IsHurd == 1
+
+const IsIllumos = goos.IsIllumos == 1
+
+const IsIos = goos.IsIos == 1
+
+const IsJs = goos.IsJs == 1
+
+const IsLinux = goos.IsLinux == 1
+
+const IsNacl = goos.IsNacl == 1
+
+const IsNetbsd = goos.IsNetbsd == 1
+
+const IsOpenbsd = goos.IsOpenbsd == 1
+
+const IsPlan9 = goos.IsPlan9 == 1
+
+const IsSolaris = goos.IsSolaris == 1
+
+const IsWindows = goos.IsWindows == 1
+
+const IsZos = goos.IsZos == 1

+ 2 - 4
inbound/tun.go

@@ -6,7 +6,6 @@ import (
 	"context"
 	"net"
 	"net/netip"
-	"runtime"
 	"strconv"
 	"strings"
 
@@ -140,10 +139,9 @@ func (t *Tun) NewError(ctx context.Context, err error) {
 }
 
 func mkInterfaceName() (tunName string) {
-	switch runtime.GOOS {
-	case "darwin":
+	if C.IsDarwin {
 		tunName = "utun"
-	default:
+	} else {
 		tunName = "tun"
 	}
 	interfaces, err := net.Interfaces()

+ 39 - 3
route/router.go

@@ -21,6 +21,7 @@ import (
 	"github.com/sagernet/sing-box/common/process"
 	"github.com/sagernet/sing-box/common/sniff"
 	"github.com/sagernet/sing-box/common/urltest"
+	"github.com/sagernet/sing-box/common/warning"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
@@ -38,6 +39,27 @@ import (
 	"golang.org/x/net/dns/dnsmessage"
 )
 
+var warnDefaultInterfaceOnUnsupportedPlatform = warning.New(
+	func() bool {
+		return !(C.IsLinux || C.IsWindows)
+	},
+	"route option `default_mark` is only supported on Linux and Windows",
+)
+
+var warnDefaultMarkOnNonLinux = warning.New(
+	func() bool {
+		return !C.IsLinux
+	},
+	"route option `default_mark` is only supported on Linux",
+)
+
+var warnFindProcessOnUnsupportedPlatform = warning.New(
+	func() bool {
+		return !(C.IsLinux || C.IsWindows || C.IsDarwin)
+	},
+	"route option `find_process` is only supported on Linux, Windows, and Mac OS X",
+)
+
 var _ adapter.Router = (*Router)(nil)
 
 type Router struct {
@@ -75,6 +97,16 @@ type Router struct {
 }
 
 func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) {
+	if options.DefaultInterface != "" {
+		warnDefaultInterfaceOnUnsupportedPlatform.Check()
+	}
+	if options.DefaultMark != 0 {
+		warnDefaultMarkOnNonLinux.Check()
+	}
+	if options.FindProcess {
+		warnFindProcessOnUnsupportedPlatform.Check()
+	}
+
 	router := &Router{
 		ctx:                   ctx,
 		logger:                logger,
@@ -225,9 +257,12 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
 	if hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess {
 		searcher, err := process.NewSearcher(logger)
 		if err != nil {
-			return nil, E.Cause(err, "create process searcher")
+			if err != os.ErrInvalid {
+				logger.Warn(E.Cause(err, "create process searcher"))
+			}
+		} else {
+			router.processSearcher = searcher
 		}
-		router.processSearcher = searcher
 	}
 	return router, nil
 }
@@ -388,7 +423,8 @@ func (r *Router) Start() error {
 		if starter, isStarter := r.processSearcher.(common.Starter); isStarter {
 			err := starter.Start()
 			if err != nil {
-				return E.Cause(err, "initialize process searcher")
+				r.logger.Error(E.Cause(err, "initialize process searcher"))
+				r.processSearcher = nil
 			}
 		}
 	}

+ 8 - 0
route/rule_package_name.go

@@ -4,6 +4,13 @@ import (
 	"strings"
 
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/warning"
+	C "github.com/sagernet/sing-box/constant"
+)
+
+var warnPackageNameOnNonAndroid = warning.New(
+	func() bool { return !C.IsAndroid },
+	"rule item `package_name` is only supported on Android",
 )
 
 var _ RuleItem = (*PackageNameItem)(nil)
@@ -14,6 +21,7 @@ type PackageNameItem struct {
 }
 
 func NewPackageNameItem(packageNameList []string) *PackageNameItem {
+	warnPackageNameOnNonAndroid.Check()
 	rule := &PackageNameItem{
 		packageNames: packageNameList,
 		packageMap:   make(map[string]bool),

+ 8 - 0
route/rule_process.go

@@ -5,6 +5,13 @@ import (
 	"strings"
 
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/warning"
+	C "github.com/sagernet/sing-box/constant"
+)
+
+var warnProcessNameOnNonSupportedPlatform = warning.New(
+	func() bool { return !(C.IsLinux || C.IsWindows || C.IsDarwin) },
+	"rule item `process_item` is only supported on Linux, Windows, and Mac OS X",
 )
 
 var _ RuleItem = (*ProcessItem)(nil)
@@ -15,6 +22,7 @@ type ProcessItem struct {
 }
 
 func NewProcessItem(processNameList []string) *ProcessItem {
+	warnProcessNameOnNonSupportedPlatform.Check()
 	rule := &ProcessItem{
 		processes:  processNameList,
 		processMap: make(map[string]bool),

+ 15 - 0
route/rule_user.go

@@ -4,9 +4,22 @@ import (
 	"strings"
 
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/warning"
+	C "github.com/sagernet/sing-box/constant"
 	F "github.com/sagernet/sing/common/format"
 )
 
+var (
+	warnUserOnNonLinux = warning.New(
+		func() bool { return !C.IsLinux },
+		"rule item `user` is only supported on Linux",
+	)
+	warnUserOnCGODisabled = warning.New(
+		func() bool { return !C.CGO_ENABLED },
+		"rule item `user` is only supported with CGO enabled, rebuild with CGO_ENABLED=1",
+	)
+)
+
 var _ RuleItem = (*UserItem)(nil)
 
 type UserItem struct {
@@ -15,6 +28,8 @@ type UserItem struct {
 }
 
 func NewUserItem(users []string) *UserItem {
+	warnUserOnNonLinux.Check()
+	warnUserOnCGODisabled.Check()
 	userMap := make(map[string]bool)
 	for _, protocol := range users {
 		userMap[protocol] = true

+ 8 - 0
route/rule_user_id.go

@@ -4,9 +4,16 @@ import (
 	"strings"
 
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/warning"
+	C "github.com/sagernet/sing-box/constant"
 	F "github.com/sagernet/sing/common/format"
 )
 
+var warnUserIDOnNonLinux = warning.New(
+	func() bool { return !C.IsLinux },
+	"rule item `user_id` is only supported on Linux",
+)
+
 var _ RuleItem = (*UserIdItem)(nil)
 
 type UserIdItem struct {
@@ -15,6 +22,7 @@ type UserIdItem struct {
 }
 
 func NewUserIDItem(userIdList []int32) *UserIdItem {
+	warnUserIDOnNonLinux.Check()
 	rule := &UserIdItem{
 		userIds:   userIdList,
 		userIdMap: make(map[int32]bool),

+ 3 - 6
test/clash_test.go

@@ -8,11 +8,11 @@ import (
 	"io"
 	"net"
 	"net/netip"
-	"runtime"
 	"sync"
 	"testing"
 	"time"
 
+	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing/common/control"
 	F "github.com/sagernet/sing/common/format"
@@ -37,13 +37,10 @@ var allImages = []string{
 	ImageV2RayCore,
 }
 
-var (
-	localIP  = netip.MustParseAddr("127.0.0.1")
-	isDarwin = runtime.GOOS == "darwin"
-)
+var localIP = netip.MustParseAddr("127.0.0.1")
 
 func init() {
-	if isDarwin {
+	if C.IsDarwin {
 		var err error
 		localIP, err = defaultRouteIP()
 		if err != nil {

+ 2 - 1
test/docker_test.go

@@ -5,6 +5,7 @@ import (
 	"testing"
 	"time"
 
+	C "github.com/sagernet/sing-box/constant"
 	F "github.com/sagernet/sing/common/format"
 
 	"github.com/docker/docker/api/types"
@@ -47,7 +48,7 @@ func startDockerContainer(t *testing.T, options DockerOptions) {
 	containerOptions.ExposedPorts = make(nat.PortSet)
 
 	var hostOptions container.HostConfig
-	if !isDarwin {
+	if !C.IsDarwin {
 		hostOptions.NetworkMode = "host"
 	}
 	hostOptions.PortBindings = make(nat.PortMap)