package config import ( "container/list" "errors" "fmt" "net" "net/netip" "net/url" "strings" "time" _ "unsafe" "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/cidr" "github.com/metacubex/mihomo/component/fakeip" "github.com/metacubex/mihomo/component/geodata" P "github.com/metacubex/mihomo/component/process" "github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/sniffer" "github.com/metacubex/mihomo/component/trie" C "github.com/metacubex/mihomo/constant" providerTypes "github.com/metacubex/mihomo/constant/provider" snifferTypes "github.com/metacubex/mihomo/constant/sniffer" "github.com/metacubex/mihomo/dns" L "github.com/metacubex/mihomo/listener" LC "github.com/metacubex/mihomo/listener/config" "github.com/metacubex/mihomo/log" R "github.com/metacubex/mihomo/rules" RC "github.com/metacubex/mihomo/rules/common" RP "github.com/metacubex/mihomo/rules/provider" T "github.com/metacubex/mihomo/tunnel" orderedmap "github.com/wk8/go-ordered-map/v2" "golang.org/x/exp/slices" "gopkg.in/yaml.v3" ) // General config type General struct { Inbound Mode T.TunnelMode `json:"mode"` UnifiedDelay bool `json:"unified-delay"` LogLevel log.LogLevel `json:"log-level"` IPv6 bool `json:"ipv6"` Interface string `json:"interface-name"` RoutingMark int `json:"routing-mark"` GeoXUrl GeoXUrl `json:"geox-url"` GeoAutoUpdate bool `json:"geo-auto-update"` GeoUpdateInterval int `json:"geo-update-interval"` GeodataMode bool `json:"geodata-mode"` GeodataLoader string `json:"geodata-loader"` GeositeMatcher string `json:"geosite-matcher"` TCPConcurrent bool `json:"tcp-concurrent"` FindProcessMode P.FindProcessMode `json:"find-process-mode"` Sniffing bool `json:"sniffing"` GlobalClientFingerprint string `json:"global-client-fingerprint"` GlobalUA string `json:"global-ua"` ETagSupport bool `json:"etag-support"` KeepAliveIdle int `json:"keep-alive-idle"` KeepAliveInterval int `json:"keep-alive-interval"` DisableKeepAlive bool `json:"disable-keep-alive"` } // Inbound config type Inbound struct { Port int `json:"port"` SocksPort int `json:"socks-port"` RedirPort int `json:"redir-port"` TProxyPort int `json:"tproxy-port"` MixedPort int `json:"mixed-port"` Tun LC.Tun `json:"tun"` TuicServer LC.TuicServer `json:"tuic-server"` ShadowSocksConfig string `json:"ss-config"` VmessConfig string `json:"vmess-config"` Authentication []string `json:"authentication"` SkipAuthPrefixes []netip.Prefix `json:"skip-auth-prefixes"` LanAllowedIPs []netip.Prefix `json:"lan-allowed-ips"` LanDisAllowedIPs []netip.Prefix `json:"lan-disallowed-ips"` AllowLan bool `json:"allow-lan"` BindAddress string `json:"bind-address"` InboundTfo bool `json:"inbound-tfo"` InboundMPTCP bool `json:"inbound-mptcp"` } // GeoXUrl config type GeoXUrl struct { GeoIp string `json:"geo-ip"` Mmdb string `json:"mmdb"` ASN string `json:"asn"` GeoSite string `json:"geo-site"` } // Controller config type Controller struct { ExternalController string ExternalControllerTLS string ExternalControllerUnix string ExternalControllerPipe string ExternalUI string ExternalUIURL string ExternalUIName string ExternalDohServer string Secret string Cors Cors } type Cors struct { AllowOrigins []string AllowPrivateNetwork bool } // Experimental config type Experimental struct { Fingerprints []string QUICGoDisableGSO bool QUICGoDisableECN bool IP4PEnable bool } // IPTables config type IPTables struct { Enable bool InboundInterface string Bypass []string DnsRedirect bool } // NTP config type NTP struct { Enable bool Server string Port int Interval int DialerProxy string WriteToSystem bool } // DNS config type DNS struct { Enable bool PreferH3 bool IPv6 bool IPv6Timeout uint UseSystemHosts bool NameServer []dns.NameServer Fallback []dns.NameServer FallbackIPFilter []C.IpMatcher FallbackDomainFilter []C.DomainMatcher Listen string EnhancedMode C.DNSMode DefaultNameserver []dns.NameServer CacheAlgorithm string FakeIPRange *fakeip.Pool Hosts *trie.DomainTrie[resolver.HostValue] NameServerPolicy []dns.Policy ProxyServerNameserver []dns.NameServer DirectNameServer []dns.NameServer DirectFollowPolicy bool } // Profile config type Profile struct { StoreSelected bool StoreFakeIP bool } // TLS config type TLS struct { Certificate string PrivateKey string CustomTrustCert []string } // Config is mihomo config manager type Config struct { General *General Controller *Controller Experimental *Experimental IPTables *IPTables NTP *NTP DNS *DNS Hosts *trie.DomainTrie[resolver.HostValue] Profile *Profile Rules []C.Rule SubRules map[string][]C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy Listeners map[string]C.InboundListener Providers map[string]providerTypes.ProxyProvider RuleProviders map[string]providerTypes.RuleProvider Tunnels []LC.Tunnel Sniffer *sniffer.Config TLS *TLS } type RawCors struct { AllowOrigins []string `yaml:"allow-origins" json:"allow-origins"` AllowPrivateNetwork bool `yaml:"allow-private-network" json:"allow-private-network"` } type RawDNS struct { Enable bool `yaml:"enable" json:"enable"` PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"` IPv6 bool `yaml:"ipv6" json:"ipv6"` IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"` UseHosts bool `yaml:"use-hosts" json:"use-hosts"` UseSystemHosts bool `yaml:"use-system-hosts" json:"use-system-hosts"` RespectRules bool `yaml:"respect-rules" json:"respect-rules"` NameServer []string `yaml:"nameserver" json:"nameserver"` Fallback []string `yaml:"fallback" json:"fallback"` FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"` Listen string `yaml:"listen" json:"listen"` EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"` FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"` FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"` FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"` DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"` CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"` NameServerPolicy *orderedmap.OrderedMap[string, any] `yaml:"nameserver-policy" json:"nameserver-policy"` ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"` DirectNameServer []string `yaml:"direct-nameserver" json:"direct-nameserver"` DirectNameServerFollowPolicy bool `yaml:"direct-nameserver-follow-policy" json:"direct-nameserver-follow-policy"` } type RawFallbackFilter struct { GeoIP bool `yaml:"geoip" json:"geoip"` GeoIPCode string `yaml:"geoip-code" json:"geoip-code"` IPCIDR []string `yaml:"ipcidr" json:"ipcidr"` Domain []string `yaml:"domain" json:"domain"` GeoSite []string `yaml:"geosite" json:"geosite"` } type RawClashForAndroid struct { AppendSystemDNS bool `yaml:"append-system-dns" json:"append-system-dns"` UiSubtitlePattern string `yaml:"ui-subtitle-pattern" json:"ui-subtitle-pattern"` } type RawNTP struct { Enable bool `yaml:"enable" json:"enable"` Server string `yaml:"server" json:"server"` Port int `yaml:"port" json:"port"` Interval int `yaml:"interval" json:"interval"` DialerProxy string `yaml:"dialer-proxy" json:"dialer-proxy"` WriteToSystem bool `yaml:"write-to-system" json:"write-to-system"` } type RawTun struct { Enable bool `yaml:"enable" json:"enable"` Device string `yaml:"device" json:"device"` Stack C.TUNStack `yaml:"stack" json:"stack"` DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"` AutoRoute bool `yaml:"auto-route" json:"auto-route"` AutoDetectInterface bool `yaml:"auto-detect-interface"` MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` GSO bool `yaml:"gso" json:"gso,omitempty"` GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` //Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` IPRoute2TableIndex int `yaml:"iproute2-table-index" json:"iproute2-table-index,omitempty"` IPRoute2RuleIndex int `yaml:"iproute2-rule-index" json:"iproute2-rule-index,omitempty"` AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"` AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"` AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"` RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"` RouteExcludeAddress []netip.Prefix `yaml:"route-exclude-address" json:"route-exclude-address,omitempty"` RouteExcludeAddressSet []string `yaml:"route-exclude-address-set" json:"route-exclude-address-set,omitempty"` IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"` ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"` IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"` ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"` EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"` } type RawTuicServer struct { Enable bool `yaml:"enable" json:"enable"` Listen string `yaml:"listen" json:"listen"` Token []string `yaml:"token" json:"token"` Users map[string]string `yaml:"users" json:"users,omitempty"` Certificate string `yaml:"certificate" json:"certificate"` PrivateKey string `yaml:"private-key" json:"private-key"` CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"` MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"` AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"` ALPN []string `yaml:"alpn" json:"alpn,omitempty"` MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"` CWND int `yaml:"cwnd" json:"cwnd,omitempty"` } type RawIPTables struct { Enable bool `yaml:"enable" json:"enable"` InboundInterface string `yaml:"inbound-interface" json:"inbound-interface"` Bypass []string `yaml:"bypass" json:"bypass"` DnsRedirect bool `yaml:"dns-redirect" json:"dns-redirect"` } type RawExperimental struct { Fingerprints []string `yaml:"fingerprints"` QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"` QUICGoDisableECN bool `yaml:"quic-go-disable-ecn"` IP4PEnable bool `yaml:"dialer-ip4p-convert"` } type RawProfile struct { StoreSelected bool `yaml:"store-selected" json:"store-selected"` StoreFakeIP bool `yaml:"store-fake-ip" json:"store-fake-ip"` } type RawGeoXUrl struct { GeoIp string `yaml:"geoip" json:"geoip"` Mmdb string `yaml:"mmdb" json:"mmdb"` ASN string `yaml:"asn" json:"asn"` GeoSite string `yaml:"geosite" json:"geosite"` } type RawSniffer struct { Enable bool `yaml:"enable" json:"enable"` OverrideDest bool `yaml:"override-destination" json:"override-destination"` Sniffing []string `yaml:"sniffing" json:"sniffing"` ForceDomain []string `yaml:"force-domain" json:"force-domain"` SkipSrcAddress []string `yaml:"skip-src-address" json:"skip-src-address"` SkipDstAddress []string `yaml:"skip-dst-address" json:"skip-dst-address"` SkipDomain []string `yaml:"skip-domain" json:"skip-domain"` Ports []string `yaml:"port-whitelist" json:"port-whitelist"` ForceDnsMapping bool `yaml:"force-dns-mapping" json:"force-dns-mapping"` ParsePureIp bool `yaml:"parse-pure-ip" json:"parse-pure-ip"` Sniff map[string]RawSniffingConfig `yaml:"sniff" json:"sniff"` } type RawSniffingConfig struct { Ports []string `yaml:"ports" json:"ports"` OverrideDest *bool `yaml:"override-destination" json:"override-destination"` } type RawTLS struct { Certificate string `yaml:"certificate" json:"certificate"` PrivateKey string `yaml:"private-key" json:"private-key"` CustomTrustCert []string `yaml:"custom-certifactes" json:"custom-certifactes"` } type RawConfig struct { Port int `yaml:"port" json:"port"` SocksPort int `yaml:"socks-port" json:"socks-port"` RedirPort int `yaml:"redir-port" json:"redir-port"` TProxyPort int `yaml:"tproxy-port" json:"tproxy-port"` MixedPort int `yaml:"mixed-port" json:"mixed-port"` ShadowSocksConfig string `yaml:"ss-config" json:"ss-config"` VmessConfig string `yaml:"vmess-config" json:"vmess-config"` InboundTfo bool `yaml:"inbound-tfo" json:"inbound-tfo"` InboundMPTCP bool `yaml:"inbound-mptcp" json:"inbound-mptcp"` Authentication []string `yaml:"authentication" json:"authentication"` SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes" json:"skip-auth-prefixes"` LanAllowedIPs []netip.Prefix `yaml:"lan-allowed-ips" json:"lan-allowed-ips"` LanDisAllowedIPs []netip.Prefix `yaml:"lan-disallowed-ips" json:"lan-disallowed-ips"` AllowLan bool `yaml:"allow-lan" json:"allow-lan"` BindAddress string `yaml:"bind-address" json:"bind-address"` Mode T.TunnelMode `yaml:"mode" json:"mode"` UnifiedDelay bool `yaml:"unified-delay" json:"unified-delay"` LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` IPv6 bool `yaml:"ipv6" json:"ipv6"` ExternalController string `yaml:"external-controller" json:"external-controller"` ExternalControllerPipe string `yaml:"external-controller-pipe" json:"external-controller-pipe"` ExternalControllerUnix string `yaml:"external-controller-unix" json:"external-controller-unix"` ExternalControllerTLS string `yaml:"external-controller-tls" json:"external-controller-tls"` ExternalControllerCors RawCors `yaml:"external-controller-cors" json:"external-controller-cors"` ExternalUI string `yaml:"external-ui" json:"external-ui"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"` ExternalDohServer string `yaml:"external-doh-server" json:"external-doh-server"` Secret string `yaml:"secret" json:"secret"` Interface string `yaml:"interface-name" json:"interface-name"` RoutingMark int `yaml:"routing-mark" json:"routing-mark"` Tunnels []LC.Tunnel `yaml:"tunnels" json:"tunnels"` GeoAutoUpdate bool `yaml:"geo-auto-update" json:"geo-auto-update"` GeoUpdateInterval int `yaml:"geo-update-interval" json:"geo-update-interval"` GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"` GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"` GeositeMatcher string `yaml:"geosite-matcher" json:"geosite-matcher"` TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` GlobalClientFingerprint string `yaml:"global-client-fingerprint" json:"global-client-fingerprint"` GlobalUA string `yaml:"global-ua" json:"global-ua"` ETagSupport bool `yaml:"etag-support" json:"etag-support"` KeepAliveIdle int `yaml:"keep-alive-idle" json:"keep-alive-idle"` KeepAliveInterval int `yaml:"keep-alive-interval" json:"keep-alive-interval"` DisableKeepAlive bool `yaml:"disable-keep-alive" json:"disable-keep-alive"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers" json:"proxy-providers"` RuleProvider map[string]map[string]any `yaml:"rule-providers" json:"rule-providers"` Proxy []map[string]any `yaml:"proxies" json:"proxies"` ProxyGroup []map[string]any `yaml:"proxy-groups" json:"proxy-groups"` Rule []string `yaml:"rules" json:"rule"` SubRules map[string][]string `yaml:"sub-rules" json:"sub-rules"` Listeners []map[string]any `yaml:"listeners" json:"listeners"` Hosts map[string]any `yaml:"hosts" json:"hosts"` DNS RawDNS `yaml:"dns" json:"dns"` NTP RawNTP `yaml:"ntp" json:"ntp"` Tun RawTun `yaml:"tun" json:"tun"` TuicServer RawTuicServer `yaml:"tuic-server" json:"tuic-server"` IPTables RawIPTables `yaml:"iptables" json:"iptables"` Experimental RawExperimental `yaml:"experimental" json:"experimental"` Profile RawProfile `yaml:"profile" json:"profile"` GeoXUrl RawGeoXUrl `yaml:"geox-url" json:"geox-url"` Sniffer RawSniffer `yaml:"sniffer" json:"sniffer"` TLS RawTLS `yaml:"tls" json:"tls"` ClashForAndroid RawClashForAndroid `yaml:"clash-for-android" json:"clash-for-android"` } var ( GroupsList = list.New() ProxiesList = list.New() ParsingProxiesCallback func(groupsList *list.List, proxiesList *list.List) ) // Parse config func Parse(buf []byte) (*Config, error) { rawCfg, err := UnmarshalRawConfig(buf) if err != nil { return nil, err } return ParseRawConfig(rawCfg) } func DefaultRawConfig() *RawConfig { return &RawConfig{ AllowLan: false, BindAddress: "*", LanAllowedIPs: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0")}, IPv6: true, Mode: T.Rule, GeoAutoUpdate: false, GeoUpdateInterval: 24, GeodataMode: geodata.GeodataMode(), GeodataLoader: "memconservative", UnifiedDelay: false, Authentication: []string{}, LogLevel: log.INFO, Hosts: map[string]any{}, Rule: []string{}, Proxy: []map[string]any{}, ProxyGroup: []map[string]any{}, TCPConcurrent: false, FindProcessMode: P.FindProcessStrict, GlobalUA: "clash.meta/" + C.Version, ETagSupport: true, DNS: RawDNS{ Enable: false, IPv6: false, UseHosts: true, UseSystemHosts: true, IPv6Timeout: 100, EnhancedMode: C.DNSMapping, FakeIPRange: "198.18.0.1/16", FallbackFilter: RawFallbackFilter{ GeoIP: true, GeoIPCode: "CN", IPCIDR: []string{}, GeoSite: []string{}, }, DefaultNameserver: []string{ "114.114.114.114", "223.5.5.5", "8.8.8.8", "1.0.0.1", }, NameServer: []string{ "https://doh.pub/dns-query", "tls://223.5.5.5:853", }, FakeIPFilter: []string{ "dns.msftnsci.com", "www.msftnsci.com", "www.msftconnecttest.com", }, FakeIPFilterMode: C.FilterBlackList, }, NTP: RawNTP{ Enable: false, WriteToSystem: false, Server: "time.apple.com", Port: 123, Interval: 30, }, Tun: RawTun{ Enable: false, Device: "", Stack: C.TunGvisor, DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query AutoRoute: true, AutoDetectInterface: true, Inet6Address: []netip.Prefix{netip.MustParsePrefix("fdfe:dcba:9876::1/126")}, }, TuicServer: RawTuicServer{ Enable: false, Token: nil, Users: nil, Certificate: "", PrivateKey: "", Listen: "", CongestionController: "", MaxIdleTime: 15000, AuthenticationTimeout: 1000, ALPN: []string{"h3"}, MaxUdpRelayPacketSize: 1500, }, IPTables: RawIPTables{ Enable: false, InboundInterface: "lo", Bypass: []string{}, DnsRedirect: true, }, Experimental: RawExperimental{ // https://github.com/quic-go/quic-go/issues/4178 // Quic-go currently cannot automatically fall back on platforms that do not support ecn, so this feature is turned off by default. QUICGoDisableECN: true, }, Profile: RawProfile{ StoreSelected: true, }, GeoXUrl: RawGeoXUrl{ Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb", ASN: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb", GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat", GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat", }, Sniffer: RawSniffer{ Enable: false, Sniff: map[string]RawSniffingConfig{}, ForceDomain: []string{}, SkipDomain: []string{}, Ports: []string{}, ForceDnsMapping: true, ParsePureIp: true, OverrideDest: true, }, ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip", ExternalControllerCors: RawCors{ AllowOrigins: []string{"*"}, AllowPrivateNetwork: true, }, } } func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { // config with default value rawCfg := DefaultRawConfig() if err := yaml.Unmarshal(buf, rawCfg); err != nil { return nil, err } return rawCfg, nil } func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config := &Config{} log.Infoln("Start initial configuration in progress") //Segment finished in xxm startTime := time.Now() general, err := parseGeneral(rawCfg) if err != nil { return nil, err } config.General = general // We need to temporarily apply some configuration in general and roll back after parsing the complete configuration. // The loading and downloading of geodata in the parseRules and parseRuleProviders rely on these. // This implementation is very disgusting, but there is currently no better solution rollback := temporaryUpdateGeneral(config.General) defer rollback() controller, err := parseController(rawCfg) if err != nil { return nil, err } config.Controller = controller experimental, err := parseExperimental(rawCfg) if err != nil { return nil, err } config.Experimental = experimental iptables, err := parseIPTables(rawCfg) if err != nil { return nil, err } config.IPTables = iptables ntpCfg, err := parseNTP(rawCfg) if err != nil { return nil, err } config.NTP = ntpCfg profile, err := parseProfile(rawCfg) if err != nil { return nil, err } config.Profile = profile tlsCfg, err := parseTLS(rawCfg) if err != nil { return nil, err } config.TLS = tlsCfg proxies, providers, err := parseProxies(rawCfg) if err != nil { return nil, err } config.Proxies = proxies config.Providers = providers listener, err := parseListeners(rawCfg) if err != nil { return nil, err } config.Listeners = listener log.Infoln("Geodata Loader mode: %s", geodata.LoaderName()) log.Infoln("Geosite Matcher implementation: %s", geodata.SiteMatcherName()) ruleProviders, err := parseRuleProviders(rawCfg) if err != nil { return nil, err } config.RuleProviders = ruleProviders subRules, err := parseSubRules(rawCfg, proxies, ruleProviders) if err != nil { return nil, err } config.SubRules = subRules rules, err := parseRules(rawCfg.Rule, proxies, ruleProviders, subRules, "rules") if err != nil { return nil, err } config.Rules = rules hosts, err := parseHosts(rawCfg) if err != nil { return nil, err } config.Hosts = hosts dnsCfg, err := parseDNS(rawCfg, hosts, ruleProviders) if err != nil { return nil, err } config.DNS = dnsCfg err = parseTun(rawCfg.Tun, config.General) if err != nil { return nil, err } err = parseTuicServer(rawCfg.TuicServer, config.General) if err != nil { return nil, err } config.Users = parseAuthentication(rawCfg.Authentication) config.Tunnels = rawCfg.Tunnels // verify tunnels for _, t := range config.Tunnels { if len(t.Proxy) > 0 { if _, ok := config.Proxies[t.Proxy]; !ok { return nil, fmt.Errorf("tunnel proxy %s not found", t.Proxy) } } } config.Sniffer, err = parseSniffer(rawCfg.Sniffer, ruleProviders) if err != nil { return nil, err } elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm return config, nil } //go:linkname temporaryUpdateGeneral func temporaryUpdateGeneral(general *General) func() func parseGeneral(cfg *RawConfig) (*General, error) { return &General{ Inbound: Inbound{ Port: cfg.Port, SocksPort: cfg.SocksPort, RedirPort: cfg.RedirPort, TProxyPort: cfg.TProxyPort, MixedPort: cfg.MixedPort, ShadowSocksConfig: cfg.ShadowSocksConfig, VmessConfig: cfg.VmessConfig, AllowLan: cfg.AllowLan, SkipAuthPrefixes: cfg.SkipAuthPrefixes, LanAllowedIPs: cfg.LanAllowedIPs, LanDisAllowedIPs: cfg.LanDisAllowedIPs, BindAddress: cfg.BindAddress, InboundTfo: cfg.InboundTfo, InboundMPTCP: cfg.InboundMPTCP, }, UnifiedDelay: cfg.UnifiedDelay, Mode: cfg.Mode, LogLevel: cfg.LogLevel, IPv6: cfg.IPv6, Interface: cfg.Interface, RoutingMark: cfg.RoutingMark, GeoXUrl: GeoXUrl{ GeoIp: cfg.GeoXUrl.GeoIp, Mmdb: cfg.GeoXUrl.Mmdb, ASN: cfg.GeoXUrl.ASN, GeoSite: cfg.GeoXUrl.GeoSite, }, GeoAutoUpdate: cfg.GeoAutoUpdate, GeoUpdateInterval: cfg.GeoUpdateInterval, GeodataMode: cfg.GeodataMode, GeodataLoader: cfg.GeodataLoader, GeositeMatcher: cfg.GeositeMatcher, TCPConcurrent: cfg.TCPConcurrent, FindProcessMode: cfg.FindProcessMode, GlobalClientFingerprint: cfg.GlobalClientFingerprint, GlobalUA: cfg.GlobalUA, ETagSupport: cfg.ETagSupport, KeepAliveIdle: cfg.KeepAliveIdle, KeepAliveInterval: cfg.KeepAliveInterval, DisableKeepAlive: cfg.DisableKeepAlive, }, nil } func parseController(cfg *RawConfig) (*Controller, error) { return &Controller{ ExternalController: cfg.ExternalController, ExternalUI: cfg.ExternalUI, ExternalUIURL: cfg.ExternalUIURL, ExternalUIName: cfg.ExternalUIName, Secret: cfg.Secret, ExternalControllerPipe: cfg.ExternalControllerPipe, ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerTLS: cfg.ExternalControllerTLS, ExternalDohServer: cfg.ExternalDohServer, Cors: Cors{ AllowOrigins: cfg.ExternalControllerCors.AllowOrigins, AllowPrivateNetwork: cfg.ExternalControllerCors.AllowPrivateNetwork, }, }, nil } func parseExperimental(cfg *RawConfig) (*Experimental, error) { return &Experimental{ Fingerprints: cfg.Experimental.Fingerprints, QUICGoDisableGSO: cfg.Experimental.QUICGoDisableGSO, QUICGoDisableECN: cfg.Experimental.QUICGoDisableECN, IP4PEnable: cfg.Experimental.IP4PEnable, }, nil } func parseIPTables(cfg *RawConfig) (*IPTables, error) { return &IPTables{ Enable: cfg.IPTables.Enable, InboundInterface: cfg.IPTables.InboundInterface, Bypass: cfg.IPTables.Bypass, DnsRedirect: cfg.IPTables.DnsRedirect, }, nil } func parseNTP(cfg *RawConfig) (*NTP, error) { return &NTP{ Enable: cfg.NTP.Enable, Server: cfg.NTP.Server, Port: cfg.NTP.Port, Interval: cfg.NTP.Interval, DialerProxy: cfg.NTP.DialerProxy, WriteToSystem: cfg.NTP.WriteToSystem, }, nil } func parseProfile(cfg *RawConfig) (*Profile, error) { return &Profile{ StoreSelected: cfg.Profile.StoreSelected, StoreFakeIP: cfg.Profile.StoreFakeIP, }, nil } func parseTLS(cfg *RawConfig) (*TLS, error) { return &TLS{ Certificate: cfg.TLS.Certificate, PrivateKey: cfg.TLS.PrivateKey, CustomTrustCert: cfg.TLS.CustomTrustCert, }, nil } func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) { proxies = make(map[string]C.Proxy) providersMap = make(map[string]providerTypes.ProxyProvider) proxiesConfig := cfg.Proxy groupsConfig := cfg.ProxyGroup providersConfig := cfg.ProxyProvider var ( proxyList []string AllProxies []string hasGlobal bool ) proxiesList := list.New() groupsList := list.New() proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect()) proxies["REJECT"] = adapter.NewProxy(outbound.NewReject()) proxies["REJECT-DROP"] = adapter.NewProxy(outbound.NewRejectDrop()) proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible()) proxies["PASS"] = adapter.NewProxy(outbound.NewPass()) proxyList = append(proxyList, "DIRECT", "REJECT") // parse proxy for idx, mapping := range proxiesConfig { proxy, err := adapter.ParseProxy(mapping) if err != nil { return nil, nil, fmt.Errorf("proxy %d: %w", idx, err) } if _, exist := proxies[proxy.Name()]; exist { return nil, nil, fmt.Errorf("proxy %s is the duplicate name", proxy.Name()) } proxies[proxy.Name()] = proxy proxyList = append(proxyList, proxy.Name()) AllProxies = append(AllProxies, proxy.Name()) proxiesList.PushBack(mapping) } // keep the original order of ProxyGroups in config file for idx, mapping := range groupsConfig { groupName, existName := mapping["name"].(string) if !existName { return nil, nil, fmt.Errorf("proxy group %d: missing name", idx) } if groupName == "GLOBAL" { hasGlobal = true } proxyList = append(proxyList, groupName) groupsList.PushBack(mapping) } // check if any loop exists and sort the ProxyGroups if err := proxyGroupsDagSort(groupsConfig); err != nil { return nil, nil, err } var AllProviders []string // parse and initial providers for name, mapping := range providersConfig { if name == provider.ReservedName { return nil, nil, fmt.Errorf("can not defined a provider called `%s`", provider.ReservedName) } pd, err := provider.ParseProxyProvider(name, mapping) if err != nil { return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err) } providersMap[name] = pd AllProviders = append(AllProviders, name) } slices.Sort(AllProxies) slices.Sort(AllProviders) // parse proxy group for idx, mapping := range groupsConfig { group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap, AllProxies, AllProviders) if err != nil { return nil, nil, fmt.Errorf("proxy group[%d]: %w", idx, err) } groupName := group.Name() if _, exist := proxies[groupName]; exist { return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName) } proxies[groupName] = adapter.NewProxy(group) } var ps []C.Proxy for _, v := range proxyList { if proxies[v].Type() == C.Pass { continue } ps = append(ps, proxies[v]) } hc := provider.NewHealthCheck(ps, "", 5000, 0, true, nil) pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc) providersMap[provider.ReservedName] = pd if !hasGlobal { global := outboundgroup.NewSelector( &outboundgroup.GroupCommonOption{ Name: "GLOBAL", }, []providerTypes.ProxyProvider{pd}, ) proxies["GLOBAL"] = adapter.NewProxy(global) } ProxiesList = proxiesList GroupsList = groupsList if ParsingProxiesCallback != nil { // refresh tray menu go ParsingProxiesCallback(GroupsList, ProxiesList) } return proxies, providersMap, nil } func parseListeners(cfg *RawConfig) (listeners map[string]C.InboundListener, err error) { listeners = make(map[string]C.InboundListener) for index, mapping := range cfg.Listeners { listener, err := L.ParseListener(mapping) if err != nil { return nil, fmt.Errorf("proxy %d: %w", index, err) } if _, exist := mapping[listener.Name()]; exist { return nil, fmt.Errorf("listener %s is the duplicate name", listener.Name()) } listeners[listener.Name()] = listener } return } func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.RuleProvider, err error) { RP.SetTunnel(T.Tunnel) ruleProviders = map[string]providerTypes.RuleProvider{} // parse rule provider for name, mapping := range cfg.RuleProvider { rp, err := RP.ParseRuleProvider(name, mapping, R.ParseRule) if err != nil { return nil, err } ruleProviders[name] = rp } return } func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy, ruleProviders map[string]providerTypes.RuleProvider) (subRules map[string][]C.Rule, err error) { subRules = map[string][]C.Rule{} for name := range cfg.SubRules { subRules[name] = make([]C.Rule, 0) } for name, rawRules := range cfg.SubRules { if len(name) == 0 { return nil, fmt.Errorf("sub-rule name is empty") } var rules []C.Rule rules, err = parseRules(rawRules, proxies, ruleProviders, subRules, fmt.Sprintf("sub-rules[%s]", name)) if err != nil { return nil, err } subRules[name] = rules } if err = verifySubRule(subRules); err != nil { return nil, err } return } func verifySubRule(subRules map[string][]C.Rule) error { for name := range subRules { err := verifySubRuleCircularReferences(name, subRules, []string{}) if err != nil { return err } } return nil } func verifySubRuleCircularReferences(n string, subRules map[string][]C.Rule, arr []string) error { isInArray := func(v string, array []string) bool { for _, c := range array { if v == c { return true } } return false } arr = append(arr, n) for i, rule := range subRules[n] { if rule.RuleType() == C.SubRules { if _, ok := subRules[rule.Adapter()]; !ok { return fmt.Errorf("sub-rule[%d:%s] error: [%s] not found", i, n, rule.Adapter()) } if isInArray(rule.Adapter(), arr) { arr = append(arr, rule.Adapter()) return fmt.Errorf("sub-rule error: circular references [%s]", strings.Join(arr, "->")) } if err := verifySubRuleCircularReferences(rule.Adapter(), subRules, arr); err != nil { return err } } } return nil } func parseRules(rulesConfig []string, proxies map[string]C.Proxy, ruleProviders map[string]providerTypes.RuleProvider, subRules map[string][]C.Rule, format string) ([]C.Rule, error) { var rules []C.Rule // parse rules for idx, line := range rulesConfig { rule := trimArr(strings.Split(line, ",")) var ( payload string target string params []string ruleName = strings.ToUpper(rule[0]) ) l := len(rule) if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" || ruleName == "SUB-RULE" || ruleName == "DOMAIN-REGEX" || ruleName == "PROCESS-NAME-REGEX" || ruleName == "PROCESS-PATH-REGEX" { target = rule[l-1] payload = strings.Join(rule[1:l-1], ",") } else { if l < 2 { return nil, fmt.Errorf("%s[%d] [%s] error: format invalid", format, idx, line) } if l < 4 { rule = append(rule, make([]string, 4-l)...) } if ruleName == "MATCH" { l = 2 } if l >= 3 { l = 3 payload = rule[1] } target = rule[l-1] params = rule[l:] } if _, ok := proxies[target]; !ok { if ruleName != "SUB-RULE" { return nil, fmt.Errorf("%s[%d] [%s] error: proxy [%s] not found", format, idx, line, target) } else if _, ok = subRules[target]; !ok { return nil, fmt.Errorf("%s[%d] [%s] error: sub-rule [%s] not found", format, idx, line, target) } } params = trimArr(params) parsed, parseErr := R.ParseRule(ruleName, payload, target, params, subRules) if parseErr != nil { return nil, fmt.Errorf("%s[%d] [%s] error: %s", format, idx, line, parseErr.Error()) } for _, name := range parsed.ProviderNames() { if _, ok := ruleProviders[name]; !ok { return nil, fmt.Errorf("%s[%d] [%s] error: rule set [%s] not found", format, idx, line, name) } } rules = append(rules, parsed) } return rules, nil } func parseHosts(cfg *RawConfig) (*trie.DomainTrie[resolver.HostValue], error) { tree := trie.New[resolver.HostValue]() // add default hosts hostValue, _ := resolver.NewHostValueByIPs( []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1})}) if err := tree.Insert("localhost", hostValue); err != nil { log.Errorln("insert localhost to host error: %s", err.Error()) } if len(cfg.Hosts) != 0 { for domain, anyValue := range cfg.Hosts { if str, ok := anyValue.(string); ok && str == "lan" { if addrs, err := net.InterfaceAddrs(); err != nil { log.Errorln("insert lan to host error: %s", err) } else { ips := make([]netip.Addr, 0) for _, addr := range addrs { if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && !ipnet.IP.IsLinkLocalUnicast() { if ip, err := netip.ParseAddr(ipnet.IP.String()); err == nil { ips = append(ips, ip) } } } anyValue = ips } } value, err := resolver.NewHostValue(anyValue) if err != nil { return nil, fmt.Errorf("%s is not a valid value", anyValue) } if value.IsDomain { node := tree.Search(value.Domain) for node != nil && node.Data().IsDomain { if node.Data().Domain == domain { return nil, fmt.Errorf("%s, there is a cycle in domain name mapping", domain) } node = tree.Search(node.Data().Domain) } } _ = tree.Insert(domain, value) } } tree.Optimize() return tree, nil } func hostWithDefaultPort(host string, defPort string) (string, error) { hostname, port, err := net.SplitHostPort(host) if err != nil { if !strings.Contains(err.Error(), "missing port in address") { return "", err } host = host + ":" + defPort if hostname, port, err = net.SplitHostPort(host); err != nil { return "", err } } return net.JoinHostPort(hostname, port), nil } func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.NameServer, error) { var nameservers []dns.NameServer for idx, server := range servers { server = parsePureDNSServer(server) u, err := url.Parse(server) if err != nil { return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) } proxyName := u.Fragment var addr, dnsNetType string params := map[string]string{} switch u.Scheme { case "udp": addr, err = hostWithDefaultPort(u.Host, "53") dnsNetType = "" // UDP case "tcp": addr, err = hostWithDefaultPort(u.Host, "53") dnsNetType = "tcp" // TCP case "tls": addr, err = hostWithDefaultPort(u.Host, "853") dnsNetType = "tcp-tls" // DNS over TLS case "http", "https": addr, err = hostWithDefaultPort(u.Host, "443") dnsNetType = "https" // DNS over HTTPS if u.Scheme == "http" { addr, err = hostWithDefaultPort(u.Host, "80") } if err == nil { proxyName = "" clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User} addr = clearURL.String() if len(u.Fragment) != 0 { for _, s := range strings.Split(u.Fragment, "&") { arr := strings.Split(s, "=") if len(arr) == 0 { continue } else if len(arr) == 1 { proxyName = arr[0] } else if len(arr) == 2 { params[arr[0]] = arr[1] } else { params[arr[0]] = strings.Join(arr[1:], "=") } } } } case "quic": addr, err = hostWithDefaultPort(u.Host, "853") dnsNetType = "quic" // DNS over QUIC case "system": dnsNetType = "system" // System DNS case "dhcp": addr = server[len("dhcp://"):] // some special notation cannot be parsed by url dnsNetType = "dhcp" // UDP from DHCP if addr == "system" { // Compatible with old writing "dhcp://system" dnsNetType = "system" addr = "" } case "rcode": dnsNetType = "rcode" addr = u.Host switch addr { case "success", "format_error", "server_failure", "name_error", "not_implemented", "refused": default: err = fmt.Errorf("unsupported RCode type: %s", addr) } default: return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) } if err != nil { return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) } if respectRules && len(proxyName) == 0 { proxyName = dns.RespectRules } nameserver := dns.NameServer{ Net: dnsNetType, Addr: addr, ProxyName: proxyName, Params: params, PreferH3: preferH3, } if slices.ContainsFunc(nameservers, nameserver.Equal) { continue // skip duplicates nameserver } nameservers = append(nameservers, nameserver) } return nameservers, nil } func init() { dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard return parseNameServer(servers, false, false) } } func parsePureDNSServer(server string) string { addPre := func(server string) string { return "udp://" + server } if server == "system" { return "system://" } if ip, err := netip.ParseAddr(server); err != nil { if strings.Contains(server, "://") { return server } return addPre(server) } else { if ip.Is4() { return addPre(server) } else { return addPre("[" + server + "]") } } } func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) ([]dns.Policy, error) { var policy []dns.Policy for pair := nsPolicy.Oldest(); pair != nil; pair = pair.Next() { k, v := pair.Key, pair.Value servers, err := utils.ToStringSlice(v) if err != nil { return nil, err } nameservers, err := parseNameServer(servers, respectRules, preferH3) if err != nil { return nil, err } kLower := strings.ToLower(k) if strings.Contains(kLower, ",") { if strings.Contains(kLower, "geosite:") { subkeys := strings.Split(k, ":") subkeys = subkeys[1:] subkeys = strings.Split(subkeys[0], ",") for _, subkey := range subkeys { newKey := "geosite:" + subkey policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers}) } } else if strings.Contains(kLower, "rule-set:") { subkeys := strings.Split(k, ":") subkeys = subkeys[1:] subkeys = strings.Split(subkeys[0], ",") for _, subkey := range subkeys { newKey := "rule-set:" + subkey policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers}) } } else { subkeys := strings.Split(k, ",") for _, subkey := range subkeys { policy = append(policy, dns.Policy{Domain: subkey, NameServers: nameservers}) } } } else { if strings.Contains(kLower, "geosite:") { policy = append(policy, dns.Policy{Domain: "geosite:" + k[8:], NameServers: nameservers}) } else if strings.Contains(kLower, "rule-set:") { policy = append(policy, dns.Policy{Domain: "rule-set:" + k[9:], NameServers: nameservers}) } else { policy = append(policy, dns.Policy{Domain: k, NameServers: nameservers}) } } } for idx, p := range policy { domain, nameservers := p.Domain, p.NameServers if strings.HasPrefix(domain, "rule-set:") { domainSetName := domain[9:] matcher, err := parseDomainRuleSet(domainSetName, "dns.nameserver-policy", ruleProviders) if err != nil { return nil, err } policy[idx] = dns.Policy{Matcher: matcher, NameServers: nameservers} } else if strings.HasPrefix(domain, "geosite:") { country := domain[8:] matcher, err := RC.NewGEOSITE(country, "dns.nameserver-policy") if err != nil { return nil, err } policy[idx] = dns.Policy{Matcher: matcher, NameServers: nameservers} } else { if _, valid := trie.ValidAndSplitDomain(domain); !valid { return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain) } } } return policy, nil } func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) { cfg := rawCfg.DNS if cfg.Enable && len(cfg.NameServer) == 0 { return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty") } if cfg.RespectRules && len(cfg.ProxyServerNameserver) == 0 { return nil, fmt.Errorf("if “respect-rules” is turned on, “proxy-server-nameserver” cannot be empty") } dnsCfg := &DNS{ Enable: cfg.Enable, Listen: cfg.Listen, PreferH3: cfg.PreferH3, IPv6Timeout: cfg.IPv6Timeout, IPv6: cfg.IPv6, UseSystemHosts: cfg.UseSystemHosts, EnhancedMode: cfg.EnhancedMode, } var err error if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.RespectRules, cfg.PreferH3); err != nil { return nil, err } if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback, cfg.RespectRules, cfg.PreferH3); err != nil { return nil, err } if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil { return nil, err } if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver, false, cfg.PreferH3); err != nil { return nil, err } if dnsCfg.DirectNameServer, err = parseNameServer(cfg.DirectNameServer, false, cfg.PreferH3); err != nil { return nil, err } dnsCfg.DirectFollowPolicy = cfg.DirectNameServerFollowPolicy if len(cfg.DefaultNameserver) == 0 { return nil, errors.New("default nameserver should have at least one nameserver") } if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver, false, cfg.PreferH3); err != nil { return nil, err } // check default nameserver is pure ip addr for _, ns := range dnsCfg.DefaultNameserver { if ns.Net == "system" { continue } host, _, err := net.SplitHostPort(ns.Addr) if err != nil || net.ParseIP(host) == nil { u, err := url.Parse(ns.Addr) if err == nil && net.ParseIP(u.Host) == nil { if ip, _, err := net.SplitHostPort(u.Host); err != nil || net.ParseIP(ip) == nil { return nil, errors.New("default nameserver should be pure IP") } } } } fakeIPRange, err := netip.ParsePrefix(cfg.FakeIPRange) T.SetFakeIPRange(fakeIPRange) if cfg.EnhancedMode == C.DNSFakeIP { if err != nil { return nil, err } var fakeIPTrie *trie.DomainTrie[struct{}] if len(dnsCfg.Fallback) != 0 { fakeIPTrie = trie.New[struct{}]() for _, fb := range dnsCfg.Fallback { if net.ParseIP(fb.Addr) != nil { continue } _ = fakeIPTrie.Insert(fb.Addr, struct{}{}) } } // fake ip skip host filter host, err := parseDomain(cfg.FakeIPFilter, fakeIPTrie, "dns.fake-ip-filter", ruleProviders) if err != nil { return nil, err } pool, err := fakeip.New(fakeip.Options{ IPNet: fakeIPRange, Size: 1000, Host: host, Mode: cfg.FakeIPFilterMode, Persistence: rawCfg.Profile.StoreFakeIP, }) if err != nil { return nil, err } dnsCfg.FakeIPRange = pool } if len(cfg.Fallback) != 0 { if cfg.FallbackFilter.GeoIP { matcher, err := RC.NewGEOIP(cfg.FallbackFilter.GeoIPCode, "dns.fallback-filter.geoip", false, true) if err != nil { return nil, fmt.Errorf("load GeoIP dns fallback filter error, %w", err) } dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, matcher.DnsFallbackFilter()) } if len(cfg.FallbackFilter.IPCIDR) > 0 { cidrSet := cidr.NewIpCidrSet() for idx, ipcidr := range cfg.FallbackFilter.IPCIDR { err = cidrSet.AddIpCidrForString(ipcidr) if err != nil { return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %w", idx, err) } } err = cidrSet.Merge() if err != nil { return nil, err } matcher := cidrSet // dns.fallback-filter.ipcidr dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, matcher) } if len(cfg.FallbackFilter.Domain) > 0 { domainTrie := trie.New[struct{}]() for idx, domain := range cfg.FallbackFilter.Domain { err = domainTrie.Insert(domain, struct{}{}) if err != nil { return nil, fmt.Errorf("DNS FallbackDomain[%d] format error: %w", idx, err) } } matcher := domainTrie.NewDomainSet() // dns.fallback-filter.domain dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, matcher) } if len(cfg.FallbackFilter.GeoSite) > 0 { log.Warnln("replace fallback-filter.geosite with nameserver-policy, it will be removed in the future") for idx, geoSite := range cfg.FallbackFilter.GeoSite { matcher, err := RC.NewGEOSITE(geoSite, "dns.fallback-filter.geosite") if err != nil { return nil, fmt.Errorf("DNS FallbackGeosite[%d] format error: %w", idx, err) } dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, matcher) } } } if cfg.UseHosts { dnsCfg.Hosts = hosts } if cfg.CacheAlgorithm == "" || cfg.CacheAlgorithm == "lru" { dnsCfg.CacheAlgorithm = "lru" } else { dnsCfg.CacheAlgorithm = "arc" } return dnsCfg, nil } func parseAuthentication(rawRecords []string) []auth.AuthUser { var users []auth.AuthUser for _, line := range rawRecords { if user, pass, found := strings.Cut(line, ":"); found { users = append(users, auth.AuthUser{User: user, Pass: pass}) } } return users } func parseTun(rawTun RawTun, general *General) error { tunAddressPrefix := T.FakeIPRange() if !tunAddressPrefix.IsValid() { tunAddressPrefix = netip.MustParsePrefix("198.18.0.1/16") } tunAddressPrefix = netip.PrefixFrom(tunAddressPrefix.Addr(), 30) if !general.IPv6 || !verifyIP6() { rawTun.Inet6Address = nil } general.Tun = LC.Tun{ Enable: rawTun.Enable, Device: rawTun.Device, Stack: rawTun.Stack, DNSHijack: rawTun.DNSHijack, AutoRoute: rawTun.AutoRoute, AutoDetectInterface: rawTun.AutoDetectInterface, MTU: rawTun.MTU, GSO: rawTun.GSO, GSOMaxSize: rawTun.GSOMaxSize, Inet4Address: []netip.Prefix{tunAddressPrefix}, Inet6Address: rawTun.Inet6Address, IPRoute2TableIndex: rawTun.IPRoute2TableIndex, IPRoute2RuleIndex: rawTun.IPRoute2RuleIndex, AutoRedirect: rawTun.AutoRedirect, AutoRedirectInputMark: rawTun.AutoRedirectInputMark, AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark, StrictRoute: rawTun.StrictRoute, RouteAddress: rawTun.RouteAddress, RouteAddressSet: rawTun.RouteAddressSet, RouteExcludeAddress: rawTun.RouteExcludeAddress, RouteExcludeAddressSet: rawTun.RouteExcludeAddressSet, IncludeInterface: rawTun.IncludeInterface, ExcludeInterface: rawTun.ExcludeInterface, IncludeUID: rawTun.IncludeUID, IncludeUIDRange: rawTun.IncludeUIDRange, ExcludeUID: rawTun.ExcludeUID, ExcludeUIDRange: rawTun.ExcludeUIDRange, IncludeAndroidUser: rawTun.IncludeAndroidUser, IncludePackage: rawTun.IncludePackage, ExcludePackage: rawTun.ExcludePackage, EndpointIndependentNat: rawTun.EndpointIndependentNat, UDPTimeout: rawTun.UDPTimeout, FileDescriptor: rawTun.FileDescriptor, Inet4RouteAddress: rawTun.Inet4RouteAddress, Inet6RouteAddress: rawTun.Inet6RouteAddress, Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress, Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress, } return nil } func parseTuicServer(rawTuic RawTuicServer, general *General) error { general.TuicServer = LC.TuicServer{ Enable: rawTuic.Enable, Listen: rawTuic.Listen, Token: rawTuic.Token, Users: rawTuic.Users, Certificate: rawTuic.Certificate, PrivateKey: rawTuic.PrivateKey, CongestionController: rawTuic.CongestionController, MaxIdleTime: rawTuic.MaxIdleTime, AuthenticationTimeout: rawTuic.AuthenticationTimeout, ALPN: rawTuic.ALPN, MaxUdpRelayPacketSize: rawTuic.MaxUdpRelayPacketSize, CWND: rawTuic.CWND, } return nil } func parseSniffer(snifferRaw RawSniffer, ruleProviders map[string]providerTypes.RuleProvider) (*sniffer.Config, error) { snifferConfig := &sniffer.Config{ Enable: snifferRaw.Enable, ForceDnsMapping: snifferRaw.ForceDnsMapping, ParsePureIp: snifferRaw.ParsePureIp, } loadSniffer := make(map[snifferTypes.Type]sniffer.SnifferConfig) if len(snifferRaw.Sniff) != 0 { for sniffType, sniffConfig := range snifferRaw.Sniff { find := false ports, err := utils.NewUnsignedRangesFromList[uint16](sniffConfig.Ports) if err != nil { return nil, err } overrideDest := snifferRaw.OverrideDest if sniffConfig.OverrideDest != nil { overrideDest = *sniffConfig.OverrideDest } for _, snifferType := range snifferTypes.List { if snifferType.String() == strings.ToUpper(sniffType) { find = true loadSniffer[snifferType] = sniffer.SnifferConfig{ Ports: ports, OverrideDest: overrideDest, } } } if !find { return nil, fmt.Errorf("not find the sniffer[%s]", sniffType) } } } else { if snifferConfig.Enable && len(snifferRaw.Sniffing) != 0 { // Deprecated: Use Sniff instead log.Warnln("Deprecated: Use Sniff instead") } globalPorts, err := utils.NewUnsignedRangesFromList[uint16](snifferRaw.Ports) if err != nil { return nil, err } for _, snifferName := range snifferRaw.Sniffing { find := false for _, snifferType := range snifferTypes.List { if snifferType.String() == strings.ToUpper(snifferName) { find = true loadSniffer[snifferType] = sniffer.SnifferConfig{ Ports: globalPorts, OverrideDest: snifferRaw.OverrideDest, } } } if !find { return nil, fmt.Errorf("not find the sniffer[%s]", snifferName) } } } snifferConfig.Sniffers = loadSniffer forceDomain, err := parseDomain(snifferRaw.ForceDomain, nil, "sniffer.force-domain", ruleProviders) if err != nil { return nil, fmt.Errorf("error in force-domain, error:%w", err) } snifferConfig.ForceDomain = forceDomain skipSrcAddress, err := parseIPCIDR(snifferRaw.SkipSrcAddress, nil, "sniffer.skip-src-address", ruleProviders) if err != nil { return nil, fmt.Errorf("error in skip-src-address, error:%w", err) } snifferConfig.SkipSrcAddress = skipSrcAddress skipDstAddress, err := parseIPCIDR(snifferRaw.SkipDstAddress, nil, "sniffer.skip-src-address", ruleProviders) if err != nil { return nil, fmt.Errorf("error in skip-dst-address, error:%w", err) } snifferConfig.SkipDstAddress = skipDstAddress skipDomain, err := parseDomain(snifferRaw.SkipDomain, nil, "sniffer.skip-domain", ruleProviders) if err != nil { return nil, fmt.Errorf("error in skip-domain, error:%w", err) } snifferConfig.SkipDomain = skipDomain return snifferConfig, nil } func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (matchers []C.IpMatcher, err error) { var matcher C.IpMatcher for _, ipcidr := range addresses { ipcidrLower := strings.ToLower(ipcidr) if strings.Contains(ipcidrLower, "geoip:") { subkeys := strings.Split(ipcidr, ":") subkeys = subkeys[1:] subkeys = strings.Split(subkeys[0], ",") for _, country := range subkeys { matcher, err = RC.NewGEOIP(country, adapterName, false, false) if err != nil { return nil, err } matchers = append(matchers, matcher) } } else if strings.Contains(ipcidrLower, "rule-set:") { subkeys := strings.Split(ipcidr, ":") subkeys = subkeys[1:] subkeys = strings.Split(subkeys[0], ",") for _, domainSetName := range subkeys { matcher, err = parseIPRuleSet(domainSetName, adapterName, ruleProviders) if err != nil { return nil, err } matchers = append(matchers, matcher) } } else { if cidrSet == nil { cidrSet = cidr.NewIpCidrSet() } err = cidrSet.AddIpCidrForString(ipcidr) if err != nil { return nil, err } } } if !cidrSet.IsEmpty() { err = cidrSet.Merge() if err != nil { return nil, err } matcher = cidrSet matchers = append(matchers, matcher) } return } func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (matchers []C.DomainMatcher, err error) { var matcher C.DomainMatcher for _, domain := range domains { domainLower := strings.ToLower(domain) if strings.Contains(domainLower, "geosite:") { subkeys := strings.Split(domain, ":") subkeys = subkeys[1:] subkeys = strings.Split(subkeys[0], ",") for _, country := range subkeys { matcher, err = RC.NewGEOSITE(country, adapterName) if err != nil { return nil, err } matchers = append(matchers, matcher) } } else if strings.Contains(domainLower, "rule-set:") { subkeys := strings.Split(domain, ":") subkeys = subkeys[1:] subkeys = strings.Split(subkeys[0], ",") for _, domainSetName := range subkeys { matcher, err = parseDomainRuleSet(domainSetName, adapterName, ruleProviders) if err != nil { return nil, err } matchers = append(matchers, matcher) } } else { if domainTrie == nil { domainTrie = trie.New[struct{}]() } err = domainTrie.Insert(domain, struct{}{}) if err != nil { return nil, err } } } if !domainTrie.IsEmpty() { matcher = domainTrie.NewDomainSet() matchers = append(matchers, matcher) } return } func parseIPRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.IpMatcher, error) { if rp, ok := ruleProviders[domainSetName]; !ok { return nil, fmt.Errorf("not found rule-set: %s", domainSetName) } else { switch rp.Behavior() { case providerTypes.Domain: return nil, fmt.Errorf("rule provider type error, except ipcidr,actual %s", rp.Behavior()) case providerTypes.Classical: log.Warnln("%s provider is %s, only matching it contain ip rule", rp.Name(), rp.Behavior()) default: } } return RP.NewRuleSet(domainSetName, adapterName, false, true) } func parseDomainRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.DomainMatcher, error) { if rp, ok := ruleProviders[domainSetName]; !ok { return nil, fmt.Errorf("not found rule-set: %s", domainSetName) } else { switch rp.Behavior() { case providerTypes.IPCIDR: return nil, fmt.Errorf("rule provider type error, except domain,actual %s", rp.Behavior()) case providerTypes.Classical: log.Warnln("%s provider is %s, only matching it contain domain rule", rp.Name(), rp.Behavior()) default: } } return RP.NewRuleSet(domainSetName, adapterName, false, true) }