mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
130 lines
3.5 KiB
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)
|
|
}
|