Compare commits

...

18 Commits

Author SHA1 Message Date
c330d2c82c chore: adjust client-fingerprint's snippet 2023-03-13 07:02:47 +00:00
6f159d0cac docs: update example config 2023-03-12 12:49:49 -07:00
a35e40486b fix: consistent naming client-fingerprint 2023-03-12 12:27:37 -07:00
1bc3ecb027 Merge remote-tracking branch 'origin/Alpha' into dev-restls 2023-03-12 11:51:56 -07:00
c6a329281e feat: bump restls-client-go version 2023-03-12 11:51:28 -07:00
13111081be fix: SA4001 for net.UDPAddr copy 2023-03-12 23:37:45 +08:00
5de043acc6 fix: tuic relay tuic 2023-03-12 19:03:18 +08:00
7d230139a0 fix: rand ip error and clash remove loopback ip 2023-03-12 18:44:30 +08:00
0a6c848c9e feat: nameserver-policy support multiple keys
e.g.,
  nameserver-policy: #   'www.baidu.com': '114.114.114.114'
    #   '+.internal.crop.com': '10.0.0.1'
    "geosite:cn,private,apple":
      - https://doh.pub/dns-query
      - https://dns.alidns.com/dns-query
    "www.baidu.com,+.google.cn":
      - 223.5.5.5
      - 1.1.1.1
2023-03-12 16:56:29 +08:00
074fee2b48 chore: add comment 2023-03-12 15:05:28 +08:00
7f588935ea feta: add hosts support domain and mulitple ip (#439)
* feat: host support domain and multiple ips

* chore: append local address via `clash`

* chore: update hosts demo

* chore: unified parse mixed string and array

* fix: flatten cname

* chore: adjust logic

* chore: reuse code

* chore: use cname in tunnel

* chore: try use domain mapping when normal dns

* chore: format code
2023-03-12 15:00:59 +08:00
4b72ae7aab fix: global-client-fingerprint is now work 2023-03-12 13:35:59 +08:00
09b4a7ff15 chore: Remove useless mutex in Vision 2023-03-12 10:13:23 +08:00
7944522188 chore: update quic-go 2023-03-12 09:39:13 +08:00
828cd6463b Merge branch 'dev-restls' of github.com:MetaCubeX/Clash.Meta into dev-restls 2023-03-08 20:40:32 -08:00
5ce6ac0a62 chores: correct restls-client-go version 2023-03-08 20:38:30 -08:00
805b3c4669 fix: don't break shadowtls' working 2023-03-09 12:21:52 +08:00
e2d590ca12 feat: impl restls 2023-03-08 20:10:27 -08:00
21 changed files with 512 additions and 167 deletions

View File

@ -12,11 +12,13 @@ import (
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/restls"
obfs "github.com/Dreamacro/clash/transport/simple-obfs" obfs "github.com/Dreamacro/clash/transport/simple-obfs"
shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls" shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
restlsC "github.com/3andne/restls-client-go"
shadowsocks "github.com/metacubex/sing-shadowsocks" shadowsocks "github.com/metacubex/sing-shadowsocks"
"github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
@ -34,19 +36,21 @@ type ShadowSocks struct {
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
shadowTLSOption *shadowtls.ShadowTLSOption shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restlsC.Config
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"` Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"` PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"` UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
type simpleObfsOption struct { type simpleObfsOption struct {
@ -66,12 +70,18 @@ type v2rayObfsOption struct {
} }
type shadowTLSOption struct { type shadowTLSOption struct {
Password string `obfs:"password"` Password string `obfs:"password"`
Host string `obfs:"host"` Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"` Fingerprint string `obfs:"fingerprint,omitempty"`
ClientFingerprint string `obfs:"client-fingerprint,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` Version int `obfs:"version,omitempty"`
Version int `obfs:"version,omitempty"` }
type restlsOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
VersionHint string `obfs:"version-hint"`
RestlsScript string `obfs:"restls-script,omitempty"`
} }
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
@ -86,6 +96,7 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
return ss.streamConn(c, metadata) return ss.streamConn(c, metadata)
} }
@ -103,6 +114,12 @@ func (ss *ShadowSocks) streamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
case restls.Mode:
var err error
c, err = restls.NewRestls(c, ss.restlsConfig)
if err != nil {
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
}
} }
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
@ -202,6 +219,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var v2rayOption *v2rayObfs.Option var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restlsC.Config
obfsMode := "" obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@ -250,10 +268,23 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
Password: opt.Password, Password: opt.Password,
Host: opt.Host, Host: opt.Host,
Fingerprint: opt.Fingerprint, Fingerprint: opt.Fingerprint,
ClientFingerprint: opt.ClientFingerprint, ClientFingerprint: option.ClientFingerprint,
SkipCertVerify: opt.SkipCertVerify, SkipCertVerify: opt.SkipCertVerify,
Version: opt.Version, Version: opt.Version,
} }
} else if option.Plugin == restls.Mode {
obfsMode = restls.Mode
restlsOpt := &restlsOption{}
if err := decoder.Decode(option.PluginOpts, restlsOpt); err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
restlsConfig.SessionTicketsDisabled = true
if err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}
} }
return &ShadowSocks{ return &ShadowSocks{
@ -274,6 +305,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption: v2rayOption, v2rayOption: v2rayOption,
obfsOption: obfsOption, obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt, shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig,
}, nil }, nil
} }

View File

@ -24,6 +24,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &outbound.ShadowSocksOption{} ssOption := &outbound.ShadowSocksOption{}
if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
ssOption.ClientFingerprint = GlobalUtlsClient
}
err = decoder.Decode(mapping, ssOption) err = decoder.Decode(mapping, ssOption)
if err != nil { if err != nil {
break break

34
common/utils/slice.go Normal file
View File

@ -0,0 +1,34 @@
package utils
import (
"errors"
"fmt"
"reflect"
)
func Filter[T comparable](tSlice []T, filter func(t T) bool) []T {
result := make([]T, 0)
for _, t := range tSlice {
if filter(t) {
result = append(result, t)
}
}
return result
}
func ToStringSlice(value any) ([]string, error) {
strArr := make([]string, 0)
switch reflect.TypeOf(value).Kind() {
case reflect.Slice, reflect.Array:
origin := reflect.ValueOf(value)
for i := 0; i < origin.Len(); i++ {
item := fmt.Sprintf("%v", origin.Index(i))
strArr = append(strArr, item)
}
case reflect.String:
strArr = append(strArr, fmt.Sprintf("%v", value))
default:
return nil, errors.New("value format error, must be string or array")
}
return strArr, nil
}

113
component/resolver/host.go Normal file
View File

@ -0,0 +1,113 @@
package resolver
import (
"errors"
"net/netip"
"strings"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/trie"
"github.com/zhangyunhao116/fastrand"
)
type Hosts struct {
*trie.DomainTrie[HostValue]
}
func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts {
return Hosts{
hosts,
}
}
// Return the search result and whether to match the parameter `isDomain`
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
value := h.DomainTrie.Search(domain)
if value == nil {
return nil, false
}
hostValue := value.Data()
for {
if isDomain && hostValue.IsDomain {
return &hostValue, true
} else {
if node := h.DomainTrie.Search(hostValue.Domain); node != nil {
hostValue = node.Data()
} else {
break
}
}
}
if isDomain == hostValue.IsDomain {
return &hostValue, true
}
return &hostValue, false
}
type HostValue struct {
IsDomain bool
IPs []netip.Addr
Domain string
}
func NewHostValue(value any) (HostValue, error) {
isDomain := true
ips := make([]netip.Addr, 0)
domain := ""
if valueArr, err := utils.ToStringSlice(value); err != nil {
return HostValue{}, err
} else {
if len(valueArr) > 1 {
isDomain = false
for _, str := range valueArr {
if ip, err := netip.ParseAddr(str); err == nil {
ips = append(ips, ip)
} else {
return HostValue{}, err
}
}
} else if len(valueArr) == 1 {
host := valueArr[0]
if ip, err := netip.ParseAddr(host); err == nil {
ips = append(ips, ip)
isDomain = false
} else {
domain = host
}
}
}
if isDomain {
return NewHostValueByDomain(domain)
} else {
return NewHostValueByIPs(ips)
}
}
func NewHostValueByIPs(ips []netip.Addr) (HostValue, error) {
if len(ips) == 0 {
return HostValue{}, errors.New("ip list is empty")
}
return HostValue{
IsDomain: false,
IPs: ips,
}, nil
}
func NewHostValueByDomain(domain string) (HostValue, error) {
domain = strings.Trim(domain, ".")
item := strings.Split(domain, ".")
if len(item) < 2 {
return HostValue{}, errors.New("invaild domain")
}
return HostValue{
IsDomain: true,
Domain: domain,
}, nil
}
func (hv HostValue) RandIP() (netip.Addr, error) {
if hv.IsDomain {
return netip.Addr{}, errors.New("value type is error")
}
return hv.IPs[fastrand.Intn(len(hv.IPs))], nil
}

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -27,7 +28,7 @@ var (
DisableIPv6 = true DisableIPv6 = true
// DefaultHosts aim to resolve hosts // DefaultHosts aim to resolve hosts
DefaultHosts = trie.New[netip.Addr]() DefaultHosts = NewHosts(trie.New[HostValue]())
// DefaultDNSTimeout defined the default dns request timeout // DefaultDNSTimeout defined the default dns request timeout
DefaultDNSTimeout = time.Second * 5 DefaultDNSTimeout = time.Second * 5
@ -51,9 +52,11 @@ type Resolver interface {
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver // LookupIPv4WithResolver same as LookupIPv4, but with a resolver
func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) { func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil { if node, ok := DefaultHosts.Search(host, false); ok {
if ip := node.Data(); ip.Is4() { if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool {
return []netip.Addr{node.Data()}, nil return ip.Is4()
}); len(addrs) > 0 {
return addrs, nil
} }
} }
@ -106,9 +109,11 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
return nil, ErrIPv6Disabled return nil, ErrIPv6Disabled
} }
if node := DefaultHosts.Search(host); node != nil { if node, ok := DefaultHosts.Search(host, false); ok {
if ip := node.Data(); ip.Is6() { if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool {
return []netip.Addr{ip}, nil return ip.Is6()
}); len(addrs) > 0 {
return addrs, nil
} }
} }
@ -155,8 +160,8 @@ func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
// LookupIPWithResolver same as LookupIP, but with a resolver // LookupIPWithResolver same as LookupIP, but with a resolver
func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) { func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil { if node, ok := DefaultHosts.Search(host, false); ok {
return []netip.Addr{node.Data()}, nil return node.IPs, nil
} }
if r != nil { if r != nil {

View File

@ -4,12 +4,11 @@ import (
"container/list" "container/list"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"reflect" "regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@ -26,6 +25,7 @@ import (
"github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
P "github.com/Dreamacro/clash/component/process" P "github.com/Dreamacro/clash/component/process"
"github.com/Dreamacro/clash/component/resolver"
SNIFF "github.com/Dreamacro/clash/component/sniffer" SNIFF "github.com/Dreamacro/clash/component/sniffer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
@ -100,7 +100,7 @@ type DNS struct {
EnhancedMode C.DNSMode `yaml:"enhanced-mode"` EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool FakeIPRange *fakeip.Pool
Hosts *trie.DomainTrie[netip.Addr] Hosts *trie.DomainTrie[resolver.HostValue]
NameServerPolicy map[string][]dns.NameServer NameServerPolicy map[string][]dns.NameServer
ProxyServerNameserver []dns.NameServer ProxyServerNameserver []dns.NameServer
} }
@ -154,7 +154,7 @@ type Config struct {
IPTables *IPTables IPTables *IPTables
DNS *DNS DNS *DNS
Experimental *Experimental Experimental *Experimental
Hosts *trie.DomainTrie[netip.Addr] Hosts *trie.DomainTrie[resolver.HostValue]
Profile *Profile Profile *Profile
Rules []C.Rule Rules []C.Rule
SubRules map[string][]C.Rule SubRules map[string][]C.Rule
@ -265,7 +265,7 @@ type RawConfig struct {
Sniffer RawSniffer `yaml:"sniffer"` Sniffer RawSniffer `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
RuleProvider map[string]map[string]any `yaml:"rule-providers"` RuleProvider map[string]map[string]any `yaml:"rule-providers"`
Hosts map[string]string `yaml:"hosts"` Hosts map[string]any `yaml:"hosts"`
DNS RawDNS `yaml:"dns"` DNS RawDNS `yaml:"dns"`
Tun RawTun `yaml:"tun"` Tun RawTun `yaml:"tun"`
TuicServer RawTuicServer `yaml:"tuic-server"` TuicServer RawTuicServer `yaml:"tuic-server"`
@ -339,7 +339,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
UnifiedDelay: false, UnifiedDelay: false,
Authentication: []string{}, Authentication: []string{},
LogLevel: log.INFO, LogLevel: log.INFO,
Hosts: map[string]string{}, Hosts: map[string]any{},
Rule: []string{}, Rule: []string{},
Proxy: []map[string]any{}, Proxy: []map[string]any{},
ProxyGroup: []map[string]any{}, ProxyGroup: []map[string]any{},
@ -446,6 +446,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.General = general config.General = general
if len(config.General.GlobalClientFingerprint) != 0 {
log.Debugln("GlobalClientFingerprint:%s", config.General.GlobalClientFingerprint)
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
}
dialer.DefaultInterface.Store(config.General.Interface) dialer.DefaultInterface.Store(config.General.Interface)
proxies, providers, err := parseProxies(rawCfg) proxies, providers, err := parseProxies(rawCfg)
if err != nil { if err != nil {
@ -521,11 +526,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
if len(config.General.GlobalClientFingerprint) != 0 {
log.Debugln("GlobalClientFingerprint:%s", config.General.GlobalClientFingerprint)
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
}
return config, nil return config, nil
} }
@ -827,21 +827,47 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
return rules, nil return rules, nil
} }
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) { func parseHosts(cfg *RawConfig) (*trie.DomainTrie[resolver.HostValue], error) {
tree := trie.New[netip.Addr]() tree := trie.New[resolver.HostValue]()
// add default hosts // add default hosts
if err := tree.Insert("localhost", netip.AddrFrom4([4]byte{127, 0, 0, 1})); err != nil { hostValue, _ := resolver.NewHostValueByIPs(
[]netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1})})
if err := tree.Insert("localhost", hostValue); err != nil {
log.Errorln("insert localhost to host error: %s", err.Error()) log.Errorln("insert localhost to host error: %s", err.Error())
} }
if len(cfg.Hosts) != 0 { if len(cfg.Hosts) != 0 {
for domain, ipStr := range cfg.Hosts { for domain, anyValue := range cfg.Hosts {
ip, err := netip.ParseAddr(ipStr) if str, ok := anyValue.(string); ok && str == "clash" {
if err != nil { if addrs, err := net.InterfaceAddrs(); err != nil {
return nil, fmt.Errorf("%s is not a valid IP", ipStr) log.Errorln("insert clash to host error: %s", err)
} else {
ips := make([]netip.Addr, 0)
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ip, err := netip.ParseAddr(ipnet.IP.String()); err == nil {
ips = append(ips, ip)
}
}
}
anyValue = ips
}
} }
_ = tree.Insert(domain, ip) value, err := resolver.NewHostValue(anyValue)
if err != nil {
return nil, fmt.Errorf("%s is not a valid value", anyValue)
}
if value.IsDomain {
node := tree.Search(value.Domain)
for node != nil && node.Data().IsDomain {
if node.Data().Domain == domain {
return nil, fmt.Errorf("%s, there is a cycle in domain name mapping", domain)
}
node = tree.Search(node.Data().Domain)
}
}
_ = tree.Insert(domain, value)
} }
} }
tree.Optimize() tree.Optimize()
@ -959,26 +985,39 @@ func parsePureDNSServer(server string) string {
} }
func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][]dns.NameServer, error) { func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][]dns.NameServer, error) {
policy := map[string][]dns.NameServer{} policy := map[string][]dns.NameServer{}
updatedPolicy := make(map[string]interface{})
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
for domain, server := range nsPolicy { for k, v := range nsPolicy {
var ( if strings.Contains(k, "geosite:") {
nameservers []dns.NameServer subkeys := strings.Split(k, ":")
err error subkeys = subkeys[1:]
) subkeys = strings.Split(subkeys[0], ",")
//log.Infoln("subkeys:%+v", subkeys)
switch reflect.TypeOf(server).Kind() { for _, subkey := range subkeys {
case reflect.Slice, reflect.Array: newKey := "geosite:" + subkey
origin := reflect.ValueOf(server) //log.Infoln("newKey:%+v", newKey)
servers := make([]string, 0) updatedPolicy[newKey] = v
for i := 0; i < origin.Len(); i++ {
servers = append(servers, fmt.Sprintf("%v", origin.Index(i)))
} }
nameservers, err = parseNameServer(servers, preferH3) } else if re.MatchString(k) {
case reflect.String: subkeys := strings.Split(k, ",")
nameservers, err = parseNameServer([]string{fmt.Sprintf("%v", server)}, preferH3) //log.Infoln("subkeys:%+v", subkeys)
default: for _, subkey := range subkeys {
return nil, errors.New("server format error, must be string or array") updatedPolicy[subkey] = v
}
} else {
updatedPolicy[k] = v
} }
}
//log.Infoln("updatedPolicy:%+v", updatedPolicy)
for domain, server := range updatedPolicy {
servers, err := utils.ToStringSlice(server)
if err != nil {
return nil, err
}
nameservers, err := parseNameServer(servers, preferH3)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1041,7 +1080,7 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
return sites, nil return sites, nil
} }
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.Rule) (*DNS, error) { func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule) (*DNS, error) {
cfg := rawCfg.DNS cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 { if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty") return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")

View File

@ -8,7 +8,7 @@ import (
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/nnip" "github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/trie" R "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
@ -21,7 +21,7 @@ type (
middleware func(next handler) handler middleware func(next handler) handler
) )
func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip.Addr, string]) middleware { func withHosts(hosts R.Hosts, mapping *cache.LruCache[netip.Addr, string]) middleware {
return func(next handler) handler { return func(next handler) handler {
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) { return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
q := r.Question[0] q := r.Question[0]
@ -31,40 +31,68 @@ func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip
} }
host := strings.TrimRight(q.Name, ".") host := strings.TrimRight(q.Name, ".")
handleCName := func(resp *D.Msg, domain string) {
record := hosts.Search(host) rr := &D.CNAME{}
if record == nil { rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeCNAME, Class: D.ClassINET, Ttl: 10}
rr.Target = domain + "."
resp.Answer = append([]D.RR{rr}, resp.Answer...)
}
record, ok := hosts.Search(host, q.Qtype != D.TypeA && q.Qtype != D.TypeAAAA)
if !ok {
if record != nil && record.IsDomain {
// replace request domain
newR := r.Copy()
newR.Question[0].Name = record.Domain + "."
resp, err := next(ctx, newR)
if err == nil {
resp.Id = r.Id
resp.Question = r.Question
handleCName(resp, record.Domain)
}
return resp, err
}
return next(ctx, r) return next(ctx, r)
} }
ip := record.Data()
msg := r.Copy() msg := r.Copy()
handleIPs := func() {
if ip.Is4() && q.Qtype == D.TypeA { for _, ipAddr := range record.IPs {
rr := &D.A{} if ipAddr.Is4() && q.Qtype == D.TypeA {
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 10} rr := &D.A{}
rr.A = ip.AsSlice() rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 10}
rr.A = ipAddr.AsSlice()
msg.Answer = []D.RR{rr} msg.Answer = append(msg.Answer, rr)
} else if q.Qtype == D.TypeAAAA { if mapping != nil {
rr := &D.AAAA{} mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10))
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10} }
ip := ip.As16() } else if q.Qtype == D.TypeAAAA {
rr.AAAA = ip[:] rr := &D.AAAA{}
msg.Answer = []D.RR{rr} rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
} else { ip := ipAddr.As16()
return next(ctx, r) rr.AAAA = ip[:]
msg.Answer = append(msg.Answer, rr)
if mapping != nil {
mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10))
}
}
}
} }
if mapping != nil { switch q.Qtype {
mapping.SetWithExpire(ip, host, time.Now().Add(time.Second*10)) case D.TypeA:
handleIPs()
case D.TypeAAAA:
handleIPs()
case D.TypeCNAME:
handleCName(r, record.Domain)
default:
return next(ctx, r)
} }
ctx.SetType(context.DNSTypeHost) ctx.SetType(context.DNSTypeHost)
msg.SetRcode(r, D.RcodeSuccess) msg.SetRcode(r, D.RcodeSuccess)
msg.Authoritative = true msg.Authoritative = true
msg.RecursionAvailable = true msg.RecursionAvailable = true
return msg, nil return msg, nil
} }
} }
@ -149,6 +177,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
func withResolver(resolver *Resolver) handler { func withResolver(resolver *Resolver) handler {
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) { return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
ctx.SetType(context.DNSTypeRaw) ctx.SetType(context.DNSTypeRaw)
q := r.Question[0] q := r.Question[0]
// return a empty AAAA msg when ipv6 disabled // return a empty AAAA msg when ipv6 disabled
@ -183,7 +212,7 @@ func NewHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
middlewares := []middleware{} middlewares := []middleware{}
if resolver.hosts != nil { if resolver.hosts != nil {
middlewares = append(middlewares, withHosts(resolver.hosts, mapper.mapping)) middlewares = append(middlewares, withHosts(R.NewHosts(resolver.hosts), mapper.mapping))
} }
if mapper.mode == C.DNSFakeIP { if mapper.mode == C.DNSFakeIP {

View File

@ -43,7 +43,7 @@ type geositePolicyRecord struct {
type Resolver struct { type Resolver struct {
ipv6 bool ipv6 bool
ipv6Timeout time.Duration ipv6Timeout time.Duration
hosts *trie.DomainTrie[netip.Addr] hosts *trie.DomainTrie[resolver.HostValue]
main []dnsClient main []dnsClient
fallback []dnsClient fallback []dnsClient
fallbackDomainFilters []fallbackDomainFilter fallbackDomainFilters []fallbackDomainFilter
@ -430,7 +430,7 @@ type Config struct {
EnhancedMode C.DNSMode EnhancedMode C.DNSMode
FallbackFilter FallbackFilter FallbackFilter FallbackFilter
Pool *fakeip.Pool Pool *fakeip.Pool
Hosts *trie.DomainTrie[netip.Addr] Hosts *trie.DomainTrie[resolver.HostValue]
Policy map[string][]NameServer Policy map[string][]NameServer
} }

View File

@ -66,7 +66,7 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
} }
func isIPRequest(q D.Question) bool { func isIPRequest(q D.Question) bool {
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA || q.Qtype == D.TypeCNAME)
} }
func transform(servers []NameServer, resolver *Resolver) []dnsClient { func transform(servers []NameServer, resolver *Resolver) []dnsClient {

View File

@ -58,6 +58,9 @@ hosts:
# '*.clash.dev': 127.0.0.1 # '*.clash.dev': 127.0.0.1
# '.dev': 127.0.0.1 # '.dev': 127.0.0.1
# 'alpha.clash.dev': '::1' # 'alpha.clash.dev': '::1'
# test.com: [1.1.1.1, 2.2.2.2]
# clash.lan: clash # clash 为特别字段,将加入本地所有网卡的地址
# baidu.com: google.com # 只允许配置一个别名
profile: # 存储 select 选择记录 profile: # 存储 select 选择记录
store-selected: false store-selected: false
@ -228,12 +231,13 @@ dns:
# - '+.youtube.com' # - '+.youtube.com'
# 配置查询域名使用的 DNS 服务器 # 配置查询域名使用的 DNS 服务器
nameserver-policy: # 'www.baidu.com': '114.114.114.114' nameserver-policy:
# 'www.baidu.com': '114.114.114.114'
# '+.internal.crop.com': '10.0.0.1' # '+.internal.crop.com': '10.0.0.1'
"geosite:cn": "geosite:cn,private,apple":
- https://doh.pub/dns-query - https://doh.pub/dns-query
- https://dns.alidns.com/dns-query - https://dns.alidns.com/dns-query
"www.baidu.com": [https://doh.pub/dns-query, https://dns.alidns.com/dns-query] "www.baidu.com,+.google.cn": [223.5.5.5, https://dns.alidns.com/dns-query]
proxies: # socks5 proxies: # socks5
- name: "socks" - name: "socks"
@ -327,17 +331,55 @@ proxies: # socks5
# headers: # headers:
# custom: value # custom: value
- name: "ss4" - name: "ss4-shadow-tls"
type: ss type: ss
server: server server: server
port: 443 port: 443
cipher: chacha20-ietf-poly1305 cipher: chacha20-ietf-poly1305
password: "password" password: "password"
plugin: shadow-tls plugin: shadow-tls
client-fingerprint: chrome
plugin-opts: plugin-opts:
host: "cloud.tencent.com" host: "cloud.tencent.com"
password: "shadow_tls_password" password: "shadow_tls_password"
version: 2 # support 1/2/3 version: 2 # support 1/2/3
- name: "ss-restls-tls13"
type: ss
server: [YOUR_SERVER_IP]
port: 443
cipher: chacha20-ietf-poly1305
password: [YOUR_SS_PASSWORD]
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
# 可以是chrome, ios, firefox, safari中的一个
plugin: restls
plugin-opts:
host: "www.microsoft.com" # Must be a TLS 1.3 server
# 应当是一个TLS 1.3 服务器
password: [YOUR_RESTLS_PASSWORD]
version-hint: "tls13"
# Control your post-handshake traffic through restls-script
# Hide proxy behaviors like "tls in tls".
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
# 用restls剧本来控制握手后的行为隐藏"tls in tls"等特征
# 详情https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"
- name: "ss-restls-tls12"
type: ss
server: [YOUR_SERVER_IP]
port: 443
cipher: chacha20-ietf-poly1305
password: [YOUR_SS_PASSWORD]
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
# 可以是chrome, ios, firefox, safari中的一个
plugin: restls
plugin-opts:
host: "vscode.dev" # Must be a TLS 1.2 server
# 应当是一个TLS 1.2 服务器
password: [YOUR_RESTLS_PASSWORD]
version-hint: "tls12"
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
# vmess # vmess
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none # cipher支持 auto/aes-128-gcm/chacha20-poly1305/none

8
go.mod
View File

@ -18,7 +18,7 @@ require (
github.com/jpillora/backoff v1.0.0 github.com/jpillora/backoff v1.0.0
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7 github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7
github.com/metacubex/quic-go v0.32.0 github.com/metacubex/quic-go v0.33.1
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947 github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3 github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3
github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb
@ -51,6 +51,7 @@ require (
) )
require ( require (
github.com/3andne/restls-client-go v0.1.4
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
@ -69,9 +70,8 @@ require (
github.com/oschwald/maxminddb-golang v1.10.0 // indirect github.com/oschwald/maxminddb-golang v1.10.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect

16
go.sum
View File

@ -1,3 +1,5 @@
github.com/3andne/restls-client-go v0.1.4 h1:kLNC2aSRHPlEVYmTj6EOqJoorCpobEe2toMRSfBF7FU=
github.com/3andne/restls-client-go v0.1.4/go.mod h1:04CGbRk1BwBiEDles8b5mlKgTqIwE5MqF7JDloJV47I=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
@ -89,8 +91,8 @@ github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005 h1:0TEvReK/D6YLszjGj/bdx4d7amQSjQ2X/98r4ZiUbxU= github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005 h1:0TEvReK/D6YLszjGj/bdx4d7amQSjQ2X/98r4ZiUbxU=
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE= github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE=
github.com/metacubex/quic-go v0.32.0 h1:dSD8LB4MSeBuD4otd8y1DUZcRdDcEB0Ax5esPOqn2Hw= github.com/metacubex/quic-go v0.33.1 h1:ZIxZFGivpSLOEZuuNkLy+aPvo1RP4uRBjNg3SAkXwIg=
github.com/metacubex/quic-go v0.32.0/go.mod h1:yParIzDYUd/t/pzFlDtZKhnvSqbUu0bPChlKEGmJStA= github.com/metacubex/quic-go v0.33.1/go.mod h1:9nOiGX6kqV3+ZbkDKdTNzdFD726QQHPH6WDb36jUSpA=
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947 h1:NnjC2+aIiyzzvFlo+C2WzBOJdsp+HAtu18FZomqYhUE= github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947 h1:NnjC2+aIiyzzvFlo+C2WzBOJdsp+HAtu18FZomqYhUE=
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947/go.mod h1:U2gwhxzqgbhKCgn2B4z3t0Cj0LpMWFl/02BGCoG421w= github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947/go.mod h1:U2gwhxzqgbhKCgn2B4z3t0Cj0LpMWFl/02BGCoG421w=
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3 h1:oQLThm1a8E7hHmoM9XF2cO0FZPsHVynC4YXW4b3liUI= github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3 h1:oQLThm1a8E7hHmoM9XF2cO0FZPsHVynC4YXW4b3liUI=
@ -115,12 +117,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U= github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk= github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=

View File

@ -226,8 +226,8 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
dns.ReCreateServer(c.Listen, r, m) dns.ReCreateServer(c.Listen, r, m)
} }
func updateHosts(tree *trie.DomainTrie[netip.Addr]) { func updateHosts(tree *trie.DomainTrie[resolver.HostValue]) {
resolver.DefaultHosts = tree resolver.DefaultHosts = resolver.NewHosts(tree)
} }
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) { func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {

View File

@ -44,7 +44,7 @@ func Parse(options ...Option) error {
if cfg.General.ExternalController != "" { if cfg.General.ExternalController != "" {
go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS, go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS,
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey,cfg.General.LogLevel==log.DEBUG) cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG)
} }
executor.ApplyConfig(cfg, true) executor.ApplyConfig(cfg, true)

View File

@ -0,0 +1,57 @@
package restls
import (
"net"
tls "github.com/3andne/restls-client-go"
)
const (
Mode string = "restls"
)
// Restls
type Restls struct {
net.Conn
firstPacketCache []byte
firstPacket bool
}
func (r *Restls) Read(b []byte) (int, error) {
if err := r.Conn.(*tls.UConn).Handshake(); err != nil {
return 0, err
}
n, err := r.Conn.(*tls.UConn).Read(b)
return n, err
}
func (r *Restls) Write(b []byte) (int, error) {
if r.firstPacket {
r.firstPacketCache = append([]byte(nil), b...)
r.firstPacket = false
return len(b), nil
}
if len(r.firstPacketCache) != 0 {
b = append(r.firstPacketCache, b...)
r.firstPacketCache = nil
}
n, err := r.Conn.(*tls.UConn).Write(b)
return n, err
}
// NewRestls return a Restls Connection
func NewRestls(conn net.Conn, config *tls.Config) (net.Conn, error) {
if config != nil {
clientIDPtr := config.ClientID.Load()
if clientIDPtr != nil {
return &Restls{
Conn: tls.UClient(conn, config, *clientIDPtr),
firstPacket: true,
}, nil
}
}
return &Restls{
Conn: tls.UClient(conn, config, tls.HelloChrome_Auto),
firstPacket: true,
}, nil
}

View File

@ -200,7 +200,7 @@ func (q *quicStreamPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err err
} }
func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if len(p) > q.maxUdpRelayPacketSize { if q.udpRelayMode != "quic" && len(p) > q.maxUdpRelayPacketSize {
return 0, fmt.Errorf("udp packet too large(%d > %d)", len(p), q.maxUdpRelayPacketSize) return 0, fmt.Errorf("udp packet too large(%d > %d)", len(p), q.maxUdpRelayPacketSize)
} }
if q.closed { if q.closed {
@ -215,7 +215,6 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
q.deferQuicConnFn(q.quicConn, err) q.deferQuicConnFn(q.quicConn, err)
}() }()
} }
addr.String()
buf := pool.GetBuffer() buf := pool.GetBuffer()
defer pool.PutBuffer(buf) defer pool.PutBuffer(buf)
addrPort, err := netip.ParseAddrPort(addr.String()) addrPort, err := netip.ParseAddrPort(addr.String())
@ -239,7 +238,8 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
return return
} }
default: // native default: // native
err = q.quicConn.SendMessage(buf.Bytes()) data := buf.Bytes()
err = q.quicConn.SendMessage(data)
if err != nil { if err != nil {
return return
} }
@ -250,7 +250,29 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
} }
func (q *quicStreamPacketConn) LocalAddr() net.Addr { func (q *quicStreamPacketConn) LocalAddr() net.Addr {
return q.quicConn.LocalAddr() addr := q.quicConn.LocalAddr()
if q.inputConn != nil { // client
return &packetAddr{addrStr: q.quicConn.LocalAddr().String(), connId: q.connId, rawAddr: addr}
}
return addr // server
} }
var _ net.PacketConn = &quicStreamPacketConn{} var _ net.PacketConn = &quicStreamPacketConn{}
type packetAddr struct {
addrStr string
connId uint32
rawAddr net.Addr
}
func (a packetAddr) Network() string {
return "tuic"
}
func (a packetAddr) String() string {
return fmt.Sprintf("%s-%d", a.addrStr, a.connId)
}
func (a packetAddr) RawAddr() net.Addr {
return a.rawAddr
}

View File

@ -114,9 +114,6 @@ func NewAuthenticate(TKN [32]byte) Authenticate {
func ReadAuthenticateWithHead(head CommandHead, reader BufferedReader) (c Authenticate, err error) { func ReadAuthenticateWithHead(head CommandHead, reader BufferedReader) (c Authenticate, err error) {
c.CommandHead = head c.CommandHead = head
if err != nil {
return
}
if c.CommandHead.TYPE != AuthenticateType { if c.CommandHead.TYPE != AuthenticateType {
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE) err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
return return
@ -170,9 +167,6 @@ func NewConnect(ADDR Address) Connect {
func ReadConnectWithHead(head CommandHead, reader BufferedReader) (c Connect, err error) { func ReadConnectWithHead(head CommandHead, reader BufferedReader) (c Connect, err error) {
c.CommandHead = head c.CommandHead = head
if err != nil {
return
}
if c.CommandHead.TYPE != ConnectType { if c.CommandHead.TYPE != ConnectType {
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE) err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
return return
@ -228,9 +222,6 @@ func NewPacket(ASSOC_ID uint32, LEN uint16, ADDR Address, DATA []byte) Packet {
func ReadPacketWithHead(head CommandHead, reader BufferedReader) (c Packet, err error) { func ReadPacketWithHead(head CommandHead, reader BufferedReader) (c Packet, err error) {
c.CommandHead = head c.CommandHead = head
if err != nil {
return
}
if c.CommandHead.TYPE != PacketType { if c.CommandHead.TYPE != PacketType {
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE) err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
return return
@ -305,9 +296,6 @@ func NewDissociate(ASSOC_ID uint32) Dissociate {
func ReadDissociateWithHead(head CommandHead, reader BufferedReader) (c Dissociate, err error) { func ReadDissociateWithHead(head CommandHead, reader BufferedReader) (c Dissociate, err error) {
c.CommandHead = head c.CommandHead = head
if err != nil {
return
}
if c.CommandHead.TYPE != DissociateType { if c.CommandHead.TYPE != DissociateType {
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE) err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
return return
@ -476,15 +464,17 @@ func NewAddress(metadata *C.Metadata) Address {
func NewAddressAddrPort(addrPort netip.AddrPort) Address { func NewAddressAddrPort(addrPort netip.AddrPort) Address {
var addrType byte var addrType byte
if addrPort.Addr().Is4() { port := addrPort.Port()
addr := addrPort.Addr().Unmap()
if addr.Is4() {
addrType = AtypIPv4 addrType = AtypIPv4
} else { } else {
addrType = AtypIPv6 addrType = AtypIPv6
} }
return Address{ return Address{
TYPE: addrType, TYPE: addrType,
ADDR: addrPort.Addr().AsSlice(), ADDR: addr.AsSlice(),
PORT: addrPort.Port(), PORT: port,
} }
} }

View File

@ -5,7 +5,6 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"fmt"
"net" "net"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -154,14 +153,10 @@ func (s *serverHandler) parsePacket(packet Packet, udpRelayMode string) (err err
return s.HandleUdpFn(packet.ADDR.SocksAddr(), &serverUDPPacket{ return s.HandleUdpFn(packet.ADDR.SocksAddr(), &serverUDPPacket{
pc: pc, pc: pc,
packet: &packet, packet: &packet,
rAddr: s.genServerAssocIdAddr(assocId, s.quicConn.RemoteAddr()), rAddr: &packetAddr{addrStr: "tuic-" + s.uuid.String(), connId: assocId, rawAddr: s.quicConn.RemoteAddr()},
}) })
} }
func (s *serverHandler) genServerAssocIdAddr(assocId uint32, addr net.Addr) net.Addr {
return &ServerAssocIdAddr{assocId: fmt.Sprintf("tuic-%s-%d", s.uuid.String(), assocId), addr: addr}
}
func (s *serverHandler) handleStream() (err error) { func (s *serverHandler) handleStream() (err error) {
for { for {
var quicStream quic.Stream var quicStream quic.Stream
@ -276,23 +271,6 @@ func (s *serverHandler) handleUniStream() (err error) {
} }
} }
type ServerAssocIdAddr struct {
assocId string
addr net.Addr
}
func (a ServerAssocIdAddr) Network() string {
return "ServerAssocIdAddr"
}
func (a ServerAssocIdAddr) String() string {
return a.assocId
}
func (a ServerAssocIdAddr) RawAddr() net.Addr {
return a.addr
}
type serverUDPPacket struct { type serverUDPPacket struct {
pc *quicStreamPacketConn pc *quicStreamPacketConn
packet *Packet packet *Packet

View File

@ -3,7 +3,6 @@ package vless
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"sync"
"github.com/Dreamacro/clash/common/buf" "github.com/Dreamacro/clash/common/buf"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
@ -20,12 +19,9 @@ const (
commandPaddingDirect byte = 0x02 commandPaddingDirect byte = 0x02
) )
var mutex sync.RWMutex
func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid.UUID, paddingTLS bool) { func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid.UUID, paddingTLS bool) {
contentLen := int32(len(p)) contentLen := int32(len(p))
var paddingLen int32 var paddingLen int32
mutex.Lock()
if contentLen < 900 { if contentLen < 900 {
if paddingTLS { if paddingTLS {
//log.Debugln("long padding") //log.Debugln("long padding")
@ -34,8 +30,7 @@ func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid
paddingLen = fastrand.Int31n(256) paddingLen = fastrand.Int31n(256)
} }
} }
mutex.Unlock() if userUUID != nil {
if userUUID != nil { // unnecessary, but keep the same with Xray
buffer.Write(userUUID.Bytes()) buffer.Write(userUUID.Bytes())
} }
@ -51,7 +46,6 @@ func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid
func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID, paddingTLS bool) { func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID, paddingTLS bool) {
contentLen := int32(buffer.Len()) contentLen := int32(buffer.Len())
var paddingLen int32 var paddingLen int32
mutex.Lock()
if contentLen < 900 { if contentLen < 900 {
if paddingTLS { if paddingTLS {
//log.Debugln("long padding") //log.Debugln("long padding")
@ -60,12 +54,11 @@ func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID, padding
paddingLen = fastrand.Int31n(256) paddingLen = fastrand.Int31n(256)
} }
} }
mutex.Unlock()
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(paddingLen)) binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(paddingLen))
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(contentLen)) binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(contentLen))
buffer.ExtendHeader(1)[0] = command buffer.ExtendHeader(1)[0] = command
if userUUID != nil { // unnecessary, but keep the same with Xray if userUUID != nil {
copy(buffer.ExtendHeader(uuid.Size), userUUID.Bytes()) copy(buffer.ExtendHeader(uuid.Size), userUUID.Bytes())
} }

View File

@ -46,7 +46,8 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, oAddr,
} }
fromUDPAddr := from.(*net.UDPAddr) fromUDPAddr := from.(*net.UDPAddr)
fromUDPAddr = &(*fromUDPAddr) // make a copy _fromUDPAddr := *fromUDPAddr
fromUDPAddr = &_fromUDPAddr // make a copy
if fromAddr, ok := netip.AddrFromSlice(fromUDPAddr.IP); ok { if fromAddr, ok := netip.AddrFromSlice(fromUDPAddr.IP); ok {
if fAddr.IsValid() && (oAddr.Unmap() == fromAddr.Unmap()) { if fAddr.IsValid() && (oAddr.Unmap() == fromAddr.Unmap()) {
fromUDPAddr.IP = fAddr.Unmap().AsSlice() fromUDPAddr.IP = fAddr.Unmap().AsSlice()

View File

@ -201,13 +201,18 @@ func preHandleMetadata(metadata *C.Metadata) error {
if resolver.FakeIPEnabled() { if resolver.FakeIPEnabled() {
metadata.DstIP = netip.Addr{} metadata.DstIP = netip.Addr{}
metadata.DNSMode = C.DNSFakeIP metadata.DNSMode = C.DNSFakeIP
} else if node := resolver.DefaultHosts.Search(host); node != nil { } else if node, ok := resolver.DefaultHosts.Search(host, false); ok {
// redir-host should lookup the hosts // redir-host should lookup the hosts
metadata.DstIP = node.Data() metadata.DstIP, _ = node.RandIP()
} else if node != nil && node.IsDomain {
metadata.Host = node.Domain
} }
} else if resolver.IsFakeIP(metadata.DstIP) { } else if resolver.IsFakeIP(metadata.DstIP) {
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
} }
} else if node, ok := resolver.DefaultHosts.Search(metadata.Host, true); ok {
// try use domain mapping
metadata.Host = node.Domain
} }
return nil return nil
@ -392,8 +397,8 @@ func handleTCPConn(connCtx C.ConnContext) {
dialMetadata := metadata dialMetadata := metadata
if len(metadata.Host) > 0 { if len(metadata.Host) > 0 {
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil { if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
if dstIp := node.Data(); !FakeIPRange().Contains(dstIp) { if dstIp, _ := node.RandIP(); !FakeIPRange().Contains(dstIp) {
dialMetadata.DstIP = dstIp dialMetadata.DstIP = dstIp
dialMetadata.DNSMode = C.DNSHosts dialMetadata.DNSMode = C.DNSHosts
dialMetadata = dialMetadata.Pure() dialMetadata = dialMetadata.Pure()
@ -498,8 +503,8 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
processFound bool processFound bool
) )
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil { if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
metadata.DstIP = node.Data() metadata.DstIP, _ = node.RandIP()
resolved = true resolved = true
} }