浏览代码

Add tun platform options

世界 2 年之前
父节点
当前提交
7834d6bca7

+ 7 - 6
box.go

@@ -11,6 +11,7 @@ import (
 	"github.com/sagernet/sing-box/adapter"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/experimental"
+	"github.com/sagernet/sing-box/experimental/libbox/platform"
 	"github.com/sagernet/sing-box/inbound"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
@@ -36,7 +37,7 @@ type Box struct {
 	done        chan struct{}
 }
 
-func New(ctx context.Context, options option.Options) (*Box, error) {
+func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
 	createdAt := time.Now()
 	logOptions := common.PtrValueOrDefault(options.Log)
 
@@ -61,7 +62,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
 	} else {
 		switch logOptions.Output {
 		case "":
-			if options.PlatformInterface != nil {
+			if platformInterface != nil {
 				logWriter = io.Discard
 			} else {
 				logWriter = os.Stdout
@@ -86,10 +87,10 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
 			TimestampFormat:  "-0700 2006-01-02 15:04:05",
 		}
 		if needClashAPI {
-			observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, options.PlatformInterface)
+			observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
 			logFactory = observableLogFactory
 		} else {
-			logFactory = log.NewFactory(logFormatter, logWriter, options.PlatformInterface)
+			logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
 		}
 		if logOptions.Level != "" {
 			logLevel, err := log.ParseLevel(logOptions.Level)
@@ -109,7 +110,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
 		common.PtrValueOrDefault(options.DNS),
 		common.PtrValueOrDefault(options.NTP),
 		options.Inbounds,
-		options.PlatformInterface,
+		platformInterface,
 	)
 	if err != nil {
 		return nil, E.Cause(err, "parse route options")
@@ -129,7 +130,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
 			router,
 			logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
 			inboundOptions,
-			options.PlatformInterface,
+			platformInterface,
 		)
 		if err != nil {
 			return nil, E.Cause(err, "parse inbound[", i, "]")

+ 1 - 1
cmd/sing-box/cmd_check.go

@@ -31,7 +31,7 @@ func check() error {
 		return err
 	}
 	ctx, cancel := context.WithCancel(context.Background())
-	_, err = box.New(ctx, options)
+	_, err = box.New(ctx, options, nil)
 	cancel()
 	return err
 }

+ 1 - 1
cmd/sing-box/cmd_run.go

@@ -64,7 +64,7 @@ func create() (*box.Box, context.CancelFunc, error) {
 		options.Log.DisableColor = true
 	}
 	ctx, cancel := context.WithCancel(context.Background())
-	instance, err := box.New(ctx, options)
+	instance, err := box.New(ctx, options, nil)
 	if err != nil {
 		cancel()
 		return nil, nil, E.Cause(err, "create service")

+ 50 - 0
experimental/libbox/platform.go

@@ -1,5 +1,7 @@
 package libbox
 
+import "github.com/sagernet/sing-box/option"
+
 type PlatformInterface interface {
 	AutoDetectInterfaceControl(fd int32) error
 	OpenTun(options TunOptions) (int32, error)
@@ -14,3 +16,51 @@ type TunInterface interface {
 	FileDescriptor() int32
 	Close() error
 }
+
+type OnDemandRuleIterator interface {
+	Next() OnDemandRule
+	HasNext() bool
+}
+
+type OnDemandRule interface {
+	Target() int32
+	DNSSearchDomainMatch() StringIterator
+	DNSServerAddressMatch() StringIterator
+	InterfaceTypeMatch() int32
+	SSIDMatch() StringIterator
+	ProbeURL() string
+}
+
+type onDemandRule struct {
+	option.OnDemandRule
+}
+
+func (r *onDemandRule) Target() int32 {
+	if r.OnDemandRule.Action == nil {
+		return -1
+	}
+	return int32(*r.OnDemandRule.Action)
+}
+
+func (r *onDemandRule) DNSSearchDomainMatch() StringIterator {
+	return newIterator(r.OnDemandRule.DNSSearchDomainMatch)
+}
+
+func (r *onDemandRule) DNSServerAddressMatch() StringIterator {
+	return newIterator(r.OnDemandRule.DNSServerAddressMatch)
+}
+
+func (r *onDemandRule) InterfaceTypeMatch() int32 {
+	if r.OnDemandRule.InterfaceTypeMatch == nil {
+		return -1
+	}
+	return int32(*r.OnDemandRule.InterfaceTypeMatch)
+}
+
+func (r *onDemandRule) SSIDMatch() StringIterator {
+	return newIterator(r.OnDemandRule.SSIDMatch)
+}
+
+func (r *onDemandRule) ProbeURL() string {
+	return r.OnDemandRule.ProbeURL
+}

+ 2 - 1
experimental/libbox/platform/interface.go

@@ -4,13 +4,14 @@ import (
 	"io"
 
 	"github.com/sagernet/sing-box/common/process"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-tun"
 	"github.com/sagernet/sing/common/control"
 )
 
 type Interface interface {
 	AutoDetectInterfaceControl() control.Func
-	OpenTun(options tun.Options) (tun.Tun, error)
+	OpenTun(options tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
 	process.Searcher
 	io.Writer
 }

+ 4 - 6
experimental/libbox/service.go

@@ -10,6 +10,7 @@ import (
 	"github.com/sagernet/sing-box/common/process"
 	"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
 	"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"
 	E "github.com/sagernet/sing/common/exceptions"
@@ -28,9 +29,8 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
 		return nil, err
 	}
 	platformInterface.WriteLog("Hello " + runtime.GOOS + "/" + runtime.GOARCH)
-	options.PlatformInterface = &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()}
 	ctx, cancel := context.WithCancel(context.Background())
-	instance, err := box.New(ctx, options)
+	instance, err := box.New(ctx, options, &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()})
 	if err != nil {
 		cancel()
 		return nil, E.Cause(err, "create service")
@@ -66,16 +66,14 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
 	}
 }
 
-func (w *platformInterfaceWrapper) OpenTun(options tun.Options) (tun.Tun, error) {
+func (w *platformInterfaceWrapper) OpenTun(options tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
 	if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
 		return nil, E.New("android: unsupported uid options")
 	}
 	if len(options.IncludeAndroidUser) > 0 {
 		return nil, E.New("android: unsupported android_user option")
 	}
-
-	optionsWrapper := tunOptions(options)
-	tunFd, err := w.iif.OpenTun(&optionsWrapper)
+	tunFd, err := w.iif.OpenTun(&tunOptions{options, platformOptions})
 	if err != nil {
 		return nil, err
 	}

+ 23 - 1
experimental/libbox/tun.go

@@ -4,6 +4,7 @@ import (
 	"net"
 	"net/netip"
 
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-tun"
 	"github.com/sagernet/sing/common"
 	E "github.com/sagernet/sing/common/exceptions"
@@ -21,6 +22,9 @@ type TunOptions interface {
 	GetInet6RouteAddress() RoutePrefixIterator
 	GetIncludePackage() StringIterator
 	GetExcludePackage() StringIterator
+	IsHTTPProxyEnabled() bool
+	GetHTTPProxyServer() string
+	GetHTTPProxyServerPort() int32
 }
 
 type RoutePrefix struct {
@@ -54,7 +58,10 @@ func mapRoutePrefix(prefixes []netip.Prefix) RoutePrefixIterator {
 
 var _ TunOptions = (*tunOptions)(nil)
 
-type tunOptions tun.Options
+type tunOptions struct {
+	tun.Options
+	option.TunPlatformOptions
+}
 
 func (o *tunOptions) GetInet4Address() RoutePrefixIterator {
 	return mapRoutePrefix(o.Inet4Address)
@@ -98,3 +105,18 @@ func (o *tunOptions) GetIncludePackage() StringIterator {
 func (o *tunOptions) GetExcludePackage() StringIterator {
 	return newIterator(o.ExcludePackage)
 }
+
+func (o *tunOptions) IsHTTPProxyEnabled() bool {
+	if o.TunPlatformOptions.HTTPProxy == nil {
+		return false
+	}
+	return o.TunPlatformOptions.HTTPProxy.Enabled
+}
+
+func (o *tunOptions) GetHTTPProxyServer() string {
+	return o.TunPlatformOptions.HTTPProxy.Server
+}
+
+func (o *tunOptions) GetHTTPProxyServerPort() int32 {
+	return int32(o.TunPlatformOptions.HTTPProxy.ServerPort)
+}

+ 3 - 1
inbound/tun.go

@@ -36,6 +36,7 @@ type Tun struct {
 	tunIf                  tun.Tun
 	tunStack               tun.Stack
 	platformInterface      platform.Interface
+	platformOptions        option.TunPlatformOptions
 }
 
 func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
@@ -96,6 +97,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
 		udpTimeout:             udpTimeout,
 		stack:                  options.Stack,
 		platformInterface:      platformInterface,
+		platformOptions:        common.PtrValueOrDefault(options.Platform),
 	}, nil
 }
 
@@ -148,7 +150,7 @@ func (t *Tun) Start() error {
 		err          error
 	)
 	if t.platformInterface != nil {
-		tunInterface, err = t.platformInterface.OpenTun(t.tunOptions)
+		tunInterface, err = t.platformInterface.OpenTun(t.tunOptions, t.platformOptions)
 	} else {
 		tunInterface, err = tun.New(t.tunOptions)
 	}

+ 7 - 9
option/config.go

@@ -5,19 +5,17 @@ import (
 	"strings"
 
 	"github.com/sagernet/sing-box/common/json"
-	"github.com/sagernet/sing-box/experimental/libbox/platform"
 	E "github.com/sagernet/sing/common/exceptions"
 )
 
 type _Options struct {
-	Log               *LogOptions          `json:"log,omitempty"`
-	DNS               *DNSOptions          `json:"dns,omitempty"`
-	NTP               *NTPOptions          `json:"ntp,omitempty"`
-	Inbounds          []Inbound            `json:"inbounds,omitempty"`
-	Outbounds         []Outbound           `json:"outbounds,omitempty"`
-	Route             *RouteOptions        `json:"route,omitempty"`
-	Experimental      *ExperimentalOptions `json:"experimental,omitempty"`
-	PlatformInterface platform.Interface   `json:"-"`
+	Log          *LogOptions          `json:"log,omitempty"`
+	DNS          *DNSOptions          `json:"dns,omitempty"`
+	NTP          *NTPOptions          `json:"ntp,omitempty"`
+	Inbounds     []Inbound            `json:"inbounds,omitempty"`
+	Outbounds    []Outbound           `json:"outbounds,omitempty"`
+	Route        *RouteOptions        `json:"route,omitempty"`
+	Experimental *ExperimentalOptions `json:"experimental,omitempty"`
 }
 
 type Options _Options

+ 104 - 0
option/platform.go

@@ -0,0 +1,104 @@
+package option
+
+import (
+	"github.com/sagernet/sing-box/common/json"
+	E "github.com/sagernet/sing/common/exceptions"
+)
+
+type OnDemandOptions struct {
+	Enabled bool           `json:"enabled,omitempty"`
+	Rules   []OnDemandRule `json:"rules,omitempty"`
+}
+
+type OnDemandRule struct {
+	Action                *OnDemandRuleAction        `json:"action,omitempty"`
+	DNSSearchDomainMatch  Listable[string]           `json:"dns_search_domain_match,omitempty"`
+	DNSServerAddressMatch Listable[string]           `json:"dns_server_address_match,omitempty"`
+	InterfaceTypeMatch    *OnDemandRuleInterfaceType `json:"interface_type_match,omitempty"`
+	SSIDMatch             Listable[string]           `json:"ssid_match,omitempty"`
+	ProbeURL              string                     `json:"probe_url,omitempty"`
+}
+
+type OnDemandRuleAction int
+
+func (r *OnDemandRuleAction) MarshalJSON() ([]byte, error) {
+	if r == nil {
+		return nil, nil
+	}
+	value := *r
+	var actionName string
+	switch value {
+	case 1:
+		actionName = "connect"
+	case 2:
+		actionName = "disconnect"
+	case 3:
+		actionName = "evaluate_connection"
+	default:
+		return nil, E.New("unknown action: ", value)
+	}
+	return json.Marshal(actionName)
+}
+
+func (r *OnDemandRuleAction) UnmarshalJSON(bytes []byte) error {
+	var actionName string
+	if err := json.Unmarshal(bytes, &actionName); err != nil {
+		return err
+	}
+	var actionValue int
+	switch actionName {
+	case "connect":
+		actionValue = 1
+	case "disconnect":
+		actionValue = 2
+	case "evaluate_connection":
+		actionValue = 3
+	case "ignore":
+		actionValue = 4
+	default:
+		return E.New("unknown action name: ", actionName)
+	}
+	*r = OnDemandRuleAction(actionValue)
+	return nil
+}
+
+type OnDemandRuleInterfaceType int
+
+func (r *OnDemandRuleInterfaceType) MarshalJSON() ([]byte, error) {
+	if r == nil {
+		return nil, nil
+	}
+	value := *r
+	var interfaceTypeName string
+	switch value {
+	case 1:
+		interfaceTypeName = "any"
+	case 2:
+		interfaceTypeName = "wifi"
+	case 3:
+		interfaceTypeName = "cellular"
+	default:
+		return nil, E.New("unknown interface type: ", value)
+	}
+	return json.Marshal(interfaceTypeName)
+}
+
+func (r *OnDemandRuleInterfaceType) UnmarshalJSON(bytes []byte) error {
+	var interfaceTypeName string
+	if err := json.Unmarshal(bytes, &interfaceTypeName); err != nil {
+		return err
+	}
+	var interfaceTypeValue int
+	switch interfaceTypeName {
+	case "any":
+		interfaceTypeValue = 1
+	case "wifi":
+		interfaceTypeValue = 2
+	case "cellular":
+		interfaceTypeValue = 3
+	default:
+		return E.New("unknown interface type name: ", interfaceTypeName)
+	}
+	*r = OnDemandRuleInterfaceType(interfaceTypeValue)
+	return nil
+}

+ 1 - 0
option/tun.go

@@ -19,5 +19,6 @@ type TunInboundOptions struct {
 	EndpointIndependentNat bool                   `json:"endpoint_independent_nat,omitempty"`
 	UDPTimeout             int64                  `json:"udp_timeout,omitempty"`
 	Stack                  string                 `json:"stack,omitempty"`
+	Platform               *TunPlatformOptions    `json:"platform,omitempty"`
 	InboundOptions
 }

+ 10 - 0
option/tun_platform.go

@@ -0,0 +1,10 @@
+package option
+
+type TunPlatformOptions struct {
+	HTTPProxy *HTTPProxyOptions `json:"http_proxy,omitempty"`
+}
+
+type HTTPProxyOptions struct {
+	Enabled bool `json:"enabled,omitempty"`
+	ServerOptions
+}