mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-19 16:30:07 +08:00
Some checks failed
Test / test (1.20, macos-13) (push) Has been cancelled
Test / test (1.20, macos-latest) (push) Has been cancelled
Test / test (1.20, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.20, ubuntu-latest) (push) Has been cancelled
Test / test (1.20, windows-latest) (push) Has been cancelled
Test / test (1.21, macos-13) (push) Has been cancelled
Test / test (1.21, macos-latest) (push) Has been cancelled
Test / test (1.21, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.21, ubuntu-latest) (push) Has been cancelled
Test / test (1.21, windows-latest) (push) Has been cancelled
Test / test (1.22, macos-13) (push) Has been cancelled
Test / test (1.22, macos-latest) (push) Has been cancelled
Test / test (1.22, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.22, ubuntu-latest) (push) Has been cancelled
Test / test (1.22, windows-latest) (push) Has been cancelled
Test / test (1.23, macos-13) (push) Has been cancelled
Test / test (1.23, macos-latest) (push) Has been cancelled
Test / test (1.23, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.23, ubuntu-latest) (push) Has been cancelled
Test / test (1.23, windows-latest) (push) Has been cancelled
Test / test (1.24, macos-13) (push) Has been cancelled
Test / test (1.24, macos-latest) (push) Has been cancelled
Test / test (1.24, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.24, ubuntu-latest) (push) Has been cancelled
Test / test (1.24, windows-latest) (push) Has been cancelled
Test / test (1.25, macos-13) (push) Has been cancelled
Test / test (1.25, macos-latest) (push) Has been cancelled
Test / test (1.25, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.25, ubuntu-latest) (push) Has been cancelled
Test / test (1.25, windows-latest) (push) Has been cancelled
Trigger CMFA Update / trigger-CMFA-update (push) Has been cancelled
When enabled, the TUN listener will use fake ping echo. This can be useful to prevent potential ICMP routing loops in certain network configurations.
1798 lines
62 KiB
Go
1798 lines
62 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"net/url"
|
|
"path/filepath"
|
|
"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
|
|
CacheMaxSize int
|
|
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
|
|
EchKey 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"`
|
|
CacheMaxSize int `yaml:"cache-max-size" json:"cache-max-size"`
|
|
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"`
|
|
LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,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"`
|
|
ExcludeSrcPort []uint16 `yaml:"exclude-src-port" json:"exclude-src-port,omitempty"`
|
|
ExcludeSrcPortRange []string `yaml:"exclude-src-port-range" json:"exclude-src-port-range,omitempty"`
|
|
ExcludeDstPort []uint16 `yaml:"exclude-dst-port" json:"exclude-dst-port,omitempty"`
|
|
ExcludeDstPortRange []string `yaml:"exclude-dst-port-range" json:"exclude-dst-port-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"`
|
|
DisableICMPForwarding bool `yaml:"disable-icmp-forwarding" json:"disable-icmp-forwarding,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"`
|
|
|
|
// darwin special config
|
|
RecvMsgX bool `yaml:"recvmsgx" json:"recvmsgx,omitempty"`
|
|
SendMsgX bool `yaml:"sendmsgx" json:"sendmsgx,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"`
|
|
EchKey string `yaml:"ech-key" json:"ech-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"`
|
|
}
|
|
|
|
// 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")},
|
|
RecvMsgX: true,
|
|
SendMsgX: false, // In the current implementation, if enabled, the kernel may freeze during multi-thread downloads, so it is disabled by default.
|
|
},
|
|
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) {
|
|
if path := cfg.ExternalUI; path != "" && !C.Path.IsSafePath(path) {
|
|
return nil, C.Path.ErrNotSafePath(path)
|
|
}
|
|
if uiName := cfg.ExternalUIName; uiName != "" && !filepath.IsLocal(uiName) {
|
|
return nil, fmt.Errorf("external UI name is not local: %s", uiName)
|
|
}
|
|
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,
|
|
EchKey: cfg.TLS.EchKey,
|
|
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
|
|
)
|
|
|
|
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())
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
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 {
|
|
tp, payload, target, params := RC.ParseRulePayload(line, true)
|
|
if target == "" {
|
|
return nil, fmt.Errorf("%s[%d] [%s] error: format invalid", format, idx, line)
|
|
}
|
|
|
|
if _, ok := proxies[target]; !ok {
|
|
if tp != "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)
|
|
}
|
|
}
|
|
|
|
parsed, parseErr := R.ParseRule(tp, 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())
|
|
}
|
|
|
|
var proxyName string
|
|
params := map[string]string{}
|
|
for _, s := range strings.Split(u.Fragment, "&") {
|
|
arr := strings.SplitN(s, "=", 2)
|
|
switch len(arr) {
|
|
case 1:
|
|
proxyName = arr[0]
|
|
case 2:
|
|
params[arr[0]] = arr[1]
|
|
}
|
|
}
|
|
|
|
var addr, dnsNetType 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 {
|
|
clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User}
|
|
addr = clearURL.String()
|
|
}
|
|
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,
|
|
CacheAlgorithm: cfg.CacheAlgorithm,
|
|
CacheMaxSize: cfg.CacheMaxSize,
|
|
}
|
|
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
|
|
}
|
|
|
|
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,
|
|
LoopbackAddress: rawTun.LoopbackAddress,
|
|
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,
|
|
ExcludeSrcPort: rawTun.ExcludeSrcPort,
|
|
ExcludeSrcPortRange: rawTun.ExcludeSrcPortRange,
|
|
ExcludeDstPort: rawTun.ExcludeDstPort,
|
|
ExcludeDstPortRange: rawTun.ExcludeDstPortRange,
|
|
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
|
IncludePackage: rawTun.IncludePackage,
|
|
ExcludePackage: rawTun.ExcludePackage,
|
|
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
|
UDPTimeout: rawTun.UDPTimeout,
|
|
DisableICMPForwarding: rawTun.DisableICMPForwarding,
|
|
FileDescriptor: rawTun.FileDescriptor,
|
|
|
|
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
|
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
|
Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
|
|
Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
|
|
|
|
RecvMsgX: rawTun.RecvMsgX,
|
|
SendMsgX: rawTun.SendMsgX,
|
|
}
|
|
|
|
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)
|
|
}
|