From 9a8b25076fa73be6065ae7f84e70d9c3d3588f53 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Fri, 1 Sep 2023 14:52:30 +0330 Subject: [PATCH] Add config builder --- custom/custom.go | 20 ++- mobile/mobile.go | 13 +- shared/config.go | 335 +++++++++++++++++++++++++++++++++++++++++++++ shared/override.go | 102 -------------- shared/template.go | 168 ----------------------- shared/utils.go | 35 ----- 6 files changed, 354 insertions(+), 319 deletions(-) create mode 100644 shared/config.go delete mode 100644 shared/override.go delete mode 100644 shared/template.go delete mode 100644 shared/utils.go diff --git a/custom/custom.go b/custom/custom.go index c535a89..e3b86a6 100644 --- a/custom/custom.go +++ b/custom/custom.go @@ -5,6 +5,7 @@ package main */ import "C" import ( + "encoding/json" "os" "unsafe" @@ -14,6 +15,7 @@ import ( ) var box *libbox.BoxService +var configOptions *shared.ConfigOptions //export setupOnce func setupOnce(api unsafe.Pointer) { @@ -34,6 +36,16 @@ func parse(path *C.char) *C.char { return C.CString("") } +//export changeConfigOptions +func changeConfigOptions(configOptionsJson *C.char) *C.char { + configOptions = &shared.ConfigOptions{} + err := json.Unmarshal([]byte(C.GoString(configOptionsJson)), configOptions) + if err != nil { + return C.CString(err.Error()) + } + return C.CString("") +} + //export create func create(configPath *C.char) *C.char { path := C.GoString(configPath) @@ -45,13 +57,7 @@ func create(configPath *C.char) *C.char { if err != nil { return C.CString(err.Error()) } - overrides := shared.ConfigOverrides{ - LogOutput: shared.StringAddr("box.log"), - EnableTun: shared.BoolAddr(false), - SetSystemProxy: shared.BoolAddr(true), - } - template := shared.DefaultTemplate(overrides) - options = shared.ApplyOverrides(template, options, overrides) + options = shared.BuildConfig(*configOptions, options) shared.SaveCurrentConfig(sWorkingPath, options) diff --git a/mobile/mobile.go b/mobile/mobile.go index 216ca6d..9031971 100644 --- a/mobile/mobile.go +++ b/mobile/mobile.go @@ -13,7 +13,7 @@ func Parse(path string) error { return shared.ParseConfig(path) } -func ApplyOverrides(path string) (string, error) { +func BuildConfig(path string, configOptionsJson string) (string, error) { fileContent, err := os.ReadFile(path) if err != nil { return "", err @@ -23,13 +23,12 @@ func ApplyOverrides(path string) (string, error) { if err != nil { return "", err } - overrides := shared.ConfigOverrides{ - EnableTun: shared.BoolAddr(true), - SetSystemProxy: shared.BoolAddr(false), - LogOutput: shared.StringAddr(""), + configOptions := &shared.ConfigOptions{} + err = json.Unmarshal([]byte(configOptionsJson), configOptions) + if err != nil { + return "", nil } - template := shared.DefaultTemplate(overrides) - options = shared.ApplyOverrides(template, options, overrides) + options = shared.BuildConfig(*configOptions, options) config, err := json.Marshal(options) return string(config), err } diff --git a/shared/config.go b/shared/config.go new file mode 100644 index 0000000..ef20188 --- /dev/null +++ b/shared/config.go @@ -0,0 +1,335 @@ +package shared + +import ( + "fmt" + "net/netip" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + dns "github.com/sagernet/sing-dns" +) + +type ConfigOptions struct { + ExecuteAsIs bool `json:"execute-config-as-is"` + LogLevel string `json:"log-level"` + ResolveDestination bool `json:"resolve-destination"` + IPv6Mode option.DomainStrategy `json:"ipv6-mode"` + RemoteDnsAddress string `json:"remote-dns-address"` + RemoteDnsDomainStrategy option.DomainStrategy `json:"remote-dns-domain-strategy"` + DirectDnsAddress string `json:"direct-dns-address"` + DirectDnsDomainStrategy option.DomainStrategy `json:"direct-dns-domain-strategy"` + MixedPort uint16 `json:"mixed-port"` + LocalDnsPort uint16 `json:"local-dns-port"` + MTU uint32 `json:"mtu"` + ConnectionTestUrl string `json:"connection-test-url"` + URLTestInterval option.Duration `json:"url-test-interval"` + EnableClashApi bool `json:"enable-clash-api"` + ClashApiPort uint16 `json:"clash-api-port"` + EnableTun bool `json:"enable-tun"` + SetSystemProxy bool `json:"set-system-proxy"` +} + +func BuildConfig(configOpt ConfigOptions, input option.Options) option.Options { + if configOpt.ExecuteAsIs { + return applyOverrides(configOpt, input) + } + + var options option.Options + + fmt.Printf("%+v\n", configOpt) + + if configOpt.EnableClashApi { + options.Experimental = &option.ExperimentalOptions{ + ClashAPI: &option.ClashAPIOptions{ + ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", configOpt.ClashApiPort), + StoreSelected: true, + }, + } + } + + options.Log = &option.LogOptions{ + Level: configOpt.LogLevel, + Output: "box.log", + Disabled: false, + Timestamp: false, + DisableColor: true, + } + + options.DNS = &option.DNSOptions{ + DNSClientOptions: option.DNSClientOptions{ + IndependentCache: true, + }, + Servers: []option.DNSServerOptions{ + { + Tag: "dns-remote", + Address: configOpt.RemoteDnsAddress, + AddressResolver: "dns-direct", + Strategy: configOpt.RemoteDnsDomainStrategy, + Detour: "select", + }, + { + Tag: "dns-direct", + Address: configOpt.DirectDnsAddress, + AddressResolver: "dns-local", + Strategy: configOpt.DirectDnsDomainStrategy, + Detour: "direct", + }, + { + Tag: "dns-local", + Address: "local", + Detour: "direct", + }, + { + Tag: "dns-block", + Address: "rcode://success", + }, + }, + Rules: []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + Outbound: []string{"any"}, + // Server: "dns-direct", + Server: "dns-local", + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + ClashMode: "Direct", + Server: "dns-local", + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + ClashMode: "Global", + Server: "dns-remote", + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + DomainSuffix: []string{"ir"}, + Server: "dns-local", + }, + }, + }, + } + + var inboundDomainStrategy option.DomainStrategy + if !configOpt.ResolveDestination { + inboundDomainStrategy = option.DomainStrategy(dns.DomainStrategyAsIS) + } else { + inboundDomainStrategy = configOpt.IPv6Mode + } + + if configOpt.EnableTun { + tunInbound := option.Inbound{ + Type: C.TypeTun, + Tag: "tun-in", + TunOptions: option.TunInboundOptions{ + MTU: configOpt.MTU, + AutoRoute: true, + StrictRoute: true, + EndpointIndependentNat: true, + InboundOptions: option.InboundOptions{ + SniffEnabled: true, + SniffOverrideDestination: true, + DomainStrategy: inboundDomainStrategy, + }, + }, + } + switch configOpt.IPv6Mode { + case option.DomainStrategy(dns.DomainStrategyUseIPv4): + tunInbound.TunOptions.Inet4Address = []option.ListenPrefix{ + option.ListenPrefix(netip.MustParsePrefix("172.19.0.1/28")), + } + case option.DomainStrategy(dns.DomainStrategyUseIPv6): + tunInbound.TunOptions.Inet6Address = []option.ListenPrefix{ + option.ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126")), + } + default: + tunInbound.TunOptions.Inet4Address = []option.ListenPrefix{ + option.ListenPrefix(netip.MustParsePrefix("172.19.0.1/28")), + } + tunInbound.TunOptions.Inet6Address = []option.ListenPrefix{ + option.ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126")), + } + } + options.Inbounds = append(options.Inbounds, tunInbound) + } + + options.Inbounds = append( + options.Inbounds, + option.Inbound{ + Type: C.TypeMixed, + Tag: "mixed-in", + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.MustParseAddr("127.0.0.1")), + ListenPort: configOpt.MixedPort, + InboundOptions: option.InboundOptions{ + SniffEnabled: true, + SniffOverrideDestination: true, + DomainStrategy: inboundDomainStrategy, + }, + }, + SetSystemProxy: configOpt.SetSystemProxy, + }, + }, + ) + + options.Inbounds = append(options.Inbounds, + option.Inbound{ + Type: C.TypeDirect, + Tag: "dns-in", + DirectOptions: option.DirectInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.MustParseAddr("127.0.0.1")), + ListenPort: configOpt.LocalDnsPort, + }, + OverrideAddress: "8.8.8.8", + OverridePort: 53, + }, + }, + ) + + options.Route = &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + Inbound: []string{"dns-in"}, + Outbound: "dns-out", + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + Port: []uint16{53}, + Outbound: "dns-out", + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + Protocol: []string{"dns"}, + Outbound: "dns-out", + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + ClashMode: "Direct", + Outbound: "direct", + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + ClashMode: "Global", + Outbound: "select", + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + Geosite: []string{"category-ads-all"}, + Outbound: "block", + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + GeoIP: []string{"ir", "private"}, + DomainSuffix: []string{"ir"}, + Outbound: "direct", + }, + }, + }, + AutoDetectInterface: true, + OverrideAndroidVPN: true, + } + + var outbounds []option.Outbound + var tags []string + for _, out := range input.Outbounds { + switch out.Type { + case C.TypeDirect, C.TypeBlock, C.TypeDNS: + continue + case C.TypeSelector, C.TypeURLTest: + continue + default: + tags = append(tags, out.Tag) + outbounds = append(outbounds, out) + } + } + + urlTest := option.Outbound{ + Type: C.TypeURLTest, + Tag: "auto", + URLTestOptions: option.URLTestOutboundOptions{ + Outbounds: tags, + URL: configOpt.ConnectionTestUrl, + Interval: configOpt.URLTestInterval, + }, + } + + selector := option.Outbound{ + Type: C.TypeSelector, + Tag: "select", + SelectorOptions: option.SelectorOutboundOptions{ + Outbounds: append([]string{urlTest.Tag}, tags...), + Default: urlTest.Tag, + }, + } + + outbounds = append([]option.Outbound{selector, urlTest}, outbounds...) + + options.Outbounds = append( + outbounds, + []option.Outbound{ + { + Tag: "dns-out", + Type: C.TypeDNS, + }, + { + Tag: "direct", + Type: C.TypeDirect, + }, + { + Tag: "block", + Type: C.TypeBlock, + }, + }..., + ) + + return options +} + +func applyOverrides(overrides ConfigOptions, options option.Options) option.Options { + if overrides.EnableClashApi { + options.Experimental.ClashAPI = &option.ClashAPIOptions{ + ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", overrides.ClashApiPort), + StoreSelected: true, + } + } + + options.Log = &option.LogOptions{ + Level: overrides.LogLevel, + Output: "box.log", + Disabled: false, + } + + var inbounds []option.Inbound + for _, inb := range options.Inbounds { + if inb.Type == C.TypeTun && !overrides.EnableTun { + continue + } + inbounds = append(inbounds, inb) + } + options.Inbounds = inbounds + + return options +} diff --git a/shared/override.go b/shared/override.go deleted file mode 100644 index 3474cd3..0000000 --- a/shared/override.go +++ /dev/null @@ -1,102 +0,0 @@ -package shared - -import ( - "fmt" - - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" -) - -type ConfigOverrides struct { - ClashApiPort *int `json:"clash-api-port"` - EnableTun *bool `json:"enable-tun"` - SetSystemProxy *bool `json:"set-system-proxy"` - LogLevel *string `json:"log-level"` - LogOutput *string `json:"log-output"` - DNSRemote *string `json:"dns-remote"` - MixedPort *int `json:"mixed-port"` -} - -func ApplyOverrides(base option.Options, options option.Options, overrides ConfigOverrides) option.Options { - clashApiPort := pointerOrDefaultInt(overrides.ClashApiPort, 9090) - base.Experimental = &option.ExperimentalOptions{ - ClashAPI: &option.ClashAPIOptions{ - ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", clashApiPort), - StoreSelected: true, - }, - } - - base.Log = &option.LogOptions{ - Level: pointerOrDefaultString(overrides.LogLevel, "info"), - Output: pointerOrDefaultString(overrides.LogOutput, ""), - Disabled: false, - Timestamp: false, - DisableColor: true, - } - - var inbounds []option.Inbound - for _, inb := range base.Inbounds { - switch inb.Type { - case C.TypeTun: - if pointerOrDefaultBool(overrides.EnableTun, true) { - inbounds = append(inbounds, inb) - } - default: - inbounds = append(inbounds, inb) - } - } - base.Inbounds = inbounds - - var outbounds []option.Outbound - var tags []string - for _, out := range options.Outbounds { - switch out.Type { - case C.TypeDirect, C.TypeBlock, C.TypeDNS: - continue - case C.TypeSelector, C.TypeURLTest: - continue - default: - tags = append(tags, out.Tag) - } - outbounds = append(outbounds, out) - } - - urlTest := option.Outbound{ - Type: C.TypeURLTest, - Tag: "auto", - URLTestOptions: option.URLTestOutboundOptions{ - Outbounds: tags, - }, - } - - selector := option.Outbound{ - Type: C.TypeSelector, - Tag: "select", - SelectorOptions: option.SelectorOutboundOptions{ - Outbounds: append([]string{urlTest.Tag}, tags...), - Default: urlTest.Tag, - }, - } - - outbounds = append([]option.Outbound{selector, urlTest}, outbounds...) - - base.Outbounds = append( - outbounds, - []option.Outbound{ - { - Tag: "direct", - Type: C.TypeDirect, - }, - { - Tag: "block", - Type: C.TypeBlock, - }, - { - Tag: "dns-out", - Type: C.TypeDNS, - }, - }..., - ) - - return base -} diff --git a/shared/template.go b/shared/template.go deleted file mode 100644 index 08f3c9c..0000000 --- a/shared/template.go +++ /dev/null @@ -1,168 +0,0 @@ -package shared - -import ( - "net/netip" - - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" -) - -func DefaultTemplate(overrides ConfigOverrides) option.Options { - var options option.Options - - options.Experimental = &option.ExperimentalOptions{ - ClashAPI: &option.ClashAPIOptions{ - ExternalController: "127.0.0.1:9090", - StoreSelected: true, - }, - } - - options.Log = &option.LogOptions{ - Level: "warn", - Disabled: false, - Timestamp: false, - DisableColor: true, - } - - options.DNS = &option.DNSOptions{ - DNSClientOptions: option.DNSClientOptions{ - Strategy: option.DomainStrategy(dns.DomainStrategyPreferIPv4), - IndependentCache: true, - }, - Servers: []option.DNSServerOptions{ - { - Tag: "local", - Address: "local", - Detour: "direct", - }, - { - Tag: "dns-remote", - Address: pointerOrDefaultString(overrides.DNSRemote, "tcp://1.1.1.1"), - AddressResolver: "local", - Strategy: option.DomainStrategy(dns.DomainStrategyPreferIPv4), - Detour: "select", - }, - }, - Rules: []option.DNSRule{ - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultDNSRule{ - ClashMode: "direct", - Server: "local", - }, - }, - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultDNSRule{ - ClashMode: "global", - Server: "dns-remote", - }, - }, - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultDNSRule{ - DomainSuffix: []string{"ir"}, - Server: "local", - }, - }, - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultDNSRule{ - Outbound: []string{"any"}, - Server: "local", - }, - }, - }, - ReverseMapping: true, - Final: "dns-remote", - } - - if pointerOrDefaultBool(overrides.EnableTun, true) { - options.Inbounds = append( - options.Inbounds, - option.Inbound{ - Type: C.TypeTun, - Tag: "tun-in", - TunOptions: option.TunInboundOptions{ - Inet4Address: []option.ListenPrefix{ - option.ListenPrefix(netip.MustParsePrefix("172.19.0.1/30")), - }, - MTU: 9000, - AutoRoute: true, - StrictRoute: true, - EndpointIndependentNat: true, - InboundOptions: option.InboundOptions{ - SniffEnabled: true, - SniffOverrideDestination: true, - DomainStrategy: option.DomainStrategy(dns.DomainStrategyUseIPv4), - }, - }, - }, - ) - } - options.Inbounds = append( - options.Inbounds, - option.Inbound{ - Type: C.TypeMixed, - Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ - ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.MustParseAddr("127.0.0.1")), - ListenPort: uint16(pointerOrDefaultInt(overrides.MixedPort, 2334)), - InboundOptions: option.InboundOptions{ - SniffEnabled: true, - SniffOverrideDestination: true, - DomainStrategy: option.DomainStrategy(dns.DomainStrategyUseIPv4), - }, - }, - SetSystemProxy: pointerOrDefaultBool(overrides.SetSystemProxy, true), - }, - }, - ) - - options.Route = &option.RouteOptions{ - Rules: []option.Rule{ - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultRule{ - Geosite: []string{"category-ads-all"}, - Outbound: "block", - }, - }, - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultRule{ - Protocol: []string{"dns"}, - Outbound: "dns-out", - }, - }, - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultRule{ - ClashMode: "direct", - Outbound: "direct", - }, - }, - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultRule{ - ClashMode: "global", - Outbound: "select", - }, - }, - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultRule{ - GeoIP: []string{"ir", "private"}, - DomainSuffix: []string{"ir"}, - Outbound: "direct", - }, - }, - }, - AutoDetectInterface: true, - OverrideAndroidVPN: true, - } - - return options -} diff --git a/shared/utils.go b/shared/utils.go deleted file mode 100644 index aac986e..0000000 --- a/shared/utils.go +++ /dev/null @@ -1,35 +0,0 @@ -package shared - -func StringAddr(b string) *string { - stringVar := b - return &stringVar -} - -func BoolAddr(b bool) *bool { - boolVar := b - return &boolVar -} - -func pointerOrDefaultString(p *string, def string) string { - if p != nil { - return *p - } - - return def -} - -func pointerOrDefaultInt(p *int, def int) int { - if p != nil { - return *p - } - - return def -} - -func pointerOrDefaultBool(p *bool, def bool) bool { - if p != nil { - return *p - } - - return def -}