init
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/option"
|
||||
sjson "github.com/sagernet/sing/common/json"
|
||||
)
|
||||
|
||||
// Config is the parsed subset of a sing-box configuration relevant to routing.
|
||||
type Config struct {
|
||||
RouteRules []option.Rule
|
||||
RouteRuleSets []option.RuleSet
|
||||
RouteFinal string
|
||||
|
||||
DNSRules []option.DNSRule
|
||||
DNSFinal string
|
||||
DNSServers []DNSServerInfo
|
||||
|
||||
Warnings []string
|
||||
}
|
||||
|
||||
// ParseConfig parses a raw sing-box JSON (JSONC allowed) configuration into the
|
||||
// routing-relevant structures, using sing-box's own option unmarshalers so that
|
||||
// rule/action/rule-set dispatch is version-accurate.
|
||||
func ParseConfig(text string) (*Config, error) {
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" {
|
||||
return nil, fmt.Errorf("empty configuration")
|
||||
}
|
||||
ctx := context.Background()
|
||||
var raw map[string]json.RawMessage
|
||||
if err := sjson.UnmarshalContext(ctx, []byte(text), &raw); err != nil {
|
||||
return nil, fmt.Errorf("invalid JSON: %w", err)
|
||||
}
|
||||
|
||||
cfg := &Config{}
|
||||
|
||||
if rm, ok := raw["route"]; ok && len(rm) > 0 {
|
||||
var route struct {
|
||||
Rules []option.Rule `json:"rules"`
|
||||
RuleSet []option.RuleSet `json:"rule_set"`
|
||||
Final string `json:"final"`
|
||||
}
|
||||
if err := sjson.UnmarshalContext(ctx, rm, &route); err != nil {
|
||||
return nil, fmt.Errorf("route: %w", err)
|
||||
}
|
||||
cfg.RouteRules = route.Rules
|
||||
cfg.RouteRuleSets = route.RuleSet
|
||||
cfg.RouteFinal = route.Final
|
||||
}
|
||||
|
||||
if rm, ok := raw["dns"]; ok && len(rm) > 0 {
|
||||
var dnsSec struct {
|
||||
Rules []option.DNSRule `json:"rules"`
|
||||
Final string `json:"final"`
|
||||
Servers []json.RawMessage `json:"servers"`
|
||||
}
|
||||
if err := sjson.UnmarshalContext(ctx, rm, &dnsSec); err != nil {
|
||||
return nil, fmt.Errorf("dns: %w", err)
|
||||
}
|
||||
cfg.DNSRules = dnsSec.Rules
|
||||
cfg.DNSFinal = dnsSec.Final
|
||||
cfg.DNSServers = parseDNSServers(dnsSec.Servers)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// parseDNSServers extracts display metadata from dns.servers generically so we
|
||||
// don't need the DNS transport registry (which would pull in the whole protocol
|
||||
// dependency tree).
|
||||
func parseDNSServers(servers []json.RawMessage) []DNSServerInfo {
|
||||
var out []DNSServerInfo
|
||||
for _, raw := range servers {
|
||||
var m map[string]any
|
||||
if json.Unmarshal(raw, &m) != nil {
|
||||
continue
|
||||
}
|
||||
info := DNSServerInfo{
|
||||
Tag: asString(m["tag"]),
|
||||
Type: asString(m["type"]),
|
||||
Detour: asString(m["detour"]),
|
||||
Address: firstString(m, "server", "address"),
|
||||
}
|
||||
out = append(out, info)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func asString(v any) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func firstString(m map[string]any, keys ...string) string {
|
||||
for _, k := range keys {
|
||||
if s, ok := m[k].(string); ok && s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// findDNSServer returns the server info for a tag, if present.
|
||||
func (c *Config) findDNSServer(tag string) *DNSServerInfo {
|
||||
for i := range c.DNSServers {
|
||||
if c.DNSServers[i].Tag == tag {
|
||||
return &c.DNSServers[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// effectiveDNSFinal returns the DNS server tag used when no rule matches: the
|
||||
// configured dns.final, or the first server tag if unset.
|
||||
func (c *Config) effectiveDNSFinal() string {
|
||||
if c.DNSFinal != "" {
|
||||
return c.DNSFinal
|
||||
}
|
||||
if len(c.DNSServers) > 0 {
|
||||
return c.DNSServers[0].Tag
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user