Xray-core/infra/conf/dns.go

321 lines
8.8 KiB
Go
Raw Permalink Normal View History

2020-11-25 11:01:53 +00:00
package conf
import (
"encoding/json"
2021-03-26 09:30:24 +00:00
2020-11-25 11:01:53 +00:00
"sort"
"strings"
2020-12-04 01:36:16 +00:00
"github.com/xtls/xray-core/app/dns"
2021-03-24 15:01:20 +00:00
dm "github.com/xtls/xray-core/common/matcher/domain"
2021-03-26 07:28:27 +00:00
"github.com/xtls/xray-core/common/matcher/domain/conf"
2021-03-26 09:30:24 +00:00
"github.com/xtls/xray-core/common/matcher/geoip"
2021-03-24 15:01:20 +00:00
"github.com/xtls/xray-core/common/matcher/geosite"
2020-12-04 01:36:16 +00:00
"github.com/xtls/xray-core/common/net"
2020-11-25 11:01:53 +00:00
)
type NameServerConfig struct {
Address *Address
ClientIP *Address
Port uint16
SkipFallback bool
Domains []string
ExpectIPs StringList
2020-11-25 11:01:53 +00:00
}
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
2021-03-27 07:33:20 +00:00
var address Address
2020-11-25 11:01:53 +00:00
if err := json.Unmarshal(data, &address); err == nil {
c.Address = &address
return nil
}
var advanced struct {
Address *Address `json:"address"`
ClientIP *Address `json:"clientIp"`
Port uint16 `json:"port"`
SkipFallback bool `json:"skipFallback"`
Domains []string `json:"domains"`
ExpectIPs StringList `json:"expectIps"`
2020-11-25 11:01:53 +00:00
}
if err := json.Unmarshal(data, &advanced); err == nil {
c.Address = advanced.Address
2021-04-02 08:57:31 +00:00
c.ClientIP = advanced.ClientIP
2020-11-25 11:01:53 +00:00
c.Port = advanced.Port
c.SkipFallback = advanced.SkipFallback
2020-11-25 11:01:53 +00:00
c.Domains = advanced.Domains
c.ExpectIPs = advanced.ExpectIPs
return nil
}
return newError("failed to parse name server: ", string(data))
}
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
if c.Address == nil {
return nil, newError("NameServer address is not specified.")
}
2021-03-24 15:01:20 +00:00
var domains []*dm.Domain
2020-11-25 11:01:53 +00:00
var originalRules []*dns.NameServer_OriginalRule
for _, rule := range c.Domains {
2021-03-26 08:56:43 +00:00
parsedDomain, err := conf.ParseDomainRule(rule)
2020-11-25 11:01:53 +00:00
if err != nil {
return nil, newError("invalid domain rule: ", rule).Base(err)
}
for _, pd := range parsedDomain {
2021-03-24 15:01:20 +00:00
domains = append(domains, &dm.Domain{
Type: pd.Type,
Value: pd.Value,
2020-11-25 11:01:53 +00:00
})
}
originalRules = append(originalRules, &dns.NameServer_OriginalRule{
Rule: rule,
Size: uint32(len(parsedDomain)),
})
}
2021-04-08 11:19:53 +00:00
geoipList, err := geoip.ParseIPList(c.ExpectIPs)
2020-11-25 11:01:53 +00:00
if err != nil {
2020-12-24 19:45:35 +00:00
return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
2020-11-25 11:01:53 +00:00
}
2021-04-02 08:57:31 +00:00
var myClientIP []byte
if c.ClientIP != nil {
if !c.ClientIP.Family().IsIP() {
return nil, newError("not an IP address:", c.ClientIP.String())
}
myClientIP = []byte(c.ClientIP.IP())
}
2020-11-25 11:01:53 +00:00
return &dns.NameServer{
Address: &net.Endpoint{
Network: net.Network_UDP,
Address: c.Address.Build(),
Port: uint32(c.Port),
},
2021-04-02 08:57:31 +00:00
ClientIp: myClientIP,
SkipFallback: c.SkipFallback,
2020-11-25 11:01:53 +00:00
PrioritizedDomain: domains,
Geoip: geoipList,
OriginalRules: originalRules,
}, nil
}
// DNSConfig is a JSON serializable object for dns.Config.
type DNSConfig struct {
Servers []*NameServerConfig `json:"servers"`
Hosts map[string]*HostAddress `json:"hosts"`
ClientIP *Address `json:"clientIp"`
Tag string `json:"tag"`
QueryStrategy string `json:"queryStrategy"`
CacheStrategy string `json:"cacheStrategy"`
DisableCache bool `json:"disableCache"`
DisableFallback bool `json:"disableFallback"`
2020-11-25 11:01:53 +00:00
}
type HostAddress struct {
addr *Address
addrs []*Address
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (h *HostAddress) UnmarshalJSON(data []byte) error {
addr := new(Address)
var addrs []*Address
switch {
case json.Unmarshal(data, &addr) == nil:
h.addr = addr
case json.Unmarshal(data, &addrs) == nil:
h.addrs = addrs
default:
return newError("invalid address")
}
return nil
}
func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
if ha.addr != nil {
if ha.addr.Family().IsDomain() {
return &dns.Config_HostMapping{
ProxiedDomain: ha.addr.Domain(),
}
2020-11-25 11:01:53 +00:00
}
return &dns.Config_HostMapping{
Ip: [][]byte{ha.addr.IP()},
2020-11-25 11:01:53 +00:00
}
}
ips := make([][]byte, 0, len(ha.addrs))
for _, addr := range ha.addrs {
if addr.Family().IsDomain() {
return &dns.Config_HostMapping{
ProxiedDomain: addr.Domain(),
}
}
ips = append(ips, []byte(addr.IP()))
}
return &dns.Config_HostMapping{
Ip: ips,
}
2020-11-25 11:01:53 +00:00
}
// Build implements Buildable
func (c *DNSConfig) Build() (*dns.Config, error) {
config := &dns.Config{
Tag: c.Tag,
CacheStrategy: dns.CacheStrategy_Cache_ALL,
DisableFallback: c.DisableFallback,
2021-04-09 15:36:36 +00:00
}
if c.DisableCache {
config.CacheStrategy = dns.CacheStrategy_Cache_DISABLE
2020-11-25 11:01:53 +00:00
}
if c.ClientIP != nil {
if !c.ClientIP.Family().IsIP() {
return nil, newError("not an IP address:", c.ClientIP.String())
}
config.ClientIp = []byte(c.ClientIP.IP())
}
2021-03-19 15:33:07 +00:00
config.QueryStrategy = dns.QueryStrategy_USE_IP
switch strings.ToLower(c.QueryStrategy) {
case "useip", "use_ip", "use-ip":
config.QueryStrategy = dns.QueryStrategy_USE_IP
case "useip4", "useipv4", "use_ip4", "use_ipv4", "use_ip_v4", "use-ip4", "use-ipv4", "use-ip-v4":
config.QueryStrategy = dns.QueryStrategy_USE_IP4
case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
config.QueryStrategy = dns.QueryStrategy_USE_IP6
}
2021-04-09 15:36:36 +00:00
switch strings.ToLower(c.CacheStrategy) {
case "noerror":
config.CacheStrategy = dns.CacheStrategy_Cache_NOERROR
case "all":
config.CacheStrategy = dns.CacheStrategy_Cache_ALL
case "disable", "none":
config.CacheStrategy = dns.CacheStrategy_Cache_DISABLE
}
2020-11-25 11:01:53 +00:00
for _, server := range c.Servers {
ns, err := server.Build()
if err != nil {
2020-12-24 19:45:35 +00:00
return nil, newError("failed to build nameserver").Base(err)
2020-11-25 11:01:53 +00:00
}
config.NameServer = append(config.NameServer, ns)
}
if c.Hosts != nil && len(c.Hosts) > 0 {
domains := make([]string, 0, len(c.Hosts))
for domain := range c.Hosts {
domains = append(domains, domain)
}
sort.Strings(domains)
for _, domain := range domains {
addr := c.Hosts[domain]
var mappings []*dns.Config_HostMapping
switch {
case strings.HasPrefix(domain, "domain:"):
2020-12-24 19:45:35 +00:00
domainName := domain[7:]
if len(domainName) == 0 {
return nil, newError("empty domain type of rule: ", domain)
}
2020-11-25 11:01:53 +00:00
mapping := getHostMapping(addr)
2021-03-24 15:01:20 +00:00
mapping.Type = dm.MatchingType_Subdomain
2020-12-24 19:45:35 +00:00
mapping.Domain = domainName
2020-11-25 11:01:53 +00:00
mappings = append(mappings, mapping)
case strings.HasPrefix(domain, "geosite:"):
2020-12-24 19:45:35 +00:00
listName := domain[8:]
if len(listName) == 0 {
return nil, newError("empty geosite rule: ", domain)
}
2021-03-24 15:01:20 +00:00
domains, err := geosite.LoadGeositeWithAttr("geosite.dat", listName)
2020-11-25 11:01:53 +00:00
if err != nil {
2020-12-24 19:45:35 +00:00
return nil, newError("failed to load geosite: ", listName).Base(err)
2020-11-25 11:01:53 +00:00
}
for _, d := range domains {
mapping := getHostMapping(addr)
2021-03-24 15:01:20 +00:00
mapping.Type = d.Type
2020-11-25 11:01:53 +00:00
mapping.Domain = d.Value
mappings = append(mappings, mapping)
}
case strings.HasPrefix(domain, "regexp:"):
2020-12-24 19:45:35 +00:00
regexpVal := domain[7:]
if len(regexpVal) == 0 {
return nil, newError("empty regexp type of rule: ", domain)
}
2020-11-25 11:01:53 +00:00
mapping := getHostMapping(addr)
2021-03-24 15:01:20 +00:00
mapping.Type = dm.MatchingType_Regex
2020-12-24 19:45:35 +00:00
mapping.Domain = regexpVal
2020-11-25 11:01:53 +00:00
mappings = append(mappings, mapping)
case strings.HasPrefix(domain, "keyword:"):
2020-12-24 19:45:35 +00:00
keywordVal := domain[8:]
if len(keywordVal) == 0 {
return nil, newError("empty keyword type of rule: ", domain)
}
2020-11-25 11:01:53 +00:00
mapping := getHostMapping(addr)
2021-03-24 15:01:20 +00:00
mapping.Type = dm.MatchingType_Keyword
2020-12-24 19:45:35 +00:00
mapping.Domain = keywordVal
2020-11-25 11:01:53 +00:00
mappings = append(mappings, mapping)
case strings.HasPrefix(domain, "full:"):
2020-12-24 19:45:35 +00:00
fullVal := domain[5:]
if len(fullVal) == 0 {
return nil, newError("empty full domain type of rule: ", domain)
}
2020-11-25 11:01:53 +00:00
mapping := getHostMapping(addr)
2021-03-24 15:01:20 +00:00
mapping.Type = dm.MatchingType_Full
2020-12-24 19:45:35 +00:00
mapping.Domain = fullVal
2020-11-25 11:01:53 +00:00
mappings = append(mappings, mapping)
case strings.HasPrefix(domain, "dotless:"):
mapping := getHostMapping(addr)
2021-03-24 15:01:20 +00:00
mapping.Type = dm.MatchingType_Regex
2020-11-25 11:01:53 +00:00
switch substr := domain[8:]; {
case substr == "":
mapping.Domain = "^[^.]*$"
case !strings.Contains(substr, "."):
mapping.Domain = "^[^.]*" + substr + "[^.]*$"
default:
return nil, newError("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, newError("invalid external resource: ", domain)
}
filename := kv[0]
2020-12-24 19:45:35 +00:00
list := kv[1]
2021-03-24 15:01:20 +00:00
domains, err := geosite.LoadGeositeWithAttr(filename, list)
2020-11-25 11:01:53 +00:00
if err != nil {
2020-12-24 19:45:35 +00:00
return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
2020-11-25 11:01:53 +00:00
}
for _, d := range domains {
mapping := getHostMapping(addr)
2021-03-24 15:01:20 +00:00
mapping.Type = d.Type
2020-11-25 11:01:53 +00:00
mapping.Domain = d.Value
mappings = append(mappings, mapping)
}
default:
mapping := getHostMapping(addr)
2021-03-24 15:01:20 +00:00
mapping.Type = dm.MatchingType_Full
2020-11-25 11:01:53 +00:00
mapping.Domain = domain
mappings = append(mappings, mapping)
}
config.StaticHosts = append(config.StaticHosts, mappings...)
}
}
return config, nil
}