mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-05-18 10:08:41 +00:00
Merge branch 'main' into hosts
This commit is contained in:
commit
e329d6e249
13 changed files with 892 additions and 288 deletions
|
@ -1,8 +1,13 @@
|
|||
package conf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/app/dns"
|
||||
|
@ -12,17 +17,19 @@ import (
|
|||
)
|
||||
|
||||
type NameServerConfig struct {
|
||||
Address *Address `json:"address"`
|
||||
ClientIP *Address `json:"clientIp"`
|
||||
Port uint16 `json:"port"`
|
||||
SkipFallback bool `json:"skipFallback"`
|
||||
Domains []string `json:"domains"`
|
||||
ExpectedIPs StringList `json:"expectedIPs"`
|
||||
ExpectIPs StringList `json:"expectIPs"`
|
||||
QueryStrategy string `json:"queryStrategy"`
|
||||
AllowUnexpectedIPs bool `json:"allowUnexpectedIps"`
|
||||
Tag string `json:"tag"`
|
||||
TimeoutMs uint64 `json:"timeoutMs"`
|
||||
Address *Address `json:"address"`
|
||||
ClientIP *Address `json:"clientIp"`
|
||||
Port uint16 `json:"port"`
|
||||
SkipFallback bool `json:"skipFallback"`
|
||||
Domains []string `json:"domains"`
|
||||
ExpectedIPs StringList `json:"expectedIPs"`
|
||||
ExpectIPs StringList `json:"expectIPs"`
|
||||
QueryStrategy string `json:"queryStrategy"`
|
||||
Tag string `json:"tag"`
|
||||
TimeoutMs uint64 `json:"timeoutMs"`
|
||||
DisableCache bool `json:"disableCache"`
|
||||
FinalQuery bool `json:"finalQuery"`
|
||||
UnexpectedIPs StringList `json:"unexpectedIPs"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
||||
|
@ -34,17 +41,19 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
var advanced struct {
|
||||
Address *Address `json:"address"`
|
||||
ClientIP *Address `json:"clientIp"`
|
||||
Port uint16 `json:"port"`
|
||||
SkipFallback bool `json:"skipFallback"`
|
||||
Domains []string `json:"domains"`
|
||||
ExpectedIPs StringList `json:"expectedIPs"`
|
||||
ExpectIPs StringList `json:"expectIPs"`
|
||||
QueryStrategy string `json:"queryStrategy"`
|
||||
AllowUnexpectedIPs bool `json:"allowUnexpectedIps"`
|
||||
Tag string `json:"tag"`
|
||||
TimeoutMs uint64 `json:"timeoutMs"`
|
||||
Address *Address `json:"address"`
|
||||
ClientIP *Address `json:"clientIp"`
|
||||
Port uint16 `json:"port"`
|
||||
SkipFallback bool `json:"skipFallback"`
|
||||
Domains []string `json:"domains"`
|
||||
ExpectedIPs StringList `json:"expectedIPs"`
|
||||
ExpectIPs StringList `json:"expectIPs"`
|
||||
QueryStrategy string `json:"queryStrategy"`
|
||||
Tag string `json:"tag"`
|
||||
TimeoutMs uint64 `json:"timeoutMs"`
|
||||
DisableCache bool `json:"disableCache"`
|
||||
FinalQuery bool `json:"finalQuery"`
|
||||
UnexpectedIPs StringList `json:"unexpectedIPs"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &advanced); err == nil {
|
||||
c.Address = advanced.Address
|
||||
|
@ -55,9 +64,11 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
|||
c.ExpectedIPs = advanced.ExpectedIPs
|
||||
c.ExpectIPs = advanced.ExpectIPs
|
||||
c.QueryStrategy = advanced.QueryStrategy
|
||||
c.AllowUnexpectedIPs = advanced.AllowUnexpectedIPs
|
||||
c.Tag = advanced.Tag
|
||||
c.TimeoutMs = advanced.TimeoutMs
|
||||
c.DisableCache = advanced.DisableCache
|
||||
c.FinalQuery = advanced.FinalQuery
|
||||
c.UnexpectedIPs = advanced.UnexpectedIPs
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -105,13 +116,38 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
|||
})
|
||||
}
|
||||
|
||||
var expectedIPs = c.ExpectedIPs
|
||||
if len(expectedIPs) == 0 {
|
||||
expectedIPs = c.ExpectIPs
|
||||
if len(c.ExpectedIPs) == 0 {
|
||||
c.ExpectedIPs = c.ExpectIPs
|
||||
}
|
||||
geoipList, err := ToCidrList(expectedIPs)
|
||||
|
||||
actPrior := false
|
||||
var newExpectedIPs StringList
|
||||
for _, s := range c.ExpectedIPs {
|
||||
if s == "*" {
|
||||
actPrior = true
|
||||
} else {
|
||||
newExpectedIPs = append(newExpectedIPs, s)
|
||||
}
|
||||
}
|
||||
|
||||
actUnprior := false
|
||||
var newUnexpectedIPs StringList
|
||||
for _, s := range c.UnexpectedIPs {
|
||||
if s == "*" {
|
||||
actUnprior = true
|
||||
} else {
|
||||
newUnexpectedIPs = append(newUnexpectedIPs, s)
|
||||
}
|
||||
}
|
||||
|
||||
expectedGeoipList, err := ToCidrList(newExpectedIPs)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid IP rule: ", expectedIPs).Base(err)
|
||||
return nil, errors.New("invalid expected IP rule: ", c.ExpectedIPs).Base(err)
|
||||
}
|
||||
|
||||
unexpectedGeoipList, err := ToCidrList(newUnexpectedIPs)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid unexpected IP rule: ", c.UnexpectedIPs).Base(err)
|
||||
}
|
||||
|
||||
var myClientIP []byte
|
||||
|
@ -128,15 +164,19 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
|||
Address: c.Address.Build(),
|
||||
Port: uint32(c.Port),
|
||||
},
|
||||
ClientIp: myClientIP,
|
||||
SkipFallback: c.SkipFallback,
|
||||
PrioritizedDomain: domains,
|
||||
Geoip: geoipList,
|
||||
OriginalRules: originalRules,
|
||||
QueryStrategy: resolveQueryStrategy(c.QueryStrategy),
|
||||
AllowUnexpectedIPs: c.AllowUnexpectedIPs,
|
||||
Tag: c.Tag,
|
||||
TimeoutMs: c.TimeoutMs,
|
||||
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,
|
||||
FinalQuery: c.FinalQuery,
|
||||
UnexpectedGeoip: unexpectedGeoipList,
|
||||
ActUnprior: actUnprior,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -157,6 +197,7 @@ type DNSConfig struct {
|
|||
DisableCache bool `json:"disableCache"`
|
||||
DisableFallback bool `json:"disableFallback"`
|
||||
DisableFallbackIfMatch bool `json:"disableFallbackIfMatch"`
|
||||
UseSystemHosts bool `json:"useSystemHosts"`
|
||||
}
|
||||
|
||||
type HostAddress struct {
|
||||
|
@ -418,6 +459,15 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
|
|||
}
|
||||
config.StaticHosts = append(config.StaticHosts, staticHosts...)
|
||||
}
|
||||
if c.UseSystemHosts {
|
||||
systemHosts, err := readSystemHosts()
|
||||
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})
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
@ -430,7 +480,91 @@ func resolveQueryStrategy(queryStrategy string) dns.QueryStrategy {
|
|||
return dns.QueryStrategy_USE_IP4
|
||||
case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
|
||||
return dns.QueryStrategy_USE_IP6
|
||||
case "usesys", "usesystem", "use_sys", "use_system", "use-sys", "use-system":
|
||||
return dns.QueryStrategy_USE_SYS
|
||||
default:
|
||||
return dns.QueryStrategy_USE_IP
|
||||
}
|
||||
}
|
||||
|
||||
func readSystemHosts() (map[string][][]byte, error) {
|
||||
var hostsPath string
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
hostsPath = filepath.Join(os.Getenv("SystemRoot"), "System32", "drivers", "etc", "hosts")
|
||||
default:
|
||||
hostsPath = "/etc/hosts"
|
||||
}
|
||||
|
||||
file, err := os.Open(hostsPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hostsMap := make(map[string][][]byte)
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if i := strings.IndexByte(line, '#'); i >= 0 {
|
||||
// Discard comments.
|
||||
line = line[0:i]
|
||||
}
|
||||
f := getFields(line)
|
||||
if len(f) < 2 {
|
||||
continue
|
||||
}
|
||||
addr := net.ParseAddress(f[0])
|
||||
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}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hostsMap, nil
|
||||
}
|
||||
|
||||
func getFields(s string) []string { return splitAtBytes(s, " \r\t\n") }
|
||||
|
||||
// Count occurrences in s of any bytes in t.
|
||||
func countAnyByte(s string, t string) int {
|
||||
n := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if strings.IndexByte(t, s[i]) >= 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Split s at any bytes in t.
|
||||
func splitAtBytes(s string, t string) []string {
|
||||
a := make([]string, 1+countAnyByte(s, t))
|
||||
n := 0
|
||||
last := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if strings.IndexByte(t, s[i]) >= 0 {
|
||||
if last < i {
|
||||
a[n] = s[last:i]
|
||||
n++
|
||||
}
|
||||
last = i + 1
|
||||
}
|
||||
}
|
||||
if last < len(s) {
|
||||
a[n] = s[last:]
|
||||
n++
|
||||
}
|
||||
return a[0:n]
|
||||
}
|
||||
|
|
|
@ -699,25 +699,34 @@ type CustomSockoptConfig struct {
|
|||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type HappyEyeballsConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
PrioritizeIPv6 bool `json:"prioritizeIPv6"`
|
||||
TryDelayMs uint64 `json:"tryDelayMs"`
|
||||
Interleave uint32 `json:"interleave"`
|
||||
MaxConcurrentTry uint32 `json:"maxConcurrentTry"`
|
||||
}
|
||||
|
||||
type SocketConfig struct {
|
||||
Mark int32 `json:"mark"`
|
||||
TFO interface{} `json:"tcpFastOpen"`
|
||||
TProxy string `json:"tproxy"`
|
||||
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
DialerProxy string `json:"dialerProxy"`
|
||||
TCPKeepAliveInterval int32 `json:"tcpKeepAliveInterval"`
|
||||
TCPKeepAliveIdle int32 `json:"tcpKeepAliveIdle"`
|
||||
TCPCongestion string `json:"tcpCongestion"`
|
||||
TCPWindowClamp int32 `json:"tcpWindowClamp"`
|
||||
TCPMaxSeg int32 `json:"tcpMaxSeg"`
|
||||
Penetrate bool `json:"penetrate"`
|
||||
TCPUserTimeout int32 `json:"tcpUserTimeout"`
|
||||
V6only bool `json:"v6only"`
|
||||
Interface string `json:"interface"`
|
||||
TcpMptcp bool `json:"tcpMptcp"`
|
||||
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
|
||||
AddressPortStrategy string `json:"addressPortStrategy"`
|
||||
Mark int32 `json:"mark"`
|
||||
TFO interface{} `json:"tcpFastOpen"`
|
||||
TProxy string `json:"tproxy"`
|
||||
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
DialerProxy string `json:"dialerProxy"`
|
||||
TCPKeepAliveInterval int32 `json:"tcpKeepAliveInterval"`
|
||||
TCPKeepAliveIdle int32 `json:"tcpKeepAliveIdle"`
|
||||
TCPCongestion string `json:"tcpCongestion"`
|
||||
TCPWindowClamp int32 `json:"tcpWindowClamp"`
|
||||
TCPMaxSeg int32 `json:"tcpMaxSeg"`
|
||||
Penetrate bool `json:"penetrate"`
|
||||
TCPUserTimeout int32 `json:"tcpUserTimeout"`
|
||||
V6only bool `json:"v6only"`
|
||||
Interface string `json:"interface"`
|
||||
TcpMptcp bool `json:"tcpMptcp"`
|
||||
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
|
||||
AddressPortStrategy string `json:"addressPortStrategy"`
|
||||
HappyEyeballsSettings *HappyEyeballsConfig `json:"happyEyeballs"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
|
@ -809,6 +818,21 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||
return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy)
|
||||
}
|
||||
|
||||
var happyEyeballs = &internet.HappyEyeballsConfig{Enabled: true, Interleave: 1, PrioritizeIpv6: false, TryDelayMs: 250, MaxConcurrentTry: 4}
|
||||
if c.HappyEyeballsSettings != nil {
|
||||
happyEyeballs.Enabled = c.HappyEyeballsSettings.Enabled
|
||||
happyEyeballs.PrioritizeIpv6 = c.HappyEyeballsSettings.PrioritizeIPv6
|
||||
if c.HappyEyeballsSettings.Interleave > 0 {
|
||||
happyEyeballs.Interleave = c.HappyEyeballsSettings.Interleave
|
||||
}
|
||||
if c.HappyEyeballsSettings.TryDelayMs > 0 {
|
||||
happyEyeballs.TryDelayMs = c.HappyEyeballsSettings.TryDelayMs
|
||||
}
|
||||
if c.HappyEyeballsSettings.MaxConcurrentTry > 0 {
|
||||
happyEyeballs.MaxConcurrentTry = c.HappyEyeballsSettings.MaxConcurrentTry
|
||||
}
|
||||
}
|
||||
|
||||
return &internet.SocketConfig{
|
||||
Mark: c.Mark,
|
||||
Tfo: tfo,
|
||||
|
@ -828,6 +852,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||
TcpMptcp: c.TcpMptcp,
|
||||
CustomSockopt: customSockopts,
|
||||
AddressPortStrategy: addressPortStrategy,
|
||||
HappyEyeballs: happyEyeballs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ func TestSocketConfig(t *testing.T) {
|
|||
Tfo: 256,
|
||||
DomainStrategy: internet.DomainStrategy_USE_IP,
|
||||
DialerProxy: "tag",
|
||||
HappyEyeballs: &internet.HappyEyeballsConfig{Enabled: true, Interleave: 1, TryDelayMs: 250, PrioritizeIpv6: false, MaxConcurrentTry: 4},
|
||||
}
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
|
@ -45,8 +46,9 @@ func TestSocketConfig(t *testing.T) {
|
|||
|
||||
// test "tcpFastOpen": false, disabled TFO is expected
|
||||
expectedOutput = &internet.SocketConfig{
|
||||
Mark: 0,
|
||||
Tfo: -1,
|
||||
Mark: 0,
|
||||
Tfo: -1,
|
||||
HappyEyeballs: &internet.HappyEyeballsConfig{Enabled: true, Interleave: 1, TryDelayMs: 250, PrioritizeIpv6: false, MaxConcurrentTry: 4},
|
||||
}
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
|
@ -63,8 +65,9 @@ func TestSocketConfig(t *testing.T) {
|
|||
|
||||
// test "tcpFastOpen": 65535, queue length 65535 is expected
|
||||
expectedOutput = &internet.SocketConfig{
|
||||
Mark: 0,
|
||||
Tfo: 65535,
|
||||
Mark: 0,
|
||||
Tfo: 65535,
|
||||
HappyEyeballs: &internet.HappyEyeballsConfig{Enabled: true, Interleave: 1, TryDelayMs: 250, PrioritizeIpv6: false, MaxConcurrentTry: 4},
|
||||
}
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
|
@ -81,8 +84,9 @@ func TestSocketConfig(t *testing.T) {
|
|||
|
||||
// test "tcpFastOpen": -65535, disable TFO is expected
|
||||
expectedOutput = &internet.SocketConfig{
|
||||
Mark: 0,
|
||||
Tfo: -65535,
|
||||
Mark: 0,
|
||||
Tfo: -65535,
|
||||
HappyEyeballs: &internet.HappyEyeballsConfig{Enabled: true, Interleave: 1, TryDelayMs: 250, PrioritizeIpv6: false, MaxConcurrentTry: 4},
|
||||
}
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
|
@ -99,8 +103,9 @@ func TestSocketConfig(t *testing.T) {
|
|||
|
||||
// test "tcpFastOpen": 0, no operation is expected
|
||||
expectedOutput = &internet.SocketConfig{
|
||||
Mark: 0,
|
||||
Tfo: 0,
|
||||
Mark: 0,
|
||||
Tfo: 0,
|
||||
HappyEyeballs: &internet.HappyEyeballsConfig{Enabled: true, Interleave: 1, TryDelayMs: 250, PrioritizeIpv6: false, MaxConcurrentTry: 4},
|
||||
}
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
|
@ -117,8 +122,9 @@ func TestSocketConfig(t *testing.T) {
|
|||
|
||||
// test omit "tcpFastOpen", no operation is expected
|
||||
expectedOutput = &internet.SocketConfig{
|
||||
Mark: 0,
|
||||
Tfo: 0,
|
||||
Mark: 0,
|
||||
Tfo: 0,
|
||||
HappyEyeballs: &internet.HappyEyeballsConfig{Enabled: true, Interleave: 1, TryDelayMs: 250, PrioritizeIpv6: false, MaxConcurrentTry: 4},
|
||||
}
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
|
@ -133,8 +139,9 @@ func TestSocketConfig(t *testing.T) {
|
|||
|
||||
// test "tcpFastOpen": null, no operation is expected
|
||||
expectedOutput = &internet.SocketConfig{
|
||||
Mark: 0,
|
||||
Tfo: 0,
|
||||
Mark: 0,
|
||||
Tfo: 0,
|
||||
HappyEyeballs: &internet.HappyEyeballsConfig{Enabled: true, Interleave: 1, TryDelayMs: 250, PrioritizeIpv6: false, MaxConcurrentTry: 4},
|
||||
}
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue