2020-11-25 19:01:53 +08:00
package dns
import (
2024-06-29 14:32:57 -04:00
"context"
2026-01-26 18:45:25 +08:00
"runtime"
2025-07-23 18:11:43 +08:00
"strconv"
2024-06-29 14:32:57 -04:00
"github.com/xtls/xray-core/common/errors"
2020-12-04 09:36:16 +08:00
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/strmatcher"
2021-03-06 23:39:50 -05:00
"github.com/xtls/xray-core/features/dns"
2020-11-25 19:01:53 +08:00
)
// StaticHosts represents static domain-ip mapping in DNS server.
type StaticHosts struct {
ips [ ] [ ] net . Address
2026-01-31 16:45:39 +03:30
matchers strmatcher . IndexMatcher
2020-11-25 19:01:53 +08:00
}
// NewStaticHosts creates a new StaticHosts instance.
2024-09-19 09:05:59 +08:00
func NewStaticHosts ( hosts [ ] * Config_HostMapping ) ( * StaticHosts , error ) {
2020-11-25 19:01:53 +08:00
g := new ( strmatcher . MatcherGroup )
sh := & StaticHosts {
2024-09-19 09:05:59 +08:00
ips : make ( [ ] [ ] net . Address , len ( hosts ) + 16 ) ,
2020-11-25 19:01:53 +08:00
matchers : g ,
}
2026-01-26 18:45:25 +08:00
defer runtime . GC ( )
for i , mapping := range hosts {
hosts [ i ] = nil
2020-11-25 19:01:53 +08:00
matcher , err := toStrMatcher ( mapping . Type , mapping . Domain )
if err != nil {
2025-12-23 18:14:42 +08:00
errors . LogErrorInner ( context . Background ( ) , err , "failed to create domain matcher, ignore domain rule [type: " , mapping . Type , ", domain: " , mapping . Domain , "]" )
continue
2020-11-25 19:01:53 +08:00
}
id := g . Add ( matcher )
ips := make ( [ ] net . Address , 0 , len ( mapping . Ip ) + 1 )
switch {
2021-10-16 21:02:51 +08:00
case len ( mapping . ProxiedDomain ) > 0 :
2025-07-23 18:11:43 +08:00
if mapping . ProxiedDomain [ 0 ] == '#' {
rcode , err := strconv . Atoi ( mapping . ProxiedDomain [ 1 : ] )
if err != nil {
return nil , err
}
ips = append ( ips , dns . RCodeError ( rcode ) )
} else {
ips = append ( ips , net . DomainAddress ( mapping . ProxiedDomain ) )
}
2020-11-25 19:01:53 +08:00
case len ( mapping . Ip ) > 0 :
for _ , ip := range mapping . Ip {
addr := net . IPAddress ( ip )
if addr == nil {
2025-12-23 18:14:42 +08:00
errors . LogError ( context . Background ( ) , "invalid IP address in static hosts: " , ip , ", ignore this ip for rule [type: " , mapping . Type , ", domain: " , mapping . Domain , "]" )
continue
2020-11-25 19:01:53 +08:00
}
ips = append ( ips , addr )
}
2025-12-23 18:14:42 +08:00
if len ( ips ) == 0 {
continue
}
2020-11-25 19:01:53 +08:00
}
sh . ips [ id ] = ips
}
return sh , nil
}
2021-03-06 23:39:50 -05:00
func filterIP ( ips [ ] net . Address , option dns . IPOption ) [ ] net . Address {
2020-11-25 19:01:53 +08:00
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
}
2025-07-23 18:11:43 +08:00
func ( h * StaticHosts ) lookupInternal ( domain string ) ( [ ] net . Address , error ) {
2025-05-07 01:41:08 +03:30
ips := make ( [ ] net . Address , 0 )
found := false
for _ , id := range h . matchers . Match ( domain ) {
2025-07-23 18:11:43 +08:00
for _ , v := range h . ips [ id ] {
if err , ok := v . ( dns . RCodeError ) ; ok {
if uint16 ( err ) == 0 {
return nil , dns . ErrEmptyResponse
}
return nil , err
}
}
2025-05-07 01:41:08 +03:30
ips = append ( ips , h . ips [ id ] ... )
found = true
}
if ! found {
2025-07-23 18:11:43 +08:00
return nil , nil
2020-11-25 19:01:53 +08:00
}
2025-07-23 18:11:43 +08:00
return ips , nil
2021-10-16 21:02:51 +08:00
}
2025-07-23 18:11:43 +08:00
func ( h * StaticHosts ) lookup ( domain string , option dns . IPOption , maxDepth int ) ( [ ] net . Address , error ) {
switch addrs , err := h . lookupInternal ( domain ) ; {
case err != nil :
return nil , err
2021-10-16 21:02:51 +08:00
case len ( addrs ) == 0 : // Not recorded in static hosts, return nil
2025-07-23 18:11:43 +08:00
return addrs , nil
2021-10-16 21:02:51 +08:00
case len ( addrs ) == 1 && addrs [ 0 ] . Family ( ) . IsDomain ( ) : // Try to unwrap domain
2024-06-29 14:32:57 -04:00
errors . LogDebug ( context . Background ( ) , "found replaced domain: " , domain , " -> " , addrs [ 0 ] . Domain ( ) , ". Try to unwrap it" )
2021-10-16 21:02:51 +08:00
if maxDepth > 0 {
2025-07-23 18:11:43 +08:00
unwrapped , err := h . lookup ( addrs [ 0 ] . Domain ( ) , option , maxDepth - 1 )
if err != nil {
return nil , err
}
2021-10-16 21:02:51 +08:00
if unwrapped != nil {
2025-07-23 18:11:43 +08:00
return unwrapped , nil
2021-10-16 21:02:51 +08:00
}
}
2025-07-23 18:11:43 +08:00
return addrs , nil
2021-10-16 21:02:51 +08:00
default : // IP record found, return a non-nil IP array
2025-07-23 18:11:43 +08:00
return filterIP ( addrs , option ) , nil
2020-11-25 19:01:53 +08:00
}
2021-10-16 21:02:51 +08:00
}
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
2025-07-23 18:11:43 +08:00
func ( h * StaticHosts ) Lookup ( domain string , option dns . IPOption ) ( [ ] net . Address , error ) {
2021-10-16 21:02:51 +08:00
return h . lookup ( domain , option , 5 )
2020-11-25 19:01:53 +08:00
}
2026-01-31 16:45:39 +03:30
func NewStaticHostsFromCache ( matcher strmatcher . IndexMatcher , hostIPs map [ string ] [ ] string ) ( * StaticHosts , error ) {
sh := & StaticHosts {
ips : make ( [ ] [ ] net . Address , matcher . Size ( ) + 1 ) ,
matchers : matcher ,
}
order := hostIPs [ "_ORDER" ]
var offset uint32
img , ok := matcher . ( * strmatcher . IndexMatcherGroup )
if ! ok {
// Single matcher (e.g. only manual or only one geosite)
if len ( order ) > 0 {
pattern := order [ 0 ]
ips := parseIPs ( hostIPs [ pattern ] )
for i := uint32 ( 1 ) ; i <= matcher . Size ( ) ; i ++ {
sh . ips [ i ] = ips
}
}
return sh , nil
}
for i , m := range img . Matchers {
if i < len ( order ) {
pattern := order [ i ]
ips := parseIPs ( hostIPs [ pattern ] )
for j := uint32 ( 1 ) ; j <= m . Size ( ) ; j ++ {
sh . ips [ offset + j ] = ips
}
offset += m . Size ( )
}
}
return sh , nil
}
func parseIPs ( raw [ ] string ) [ ] net . Address {
addrs := make ( [ ] net . Address , 0 , len ( raw ) )
for _ , s := range raw {
if len ( s ) > 1 && s [ 0 ] == '#' {
rcode , _ := strconv . Atoi ( s [ 1 : ] )
addrs = append ( addrs , dns . RCodeError ( rcode ) )
} else {
addrs = append ( addrs , net . ParseAddress ( s ) )
}
}
return addrs
}