Files
xray-core/app/dns/hosts.go

130 lines
3.5 KiB
Go

package dns
import (
"context"
"strconv"
"strings"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/features/dns"
)
// StaticHosts represents static domain-ip mapping in DNS server.
type StaticHosts struct {
responses [][]net.Address
matcher geodata.DomainMatcher
}
// NewStaticHosts creates a new StaticHosts instance.
func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
reps := make([][]net.Address, 0, len(hosts))
rules := make([]*geodata.DomainRule, 0, len(hosts))
for _, mapping := range hosts {
rep := make([]net.Address, 0, len(mapping.Ip))
switch {
case len(mapping.ProxiedDomain) > 0:
if mapping.ProxiedDomain[0] == '#' {
rcode, err := strconv.Atoi(mapping.ProxiedDomain[1:])
if err != nil {
return nil, err
}
rep = append(rep, dns.RCodeError(rcode))
} else {
rep = append(rep, net.DomainAddress(mapping.ProxiedDomain))
}
case len(mapping.Ip) > 0:
for _, ip := range mapping.Ip {
addr := net.IPAddress(ip)
if addr == nil {
errors.LogError(context.Background(), "invalid IP address in static hosts: ", ip, ", ignore this ip for rule: ", mapping.Domain)
continue
}
rep = append(rep, addr)
}
}
reps = append(reps, rep)
rules = append(rules, mapping.Domain)
}
if len(rules) == 0 {
return &StaticHosts{}, nil
}
matcher, err := geodata.DomainReg.BuildDomainMatcher(rules)
if err != nil {
return nil, err
}
return &StaticHosts{
responses: reps,
matcher: matcher,
}, nil
}
func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
filtered := make([]net.Address, 0, len(ips))
for _, ip := range ips {
if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) {
filtered = append(filtered, ip)
}
}
return filtered
}
func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
ips := make([]net.Address, 0)
found := false
for _, idx := range h.matcher.Match(domain) {
for _, rep := range h.responses[idx] {
if err, ok := rep.(dns.RCodeError); ok {
if uint16(err) == 0 {
return nil, dns.ErrEmptyResponse
}
return nil, err
}
}
ips = append(ips, h.responses[idx]...)
found = true
}
if !found {
return nil, nil
}
return ips, nil
}
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) {
domain = strings.ToLower(domain)
switch addrs, err := h.lookupInternal(domain); {
case err != nil:
return nil, err
case addrs == nil: // Not recorded in static hosts, return nil
return nil, nil
case len(addrs) == 0: // Domain recorded, but no valid IP returned
return addrs, nil
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
if maxDepth > 0 {
unwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1)
if err != nil {
return nil, err
}
if unwrapped != nil {
return unwrapped, nil
}
}
return addrs, nil
default: // IP record found, return a non-nil IP array
return filterIP(addrs, option), nil
}
}
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {
if h.matcher == nil {
return nil, nil
}
return h.lookup(domain, option, 5)
}