Add routing rules

This commit is contained in:
problematicconsumer
2023-10-19 19:56:50 +03:30
parent 4beb9ce154
commit 9936a52742
2 changed files with 241 additions and 87 deletions

View File

@@ -1,8 +1,12 @@
package shared
import (
"encoding/json"
"fmt"
"net"
"net/netip"
"net/url"
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
@@ -28,29 +32,36 @@ type ConfigOptions struct {
ClashApiPort uint16 `json:"clash-api-port"`
EnableTun bool `json:"enable-tun"`
SetSystemProxy bool `json:"set-system-proxy"`
BypassLAN bool `json:"bypass-lan"`
Rules []Rule `json:"rules"`
}
// TODO add fake dns
// TODO add bypass outbound
// TODO include selectors
func BuildConfig(configOpt ConfigOptions, input option.Options) option.Options {
if configOpt.ExecuteAsIs {
return applyOverrides(configOpt, input)
}
var options option.Options
fmt.Printf("config options: %+v\n", configOpt)
fmt.Printf("%+v\n", configOpt)
var options option.Options
directDNSDomains := []string{}
if configOpt.EnableClashApi {
options.Experimental = &option.ExperimentalOptions{
ClashAPI: &option.ClashAPIOptions{
ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", configOpt.ClashApiPort),
StoreSelected: true,
CacheFile: "clash.db",
},
}
}
options.Log = &option.LogOptions{
Level: configOpt.LogLevel,
Output: "box.log",
Output: "./logs/box.log",
Disabled: false,
Timestamp: false,
DisableColor: true,
@@ -66,7 +77,6 @@ func BuildConfig(configOpt ConfigOptions, input option.Options) option.Options {
Address: configOpt.RemoteDnsAddress,
AddressResolver: "dns-direct",
Strategy: configOpt.RemoteDnsDomainStrategy,
Detour: "select",
},
{
Tag: "dns-direct",
@@ -85,37 +95,6 @@ func BuildConfig(configOpt ConfigOptions, input option.Options) option.Options {
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
@@ -182,7 +161,8 @@ func BuildConfig(configOpt ConfigOptions, input option.Options) option.Options {
},
)
options.Inbounds = append(options.Inbounds,
options.Inbounds = append(
options.Inbounds,
option.Inbound{
Type: C.TypeDirect,
Tag: "dns-in",
@@ -197,59 +177,104 @@ func BuildConfig(configOpt ConfigOptions, input option.Options) option.Options {
},
)
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",
},
remoteDNSAddress := configOpt.RemoteDnsAddress
if strings.Contains(remoteDNSAddress, "://") {
remoteDNSAddress = strings.SplitAfter(remoteDNSAddress, "://")[1]
}
parsedUrl, err := url.Parse(fmt.Sprintf("https://%s", remoteDNSAddress))
if err == nil && net.ParseIP(parsedUrl.Host) == nil {
directDNSDomains = append(directDNSDomains, fmt.Sprintf("full:%s", parsedUrl.Host))
}
routeRules := []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{
ClashMode: "Direct",
Outbound: "direct",
},
},
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
ClashMode: "Global",
Outbound: "select",
},
},
}
if configOpt.BypassLAN {
routeRules = append(
routeRules,
option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
GeoIP: []string{"private"},
Outbound: "direct",
},
},
)
}
for _, rule := range configOpt.Rules {
routeRule := rule.MakeRule()
switch rule.Outbound {
case "bypass":
routeRule.Outbound = "direct"
case "block":
routeRule.Outbound = "block"
case "proxy":
routeRule.Outbound = "dns-out"
}
if routeRule.IsValid() {
routeRules = append(
routeRules,
option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: routeRule,
},
)
}
dnsRule := rule.MakeDNSRule()
switch rule.Outbound {
case "bypass":
dnsRule.Server = "dns-direct"
case "block":
dnsRule.Server = "dns-block"
dnsRule.DisableCache = true
case "proxy":
dnsRule.Server = "dns-remote"
}
if dnsRule.IsValid() {
options.DNS.Rules = append(
options.DNS.Rules,
option.DNSRule{
Type: C.RuleTypeDefault,
DefaultOptions: dnsRule,
},
)
}
}
options.Route = &option.RouteOptions{
Rules: routeRules,
AutoDetectInterface: true,
OverrideAndroidVPN: true,
}
@@ -257,6 +282,20 @@ func BuildConfig(configOpt ConfigOptions, input option.Options) option.Options {
var outbounds []option.Outbound
var tags []string
for _, out := range input.Outbounds {
jsonData, err := out.MarshalJSON()
if err == nil {
var obj map[string]interface{}
err = json.Unmarshal(jsonData, &obj)
if err == nil {
if value, ok := obj["server"]; ok {
server := value.(string)
if server != "" && net.ParseIP(server) == nil {
directDNSDomains = append(directDNSDomains, fmt.Sprintf("full:%s", server))
}
}
}
}
switch out.Type {
case C.TypeDirect, C.TypeBlock, C.TypeDNS:
continue
@@ -307,6 +346,14 @@ func BuildConfig(configOpt ConfigOptions, input option.Options) option.Options {
}...,
)
if len(directDNSDomains) > 0 {
domains := strings.Join(removeDuplicateStr(directDNSDomains), ",")
directRule := Rule{Domains: domains, Outbound: "bypass"}
dnsRule := directRule.MakeDNSRule()
dnsRule.Server = "dns-direct"
options.DNS.Rules = append([]option.DNSRule{{Type: C.RuleTypeDefault, DefaultOptions: dnsRule}}, options.DNS.Rules...)
}
return options
}
@@ -335,3 +382,15 @@ func applyOverrides(overrides ConfigOptions, options option.Options) option.Opti
return options
}
func removeDuplicateStr(strSlice []string) []string {
allKeys := make(map[string]bool)
list := []string{}
for _, item := range strSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}

95
shared/rules.go Normal file
View File

@@ -0,0 +1,95 @@
package shared
import (
"strconv"
"strings"
"github.com/sagernet/sing-box/option"
)
type Rule struct {
Domains string `json:"domains"`
IP string `json:"ip"`
Port string `json:"port"`
Network string `json:"network"`
Protocol string `json:"protocol"`
Outbound string `json:"outbound"`
}
func (r *Rule) MakeRule() option.DefaultRule {
rule := option.DefaultRule{}
if len(r.Domains) > 0 {
rule = makeDomainRule(rule, strings.Split(r.Domains, ","))
}
if len(r.IP) > 0 {
rule = makeIpRule(rule, strings.Split(r.IP, ","))
}
if len(r.Port) > 0 {
rule = makePortRule(rule, strings.Split(r.Port, ","))
}
if len(r.Network) > 0 {
rule.Network = append(rule.Network, r.Network)
}
if len(r.Protocol) > 0 {
rule.Protocol = append(rule.Protocol, strings.Split(r.Protocol, ",")...)
}
return rule
}
func (r *Rule) MakeDNSRule() option.DefaultDNSRule {
rule := option.DefaultDNSRule{}
domains := strings.Split(r.Domains, ",")
for _, item := range domains {
if strings.HasPrefix(item, "geosite:") {
rule.Geosite = append(rule.Geosite, strings.TrimPrefix(item, "geosite:"))
} else if strings.HasPrefix(item, "full:") {
rule.Domain = append(rule.Domain, strings.ToLower(strings.TrimPrefix(item, "full:")))
} else if strings.HasPrefix(item, "domain:") {
rule.DomainSuffix = append(rule.DomainSuffix, strings.ToLower(strings.TrimPrefix(item, "domain:")))
} else if strings.HasPrefix(item, "regexp:") {
rule.DomainRegex = append(rule.DomainRegex, strings.ToLower(strings.TrimPrefix(item, "regexp:")))
} else if strings.HasPrefix(item, "keyword:") {
rule.DomainKeyword = append(rule.DomainKeyword, strings.ToLower(strings.TrimPrefix(item, "keyword:")))
}
}
return rule
}
func makeDomainRule(options option.DefaultRule, list []string) option.DefaultRule {
for _, item := range list {
if strings.HasPrefix(item, "geosite:") {
options.Geosite = append(options.Geosite, strings.TrimPrefix(item, "geosite:"))
} else if strings.HasPrefix(item, "full:") {
options.Domain = append(options.Domain, strings.ToLower(strings.TrimPrefix(item, "full:")))
} else if strings.HasPrefix(item, "domain:") {
options.DomainSuffix = append(options.DomainSuffix, strings.ToLower(strings.TrimPrefix(item, "domain:")))
} else if strings.HasPrefix(item, "regexp:") {
options.DomainRegex = append(options.DomainRegex, strings.ToLower(strings.TrimPrefix(item, "regexp:")))
} else if strings.HasPrefix(item, "keyword:") {
options.DomainKeyword = append(options.DomainKeyword, strings.ToLower(strings.TrimPrefix(item, "keyword:")))
}
}
return options
}
func makeIpRule(options option.DefaultRule, list []string) option.DefaultRule {
for _, item := range list {
if strings.HasPrefix(item, "geoip:") {
options.GeoIP = append(options.GeoIP, strings.TrimPrefix(item, "geoip:"))
} else {
options.IPCIDR = append(options.IPCIDR, item)
}
}
return options
}
func makePortRule(options option.DefaultRule, list []string) option.DefaultRule {
for _, item := range list {
if strings.Contains(item, ":") {
options.PortRange = append(options.PortRange, item)
} else if i, err := strconv.Atoi(item); err == nil {
options.Port = append(options.Port, uint16(i))
}
}
return options
}