mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-09 13:18:46 +00:00
Xray-core: Refactor geodata (#5814)
https://github.com/XTLS/Xray-core/issues/4422#issuecomment-3533007890 Breaking changes https://github.com/XTLS/Xray-core/pull/5569 Reverts https://github.com/XTLS/Xray-core/pull/5505 Closes https://github.com/XTLS/Xray-core/pull/643
This commit is contained in:
+60
-181
@@ -3,6 +3,7 @@ package conf
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -10,8 +11,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/app/dns"
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
@@ -20,7 +21,7 @@ type NameServerConfig struct {
|
||||
ClientIP *Address `json:"clientIp"`
|
||||
Port uint16 `json:"port"`
|
||||
SkipFallback bool `json:"skipFallback"`
|
||||
Domains []string `json:"domains"`
|
||||
Domains StringList `json:"domains"`
|
||||
ExpectedIPs StringList `json:"expectedIPs"`
|
||||
ExpectIPs StringList `json:"expectIPs"`
|
||||
QueryStrategy string `json:"queryStrategy"`
|
||||
@@ -46,7 +47,7 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
||||
ClientIP *Address `json:"clientIp"`
|
||||
Port uint16 `json:"port"`
|
||||
SkipFallback bool `json:"skipFallback"`
|
||||
Domains []string `json:"domains"`
|
||||
Domains StringList `json:"domains"`
|
||||
ExpectedIPs StringList `json:"expectedIPs"`
|
||||
ExpectIPs StringList `json:"expectIPs"`
|
||||
QueryStrategy string `json:"queryStrategy"`
|
||||
@@ -80,45 +81,14 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
||||
return errors.New("failed to parse name server: ", string(data))
|
||||
}
|
||||
|
||||
func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
|
||||
switch t {
|
||||
case router.Domain_Domain:
|
||||
return dns.DomainMatchingType_Subdomain
|
||||
case router.Domain_Full:
|
||||
return dns.DomainMatchingType_Full
|
||||
case router.Domain_Plain:
|
||||
return dns.DomainMatchingType_Keyword
|
||||
case router.Domain_Regex:
|
||||
return dns.DomainMatchingType_Regex
|
||||
default:
|
||||
panic("unknown domain type")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||
if c.Address == nil {
|
||||
return nil, errors.New("NameServer address is not specified.")
|
||||
return nil, errors.New("nameserver address is not specified")
|
||||
}
|
||||
|
||||
var domains []*dns.NameServer_PriorityDomain
|
||||
var originalRules []*dns.NameServer_OriginalRule
|
||||
|
||||
for _, rule := range c.Domains {
|
||||
parsedDomain, err := parseDomainRule(rule)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid domain rule: ", rule).Base(err)
|
||||
}
|
||||
|
||||
for _, pd := range parsedDomain {
|
||||
domains = append(domains, &dns.NameServer_PriorityDomain{
|
||||
Type: toDomainMatchingType(pd.Type),
|
||||
Domain: pd.Value,
|
||||
})
|
||||
}
|
||||
originalRules = append(originalRules, &dns.NameServer_OriginalRule{
|
||||
Rule: rule,
|
||||
Size: uint32(len(parsedDomain)),
|
||||
})
|
||||
domainRules, err := geodata.ParseDomainRules(c.Domains, geodata.Domain_Substr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(c.ExpectedIPs) == 0 {
|
||||
@@ -145,14 +115,14 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||
}
|
||||
}
|
||||
|
||||
expectedGeoipList, err := ToCidrList(newExpectedIPs)
|
||||
expectedIPRules, err := geodata.ParseIPRules(newExpectedIPs)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid expected IP rule: ", c.ExpectedIPs).Base(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unexpectedGeoipList, err := ToCidrList(newUnexpectedIPs)
|
||||
unexpectedIPRules, err := geodata.ParseIPRules(newUnexpectedIPs)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid unexpected IP rule: ", c.UnexpectedIPs).Base(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var myClientIP []byte
|
||||
@@ -169,32 +139,24 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||
Address: c.Address.Build(),
|
||||
Port: uint32(c.Port),
|
||||
},
|
||||
ClientIp: myClientIP,
|
||||
SkipFallback: c.SkipFallback,
|
||||
PrioritizedDomain: domains,
|
||||
ExpectedGeoip: expectedGeoipList,
|
||||
OriginalRules: originalRules,
|
||||
QueryStrategy: resolveQueryStrategy(c.QueryStrategy),
|
||||
ActPrior: actPrior,
|
||||
Tag: c.Tag,
|
||||
TimeoutMs: c.TimeoutMs,
|
||||
DisableCache: c.DisableCache,
|
||||
ServeStale: c.ServeStale,
|
||||
ServeExpiredTTL: c.ServeExpiredTTL,
|
||||
FinalQuery: c.FinalQuery,
|
||||
UnexpectedGeoip: unexpectedGeoipList,
|
||||
ActUnprior: actUnprior,
|
||||
ClientIp: myClientIP,
|
||||
SkipFallback: c.SkipFallback,
|
||||
Domain: domainRules,
|
||||
ExpectedIp: expectedIPRules,
|
||||
QueryStrategy: resolveQueryStrategy(c.QueryStrategy),
|
||||
ActPrior: actPrior,
|
||||
Tag: c.Tag,
|
||||
TimeoutMs: c.TimeoutMs,
|
||||
DisableCache: c.DisableCache,
|
||||
ServeStale: c.ServeStale,
|
||||
ServeExpiredTTL: c.ServeExpiredTTL,
|
||||
FinalQuery: c.FinalQuery,
|
||||
UnexpectedIp: unexpectedIPRules,
|
||||
ActUnprior: actUnprior,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var typeMap = map[router.Domain_Type]dns.DomainMatchingType{
|
||||
router.Domain_Full: dns.DomainMatchingType_Full,
|
||||
router.Domain_Domain: dns.DomainMatchingType_Subdomain,
|
||||
router.Domain_Plain: dns.DomainMatchingType_Keyword,
|
||||
router.Domain_Regex: dns.DomainMatchingType_Regex,
|
||||
}
|
||||
|
||||
// DNSConfig is a JSON serializable object for dns.Config.
|
||||
// DNSConfig is a JSON serializable object for dns.Config
|
||||
type DNSConfig struct {
|
||||
Servers []*NameServerConfig `json:"servers"`
|
||||
Hosts *HostsWrapper `json:"hosts"`
|
||||
@@ -246,7 +208,7 @@ type HostsWrapper struct {
|
||||
Hosts map[string]*HostAddress
|
||||
}
|
||||
|
||||
func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
|
||||
func newHostMapping(ha *HostAddress) *dns.Config_HostMapping {
|
||||
if ha.addr != nil {
|
||||
if ha.addr.Family().IsDomain() {
|
||||
return &dns.Config_HostMapping{
|
||||
@@ -290,109 +252,15 @@ func (m *HostsWrapper) UnmarshalJSON(data []byte) error {
|
||||
|
||||
// Build implements Buildable
|
||||
func (m *HostsWrapper) Build() ([]*dns.Config_HostMapping, error) {
|
||||
mappings := make([]*dns.Config_HostMapping, 0, 20)
|
||||
|
||||
domains := make([]string, 0, len(m.Hosts))
|
||||
for domain := range m.Hosts {
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
sort.Strings(domains)
|
||||
|
||||
for _, domain := range domains {
|
||||
switch {
|
||||
case strings.HasPrefix(domain, "domain:"):
|
||||
domainName := domain[7:]
|
||||
if len(domainName) == 0 {
|
||||
return nil, errors.New("empty domain type of rule: ", domain)
|
||||
}
|
||||
mapping := getHostMapping(m.Hosts[domain])
|
||||
mapping.Type = dns.DomainMatchingType_Subdomain
|
||||
mapping.Domain = domainName
|
||||
mappings = append(mappings, mapping)
|
||||
|
||||
case strings.HasPrefix(domain, "geosite:"):
|
||||
listName := domain[8:]
|
||||
if len(listName) == 0 {
|
||||
return nil, errors.New("empty geosite rule: ", domain)
|
||||
}
|
||||
geositeList, err := loadGeositeWithAttr("geosite.dat", listName)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load geosite: ", listName).Base(err)
|
||||
}
|
||||
for _, d := range geositeList {
|
||||
mapping := getHostMapping(m.Hosts[domain])
|
||||
mapping.Type = typeMap[d.Type]
|
||||
mapping.Domain = d.Value
|
||||
mappings = append(mappings, mapping)
|
||||
}
|
||||
|
||||
case strings.HasPrefix(domain, "regexp:"):
|
||||
regexpVal := domain[7:]
|
||||
if len(regexpVal) == 0 {
|
||||
return nil, errors.New("empty regexp type of rule: ", domain)
|
||||
}
|
||||
mapping := getHostMapping(m.Hosts[domain])
|
||||
mapping.Type = dns.DomainMatchingType_Regex
|
||||
mapping.Domain = regexpVal
|
||||
mappings = append(mappings, mapping)
|
||||
|
||||
case strings.HasPrefix(domain, "keyword:"):
|
||||
keywordVal := domain[8:]
|
||||
if len(keywordVal) == 0 {
|
||||
return nil, errors.New("empty keyword type of rule: ", domain)
|
||||
}
|
||||
mapping := getHostMapping(m.Hosts[domain])
|
||||
mapping.Type = dns.DomainMatchingType_Keyword
|
||||
mapping.Domain = keywordVal
|
||||
mappings = append(mappings, mapping)
|
||||
|
||||
case strings.HasPrefix(domain, "full:"):
|
||||
fullVal := domain[5:]
|
||||
if len(fullVal) == 0 {
|
||||
return nil, errors.New("empty full domain type of rule: ", domain)
|
||||
}
|
||||
mapping := getHostMapping(m.Hosts[domain])
|
||||
mapping.Type = dns.DomainMatchingType_Full
|
||||
mapping.Domain = fullVal
|
||||
mappings = append(mappings, mapping)
|
||||
|
||||
case strings.HasPrefix(domain, "dotless:"):
|
||||
mapping := getHostMapping(m.Hosts[domain])
|
||||
mapping.Type = dns.DomainMatchingType_Regex
|
||||
switch substr := domain[8:]; {
|
||||
case substr == "":
|
||||
mapping.Domain = "^[^.]*$"
|
||||
case !strings.Contains(substr, "."):
|
||||
mapping.Domain = "^[^.]*" + substr + "[^.]*$"
|
||||
default:
|
||||
return nil, errors.New("substr in dotless rule should not contain a dot: ", substr)
|
||||
}
|
||||
mappings = append(mappings, mapping)
|
||||
|
||||
case strings.HasPrefix(domain, "ext:"):
|
||||
kv := strings.Split(domain[4:], ":")
|
||||
if len(kv) != 2 {
|
||||
return nil, errors.New("invalid external resource: ", domain)
|
||||
}
|
||||
filename := kv[0]
|
||||
list := kv[1]
|
||||
geositeList, err := loadGeositeWithAttr(filename, list)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load domain list: ", list, " from ", filename).Base(err)
|
||||
}
|
||||
for _, d := range geositeList {
|
||||
mapping := getHostMapping(m.Hosts[domain])
|
||||
mapping.Type = typeMap[d.Type]
|
||||
mapping.Domain = d.Value
|
||||
mappings = append(mappings, mapping)
|
||||
}
|
||||
|
||||
default:
|
||||
mapping := getHostMapping(m.Hosts[domain])
|
||||
mapping.Type = dns.DomainMatchingType_Full
|
||||
mapping.Domain = domain
|
||||
mappings = append(mappings, mapping)
|
||||
mappings := make([]*dns.Config_HostMapping, 0, len(m.Hosts))
|
||||
for rule, addrs := range m.Hosts {
|
||||
mapping := newHostMapping(addrs)
|
||||
rule, err := geodata.ParseDomainRule(rule, geodata.Domain_Full)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapping.Domain = rule
|
||||
mappings = append(mappings, mapping)
|
||||
}
|
||||
return mappings, nil
|
||||
}
|
||||
@@ -504,9 +372,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to read system hosts").Base(err)
|
||||
}
|
||||
for domain, ips := range systemHosts {
|
||||
config.StaticHosts = append(config.StaticHosts, &dns.Config_HostMapping{Ip: ips, Domain: domain, Type: dns.DomainMatchingType_Full})
|
||||
}
|
||||
config.StaticHosts = append(config.StaticHosts, systemHosts...)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
@@ -527,7 +393,7 @@ func resolveQueryStrategy(queryStrategy string) dns.QueryStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
func readSystemHosts() (map[string][][]byte, error) {
|
||||
func readSystemHosts() ([]*dns.Config_HostMapping, error) {
|
||||
var hostsPath string
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
@@ -542,12 +408,16 @@ func readSystemHosts() (map[string][][]byte, error) {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hostsMap := make(map[string][][]byte)
|
||||
scanner := bufio.NewScanner(file)
|
||||
return readSystemHostsFrom(file)
|
||||
}
|
||||
|
||||
func readSystemHostsFrom(r io.Reader) ([]*dns.Config_HostMapping, error) {
|
||||
hosts := make(map[string][][]byte, 16)
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if i := strings.IndexByte(line, '#'); i >= 0 {
|
||||
// Discard comments.
|
||||
// Strip inline comments before splitting the line into fields.
|
||||
line = line[0:i]
|
||||
}
|
||||
f := strings.Fields(line)
|
||||
@@ -558,19 +428,28 @@ func readSystemHosts() (map[string][][]byte, error) {
|
||||
if addr.Family().IsDomain() {
|
||||
continue
|
||||
}
|
||||
ip := addr.IP()
|
||||
for i := 1; i < len(f); i++ {
|
||||
domain := strings.TrimSuffix(f[i], ".")
|
||||
domain = strings.ToLower(domain)
|
||||
if v, ok := hostsMap[domain]; ok {
|
||||
hostsMap[domain] = append(v, ip)
|
||||
} else {
|
||||
hostsMap[domain] = [][]byte{ip}
|
||||
}
|
||||
hosts[domain] = append(hosts[domain], addr.IP())
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostsMap := make([]*dns.Config_HostMapping, 0, len(hosts))
|
||||
for domain, ips := range hosts {
|
||||
// ParseDomainRule accepts rule syntax too, not just plain domains.
|
||||
rule, err := geodata.ParseDomainRule(domain, geodata.Domain_Full)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hostsMap = append(hostsMap, &dns.Config_HostMapping{
|
||||
Domain: rule,
|
||||
Ip: ips,
|
||||
})
|
||||
}
|
||||
|
||||
return hostsMap, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user