Compare commits

...

7 Commits

37 changed files with 326 additions and 289 deletions

View File

@ -152,7 +152,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (v *Vless) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vless) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return nil, errors.New("can't resolve ip") return nil, errors.New("can't resolve ip")
} }
@ -245,7 +245,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
if v.transport != nil && len(opts) == 0 { if v.transport != nil && len(opts) == 0 {
// vless use stream-oriented udp with a special address, so we needs a net.UDPAddr // vless use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return nil, errors.New("can't resolve ip") return nil, errors.New("can't resolve ip")
} }

View File

@ -203,7 +203,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (v *Vmess) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vmess) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return c, fmt.Errorf("can't resolve ip: %w", err) return c, fmt.Errorf("can't resolve ip: %w", err)
} }
@ -255,7 +255,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
if v.transport != nil && len(opts) == 0 { if v.transport != nil && len(opts) == 0 {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't resolve ip: %w", err) return nil, fmt.Errorf("can't resolve ip: %w", err)
} }

View File

@ -3,6 +3,8 @@ package outboundgroup
import ( import (
"time" "time"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outbound"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
) )
@ -11,14 +13,19 @@ const (
defaultGetProxiesDuration = time.Second * 5 defaultGetProxiesDuration = time.Second * 5
) )
var defaultRejectProxy = adapter.NewProxy(outbound.NewReject())
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy { func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy {
proxies := []C.Proxy{} proxies := []C.Proxy{}
for _, provider := range providers { for _, pd := range providers {
if touch { if touch {
proxies = append(proxies, provider.ProxiesWithTouch()...) proxies = append(proxies, pd.ProxiesWithTouch()...)
} else { } else {
proxies = append(proxies, provider.Proxies()...) proxies = append(proxies, pd.Proxies()...)
} }
} }
if len(proxies) == 0 {
proxies = append(proxies, defaultRejectProxy)
}
return proxies return proxies
} }

View File

@ -30,10 +30,8 @@ type LoadBalance struct {
var errStrategy = errors.New("unsupported strategy") var errStrategy = errors.New("unsupported strategy")
func parseStrategy(config map[string]any) string { func parseStrategy(config map[string]any) string {
if elm, ok := config["strategy"]; ok { if strategy, ok := config["strategy"].(string); ok {
if strategy, ok := elm.(string); ok { return strategy
return strategy
}
} }
return "consistent-hashing" return "consistent-hashing"
} }

View File

@ -78,30 +78,18 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
return nil, errDuplicateProvider return nil, errDuplicateProvider
} }
// select don't need health check hc, err := newHealthCheck(ps, groupOption)
if groupOption.Type == "select" || groupOption.Type == "relay" { if err != nil {
hc := provider.NewHealthCheck(ps, "", 0, true) return nil, err
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
} else {
if groupOption.URL == "" || groupOption.Interval == 0 {
return nil, errMissHealthCheck
}
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
} }
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
} }
if len(groupOption.Use) != 0 { if len(groupOption.Use) != 0 {
@ -109,7 +97,12 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
if err != nil { if err != nil {
return nil, err return nil, err
} }
providers = append(providers, list...)
if groupOption.Type == "fallback" {
providers = append(list, providers...)
} else {
providers = append(providers, list...)
}
} }
var group C.ProxyAdapter var group C.ProxyAdapter
@ -163,22 +156,18 @@ func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupComm
} }
if filterRegx != nil { if filterRegx != nil {
var hc *provider.HealthCheck hc, err := newHealthCheck([]C.Proxy{}, groupOption)
if groupOption.Type == "select" || groupOption.Type == "relay" { if err != nil {
hc = provider.NewHealthCheck([]C.Proxy{}, "", 0, true) return nil, err
} else {
if groupOption.URL == "" || groupOption.Interval == 0 {
return nil, errMissHealthCheck
}
hc = provider.NewHealthCheck([]C.Proxy{}, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
} }
if _, ok = mapping[groupName]; ok { gName := groupName
groupName += "->" + p.Name() if _, ok = mapping[gName]; ok {
gName = groupName + " -> " + p.Name()
} }
pd := p.(*provider.ProxySetProvider) pd := p.(*provider.ProxySetProvider)
p = provider.NewProxyFilterProvider(groupName, pd, hc, filterRegx) p = provider.NewProxyFilterProvider(gName, pd, hc, filterRegx)
pd.RegisterProvidersInUse(p) pd.RegisterProvidersInUse(p)
} }
@ -186,3 +175,18 @@ func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupComm
} }
return ps, nil return ps, nil
} }
func newHealthCheck(ps []C.Proxy, groupOption *GroupCommonOption) (*provider.HealthCheck, error) {
var hc *provider.HealthCheck
// select don't need health check
if groupOption.Type == "select" || groupOption.Type == "relay" {
hc = provider.NewHealthCheck(ps, "", 0, true)
} else {
if groupOption.URL == "" || groupOption.Interval == 0 {
return nil, errMissHealthCheck
}
hc = provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
}
return hc, nil
}

View File

@ -98,7 +98,11 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
} }
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
selected := providers[0].Proxies()[0].Name() selected := "REJECT"
if len(providers) != 0 && len(providers[0].Proxies()) != 0 {
selected = providers[0].Proxies()[0].Name()
}
return &Selector{ return &Selector{
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,

View File

@ -125,10 +125,8 @@ func parseURLTestOption(config map[string]any) []urlTestOption {
opts := []urlTestOption{} opts := []urlTestOption{}
// tolerance // tolerance
if elm, ok := config["tolerance"]; ok { if tolerance, ok := config["tolerance"].(int); ok {
if tolerance, ok := elm.(int); ok { opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
}
} }
return opts return opts

View File

@ -5,6 +5,7 @@ import (
"crypto/md5" "crypto/md5"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"time" "time"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
@ -14,6 +15,8 @@ import (
var ( var (
fileMode os.FileMode = 0o666 fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755 dirMode os.FileMode = 0o755
commentRegx = regexp.MustCompile(`(.*#.*\n)`)
) )
type parser[V any] func([]byte) (V, error) type parser[V any] func([]byte) (V, error)
@ -168,6 +171,18 @@ func safeWrite(path string, buf []byte) error {
return os.WriteFile(path, buf, fileMode) return os.WriteFile(path, buf, fileMode)
} }
func removeComment(buf []byte) []byte {
arr := commentRegx.FindAllSubmatch(buf, -1)
for _, subs := range arr {
sub := subs[0]
if !bytes.HasPrefix(bytes.TrimLeft(sub, " "), []byte("#")) {
continue
}
buf = bytes.Replace(buf, sub, []byte(""), 1)
}
return buf
}
func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] { func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
var ticker *time.Ticker var ticker *time.Ticker
if interval != 0 { if interval != 0 {

View File

@ -25,10 +25,16 @@ type HealthCheck struct {
interval uint interval uint
lazy bool lazy bool
lastTouch *atomic.Int64 lastTouch *atomic.Int64
running *atomic.Bool
done chan struct{} done chan struct{}
} }
func (hc *HealthCheck) process() { func (hc *HealthCheck) process() {
if hc.running.Load() {
return
}
hc.running.Store(true)
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
go func() { go func() {
@ -84,6 +90,10 @@ func (hc *HealthCheck) check() {
} }
func (hc *HealthCheck) close() { func (hc *HealthCheck) close() {
if !hc.running.Load() {
return
}
hc.running.Store(false)
hc.done <- struct{}{} hc.done <- struct{}{}
} }
@ -94,6 +104,7 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *He
interval: interval, interval: interval,
lazy: lazy, lazy: lazy,
lastTouch: atomic.NewInt64(0), lastTouch: atomic.NewInt64(0),
running: atomic.NewBool(false),
done: make(chan struct{}, 1), done: make(chan struct{}, 1),
} }
} }

View File

@ -234,7 +234,9 @@ func (pf *proxyFilterProvider) HealthCheck() {
} }
func (pf *proxyFilterProvider) Update() error { func (pf *proxyFilterProvider) Update() error {
var proxies []C.Proxy pf.healthCheck.close()
proxies := []C.Proxy{}
if pf.filter != nil { if pf.filter != nil {
for _, proxy := range pf.psd.Proxies() { for _, proxy := range pf.psd.Proxies() {
if !pf.filter.MatchString(proxy.Name()) { if !pf.filter.MatchString(proxy.Name()) {
@ -248,6 +250,10 @@ func (pf *proxyFilterProvider) Update() error {
pf.proxies = proxies pf.proxies = proxies
pf.healthCheck.setProxy(proxies) pf.healthCheck.setProxy(proxies)
if len(proxies) != 0 && pf.healthCheck.auto() {
go pf.healthCheck.process()
}
return nil return nil
} }
@ -286,10 +292,6 @@ func NewProxyFilterProvider(name string, psd *ProxySetProvider, hc *HealthCheck,
_ = pd.Update() _ = pd.Update()
if hc.auto() {
go hc.process()
}
wrapper := &ProxyFilterProvider{pd} wrapper := &ProxyFilterProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyFilterProvider) runtime.SetFinalizer(wrapper, stopProxyFilterProvider)
return wrapper return wrapper
@ -319,12 +321,13 @@ func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVer
proxies := []C.Proxy{} proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies { for idx, mapping := range schema.Proxies {
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) { name, ok := mapping["name"].(string)
if ok && len(filter) > 0 && !filterReg.MatchString(name) {
continue continue
} }
if prefixName != "" { if prefixName != "" {
mapping["name"] = prefixName + mapping["name"].(string) mapping["name"] = prefixName + name
} }
proxy, err := adapter.ParseProxy(mapping, forceCertVerify) proxy, err := adapter.ParseProxy(mapping, forceCertVerify)

View File

@ -103,7 +103,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
return nil, err return nil, err
} }
return buf, nil return removeComment(buf), nil
} }
func NewHTTPVehicle(url string, path string, header http.Header) *HTTPVehicle { func NewHTTPVehicle(url string, path string, header http.Header) *HTTPVehicle {

View File

@ -2,6 +2,7 @@ package cert
import ( import (
"crypto/tls" "crypto/tls"
"sync"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
) )
@ -9,10 +10,14 @@ import (
// DomainTrieCertsStorage cache wildcard certificates // DomainTrieCertsStorage cache wildcard certificates
type DomainTrieCertsStorage struct { type DomainTrieCertsStorage struct {
certsCache *trie.DomainTrie[*tls.Certificate] certsCache *trie.DomainTrie[*tls.Certificate]
lock sync.RWMutex
} }
// Get gets the certificate from the storage // Get gets the certificate from the storage
func (c *DomainTrieCertsStorage) Get(key string) (*tls.Certificate, bool) { func (c *DomainTrieCertsStorage) Get(key string) (*tls.Certificate, bool) {
c.lock.RLock()
defer c.lock.RUnlock()
ca := c.certsCache.Search(key) ca := c.certsCache.Search(key)
if ca == nil { if ca == nil {
return nil, false return nil, false
@ -22,7 +27,9 @@ func (c *DomainTrieCertsStorage) Get(key string) (*tls.Certificate, bool) {
// Set saves the certificate to the storage // Set saves the certificate to the storage
func (c *DomainTrieCertsStorage) Set(key string, cert *tls.Certificate) { func (c *DomainTrieCertsStorage) Set(key string, cert *tls.Certificate) {
c.lock.Lock()
_ = c.certsCache.Insert(key, cert) _ = c.certsCache.Insert(key, cert)
c.lock.Unlock()
} }
func NewDomainTrieCertsStorage() *DomainTrieCertsStorage { func NewDomainTrieCertsStorage() *DomainTrieCertsStorage {

View File

@ -29,13 +29,13 @@ func bindMarkToControl(mark int, chain controlFn) controlFn {
return return
} }
return c.Control(func(fd uintptr) { var innerErr error
switch network { err = c.Control(func(fd uintptr) {
case "tcp4", "udp4": innerErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
case "tcp6", "udp6":
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
}
}) })
if err == nil && innerErr != nil {
err = innerErr
}
return
} }
} }

View File

@ -2,11 +2,7 @@ package process
import ( import (
"errors" "errors"
"net"
"net/netip" "net/netip"
"github.com/Dreamacro/clash/common/nnip"
C "github.com/Dreamacro/clash/constant"
) )
var ( var (
@ -23,49 +19,3 @@ const (
func FindProcessName(network string, srcIP netip.Addr, srcPort int) (string, error) { func FindProcessName(network string, srcIP netip.Addr, srcPort int) (string, error) {
return findProcessName(network, srcIP, srcPort) return findProcessName(network, srcIP, srcPort)
} }
func ShouldFindProcess(metadata *C.Metadata) bool {
if metadata.Process != "" {
return false
}
for _, ip := range localIPs {
if ip == metadata.SrcIP {
return true
}
}
return false
}
func AppendLocalIPs(ip ...netip.Addr) {
localIPs = append(ip, localIPs...)
}
func getLocalIPs() []netip.Addr {
ips := []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified()}
netInterfaces, err := net.Interfaces()
if err != nil {
ips = append(ips, netip.AddrFrom4([4]byte{127, 0, 0, 1}), nnip.IpToAddr(net.IPv6loopback))
return ips
}
for i := 0; i < len(netInterfaces); i++ {
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
adds, _ := netInterfaces[i].Addrs()
for _, address := range adds {
if ipNet, ok := address.(*net.IPNet); ok {
ips = append(ips, nnip.IpToAddr(ipNet.IP))
}
}
}
}
return ips
}
var localIPs []netip.Addr
func init() {
localIPs = getLocalIPs()
}

View File

@ -6,8 +6,6 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/nnip"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -63,10 +61,10 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
switch { switch {
case flag&0x1 > 0 && isIPv4: case flag&0x1 > 0 && isIPv4:
// ipv4 // ipv4
srcIP = nnip.IpToAddr(buf[inp+76 : inp+80]) srcIP, _ = netip.AddrFromSlice(buf[inp+76 : inp+80])
case flag&0x2 > 0 && !isIPv4: case flag&0x2 > 0 && !isIPv4:
// ipv6 // ipv6
srcIP = nnip.IpToAddr(buf[inp+64 : inp+80]) srcIP, _ = netip.AddrFromSlice(buf[inp+64 : inp+80])
default: default:
continue continue
} }

View File

@ -10,7 +10,6 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
@ -135,10 +134,10 @@ func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (u
switch { switch {
case flag&0x1 > 0 && isIPv4: case flag&0x1 > 0 && isIPv4:
// ipv4 // ipv4
srcIP = nnip.IpToAddr(buf[inp+s.ip : inp+s.ip+4]) srcIP, _ = netip.AddrFromSlice(buf[inp+s.ip : inp+s.ip+4])
case flag&0x2 > 0 && !isIPv4: case flag&0x2 > 0 && !isIPv4:
// ipv6 // ipv6
srcIP = nnip.IpToAddr(buf[inp+s.ip-12 : inp+s.ip+4]) srcIP, _ = netip.AddrFromSlice(buf[inp+s.ip-12 : inp+s.ip+4])
default: default:
continue continue
} }

View File

@ -7,7 +7,6 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@ -132,7 +131,7 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
continue continue
} }
srcIP := nnip.IpToAddr(row[s.ip : s.ip+s.ipSize]) srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) { if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue continue

View File

@ -37,17 +37,17 @@ var (
) )
type Resolver interface { type Resolver interface {
ResolveIP(host string) (ip netip.Addr, err error) ResolveIP(host string, random bool) (ip netip.Addr, err error)
ResolveIPv4(host string) (ip netip.Addr, err error) ResolveIPv4(host string, random bool) (ip netip.Addr, err error)
ResolveIPv6(host string) (ip netip.Addr, err error) ResolveIPv6(host string, random bool) (ip netip.Addr, err error)
} }
// ResolveIPv4 with a host, return ipv4 // ResolveIPv4 with a host, return ipv4
func ResolveIPv4(host string) (netip.Addr, error) { func ResolveIPv4(host string) (netip.Addr, error) {
return ResolveIPv4WithResolver(host, DefaultResolver) return resolveIPv4(host, true)
} }
func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) { func ResolveIPv4WithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil { if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data; ip.Is4() { if ip := node.Data; ip.Is4() {
return ip, nil return ip, nil
@ -56,6 +56,7 @@ func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
ip, err := netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
if err == nil { if err == nil {
ip = ip.Unmap()
if ip.Is4() { if ip.Is4() {
return ip, nil return ip, nil
} }
@ -63,7 +64,7 @@ func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
} }
if r != nil { if r != nil {
return r.ResolveIPv4(host) return r.ResolveIPv4(host, random)
} }
if DefaultResolver == nil { if DefaultResolver == nil {
@ -76,7 +77,11 @@ func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
return netip.Addr{}, ErrIPNotFound return netip.Addr{}, ErrIPNotFound
} }
ip := ipAddrs[rand.Intn(len(ipAddrs))].To4() index := 0
if random {
index = rand.Intn(len(ipAddrs))
}
ip := ipAddrs[index].To4()
if ip == nil { if ip == nil {
return netip.Addr{}, ErrIPVersion return netip.Addr{}, ErrIPVersion
} }
@ -89,10 +94,10 @@ func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
// ResolveIPv6 with a host, return ipv6 // ResolveIPv6 with a host, return ipv6
func ResolveIPv6(host string) (netip.Addr, error) { func ResolveIPv6(host string) (netip.Addr, error) {
return ResolveIPv6WithResolver(host, DefaultResolver) return ResolveIPv6WithResolver(host, DefaultResolver, true)
} }
func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) { func ResolveIPv6WithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
if DisableIPv6 { if DisableIPv6 {
return netip.Addr{}, ErrIPv6Disabled return netip.Addr{}, ErrIPv6Disabled
} }
@ -112,7 +117,7 @@ func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) {
} }
if r != nil { if r != nil {
return r.ResolveIPv6(host) return r.ResolveIPv6(host, random)
} }
if DefaultResolver == nil { if DefaultResolver == nil {
@ -125,25 +130,29 @@ func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) {
return netip.Addr{}, ErrIPNotFound return netip.Addr{}, ErrIPNotFound
} }
return netip.AddrFrom16(*(*[16]byte)(ipAddrs[rand.Intn(len(ipAddrs))])), nil index := 0
if random {
index = rand.Intn(len(ipAddrs))
}
return netip.AddrFrom16(*(*[16]byte)(ipAddrs[index])), nil
} }
return netip.Addr{}, ErrIPNotFound return netip.Addr{}, ErrIPNotFound
} }
// ResolveIPWithResolver same as ResolveIP, but with a resolver // ResolveIPWithResolver same as ResolveIP, but with a resolver
func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) { func ResolveIPWithResolver(host string, r Resolver, random bool) (netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil { if node := DefaultHosts.Search(host); node != nil {
return node.Data, nil return node.Data, nil
} }
if r != nil { if r != nil {
if DisableIPv6 { if DisableIPv6 {
return r.ResolveIPv4(host) return r.ResolveIPv4(host, random)
} }
return r.ResolveIP(host) return r.ResolveIP(host, random)
} else if DisableIPv6 { } else if DisableIPv6 {
return ResolveIPv4(host) return resolveIPv4(host, random)
} }
ip, err := netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
@ -165,13 +174,18 @@ func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) {
// ResolveIP with a host, return ip // ResolveIP with a host, return ip
func ResolveIP(host string) (netip.Addr, error) { func ResolveIP(host string) (netip.Addr, error) {
return ResolveIPWithResolver(host, DefaultResolver) return resolveIP(host, true)
}
// ResolveFirstIP with a host, return ip
func ResolveFirstIP(host string) (netip.Addr, error) {
return resolveIP(host, false)
} }
// ResolveIPv4ProxyServerHost proxies server host only // ResolveIPv4ProxyServerHost proxies server host only
func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) { func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveIPv4WithResolver(host, ProxyServerHostResolver) return ResolveIPv4WithResolver(host, ProxyServerHostResolver, true)
} }
return ResolveIPv4(host) return ResolveIPv4(host)
} }
@ -179,7 +193,7 @@ func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
// ResolveIPv6ProxyServerHost proxies server host only // ResolveIPv6ProxyServerHost proxies server host only
func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) { func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveIPv6WithResolver(host, ProxyServerHostResolver) return ResolveIPv6WithResolver(host, ProxyServerHostResolver, true)
} }
return ResolveIPv6(host) return ResolveIPv6(host)
} }
@ -187,7 +201,15 @@ func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
// ResolveProxyServerHost proxies server host only // ResolveProxyServerHost proxies server host only
func ResolveProxyServerHost(host string) (netip.Addr, error) { func ResolveProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveIPWithResolver(host, ProxyServerHostResolver) return ResolveIPWithResolver(host, ProxyServerHostResolver, true)
} }
return ResolveIP(host) return ResolveIP(host)
} }
func resolveIP(host string, random bool) (netip.Addr, error) {
return ResolveIPWithResolver(host, DefaultResolver, random)
}
func resolveIPv4(host string, random bool) (netip.Addr, error) {
return ResolveIPv4WithResolver(host, DefaultResolver, random)
}

View File

@ -99,12 +99,13 @@ type Profile struct {
// Tun config // Tun config
type Tun struct { type Tun struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
Device string `yaml:"device" json:"device"` Device string `yaml:"device" json:"device"`
Stack C.TUNStack `yaml:"stack" json:"stack"` Stack C.TUNStack `yaml:"stack" json:"stack"`
DNSHijack []C.DNSUrl `yaml:"dns-hijack" json:"dns-hijack"` DNSHijack []C.DNSUrl `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"` AutoRoute bool `yaml:"auto-route" json:"auto-route"`
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
TunAddressPrefix *netip.Prefix `yaml:"_" json:"_"`
} }
// IPTables config // IPTables config

View File

@ -36,7 +36,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
if c.r == nil { if c.r == nil {
return nil, fmt.Errorf("dns %s not a valid ip", c.host) return nil, fmt.Errorf("dns %s not a valid ip", c.host)
} else { } else {
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil { if ip, err = resolver.ResolveIPWithResolver(c.host, c.r, true); err != nil {
return nil, fmt.Errorf("use default dns resolve failed: %w", err) return nil, fmt.Errorf("use default dns resolve failed: %w", err)
} }
c.host = ip.String() c.host = ip.String()

View File

@ -94,7 +94,7 @@ func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
return nil, err return nil, err
} }
ip, err := resolver.ResolveIPWithResolver(host, r) ip, err := resolver.ResolveIPWithResolver(host, r, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -21,6 +21,8 @@ import (
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
var _ resolver.Resolver = (*Resolver)(nil)
type dnsClient interface { type dnsClient interface {
Exchange(m *D.Msg) (msg *D.Msg, err error) Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
@ -45,18 +47,18 @@ type Resolver struct {
} }
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA // ResolveIP request with TypeA and TypeAAAA, priority return TypeA
func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) { func (r *Resolver) ResolveIP(host string, random bool) (ip netip.Addr, err error) {
ch := make(chan netip.Addr, 1) ch := make(chan netip.Addr, 1)
go func() { go func() {
defer close(ch) defer close(ch)
ip, err := r.resolveIP(host, D.TypeAAAA) ip, err := r.resolveIP(host, D.TypeAAAA, random)
if err != nil { if err != nil {
return return
} }
ch <- ip ch <- ip
}() }()
ip, err = r.resolveIP(host, D.TypeA) ip, err = r.resolveIP(host, D.TypeA, random)
if err == nil { if err == nil {
return return
} }
@ -70,13 +72,13 @@ func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) {
} }
// ResolveIPv4 request with TypeA // ResolveIPv4 request with TypeA
func (r *Resolver) ResolveIPv4(host string) (ip netip.Addr, err error) { func (r *Resolver) ResolveIPv4(host string, random bool) (ip netip.Addr, err error) {
return r.resolveIP(host, D.TypeA) return r.resolveIP(host, D.TypeA, random)
} }
// ResolveIPv6 request with TypeAAAA // ResolveIPv6 request with TypeAAAA
func (r *Resolver) ResolveIPv6(host string) (ip netip.Addr, err error) { func (r *Resolver) ResolveIPv6(host string, random bool) (ip netip.Addr, err error) {
return r.resolveIP(host, D.TypeAAAA) return r.resolveIP(host, D.TypeAAAA, random)
} }
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool { func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
@ -255,9 +257,10 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
return return
} }
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip netip.Addr, err error) { func (r *Resolver) resolveIP(host string, dnsType uint16, random bool) (ip netip.Addr, err error) {
ip, err = netip.ParseAddr(host) ip, err = netip.ParseAddr(host)
if err == nil { if err == nil {
ip = ip.Unmap()
isIPv4 := ip.Is4() isIPv4 := ip.Is4()
if dnsType == D.TypeAAAA && !isIPv4 { if dnsType == D.TypeAAAA && !isIPv4 {
return ip, nil return ip, nil
@ -282,7 +285,12 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip netip.Addr, err er
return netip.Addr{}, resolver.ErrIPNotFound return netip.Addr{}, resolver.ErrIPNotFound
} }
ip = ips[rand.Intn(ipLength)] index := 0
if random {
index = rand.Intn(ipLength)
}
ip = ips[index]
return return
} }

10
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/gofrs/uuid v4.2.0+incompatible github.com/gofrs/uuid v4.2.0+incompatible
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
github.com/miekg/dns v1.1.49 github.com/miekg/dns v1.1.50
github.com/oschwald/geoip2-golang v1.7.0 github.com/oschwald/geoip2-golang v1.7.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.2 github.com/stretchr/testify v1.7.2
@ -17,18 +17,18 @@ require (
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
go.uber.org/atomic v1.9.0 go.uber.org/atomic v1.9.0
go.uber.org/automaxprocs v1.5.1 go.uber.org/automaxprocs v1.5.1
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 golang.org/x/net v0.0.0-20220630215102-69896b714898
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 golang.org/x/time v0.0.0-20220609170525-579cf78fd858
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
google.golang.org/protobuf v1.28.0 google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20220616232550-d004a30ec069 gvisor.dev/gvisor v0.0.0-20220630235058-ce952880a3d3
) )
require ( require (

20
go.sum
View File

@ -45,8 +45,8 @@ github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcK
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8= github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8=
github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ= github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y= github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
@ -78,8 +78,8 @@ go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0= golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -97,8 +97,8 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
@ -123,8 +123,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -158,5 +158,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20220616232550-d004a30ec069 h1:Ly12hwbYd06NuYM2/nssGPgQ9ZVw7xaNs2lc087VW1w= gvisor.dev/gvisor v0.0.0-20220630235058-ce952880a3d3 h1:rnajnEGeshXCEr3k/7pWAM6zXh1Ws4jkZgWuWXjCJSw=
gvisor.dev/gvisor v0.0.0-20220616232550-d004a30ec069/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= gvisor.dev/gvisor v0.0.0-20220630235058-ce952880a3d3/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=

View File

@ -82,7 +82,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateHosts(cfg.Hosts) updateHosts(cfg.Hosts)
updateMitm(cfg.Mitm) updateMitm(cfg.Mitm)
updateProfile(cfg) updateProfile(cfg)
updateDNS(cfg.DNS, cfg.General.Tun) updateDNS(cfg.DNS, &cfg.General.Tun)
updateGeneral(cfg.General, force) updateGeneral(cfg.General, force)
updateIPTables(cfg) updateIPTables(cfg)
updateExperimental(cfg) updateExperimental(cfg)
@ -121,7 +121,7 @@ func GetGeneral() *config.General {
func updateExperimental(_ *config.Config) {} func updateExperimental(_ *config.Config) {}
func updateDNS(c *config.DNS, t config.Tun) { func updateDNS(c *config.DNS, t *config.Tun) {
cfg := dns.Config{ cfg := dns.Config{
Main: c.NameServer, Main: c.NameServer,
Fallback: c.Fallback, Fallback: c.Fallback,
@ -179,7 +179,7 @@ func updateDNS(c *config.DNS, t config.Tun) {
} }
if cfg.Pool != nil { if cfg.Pool != nil {
P.SetTunAddressPrefix(cfg.Pool.IPNet()) t.TunAddressPrefix = cfg.Pool.IPNet()
} }
} }

View File

@ -31,11 +31,9 @@ import (
) )
var ( var (
allowLan = false allowLan = false
bindAddress = "*" bindAddress = "*"
lastTunConf *config.Tun lastTunConf *config.Tun
lastTunAddressPrefix *netip.Prefix
tunAddressPrefix *netip.Prefix
socksListener *socks.Listener socksListener *socks.Listener
socksUDPListener *socks.UDPListener socksUDPListener *socks.UDPListener
@ -109,10 +107,6 @@ func SetBindAddress(host string) {
bindAddress = host bindAddress = host
} }
func SetTunAddressPrefix(tunAddress *netip.Prefix) {
tunAddressPrefix = tunAddress
}
func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) { func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) {
httpMux.Lock() httpMux.Lock()
defer httpMux.Unlock() defer httpMux.Unlock()
@ -363,14 +357,10 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
} }
}() }()
if tunAddressPrefix == nil {
tunAddressPrefix = lastTunAddressPrefix
}
tunConf.DNSHijack = C.RemoveDuplicateDNSUrl(tunConf.DNSHijack) tunConf.DNSHijack = C.RemoveDuplicateDNSUrl(tunConf.DNSHijack)
if tunStackListener != nil { if tunStackListener != nil {
if !hasTunConfigChange(tunConf, tunAddressPrefix) { if !hasTunConfigChange(tunConf) {
return return
} }
@ -379,7 +369,6 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
} }
lastTunConf = tunConf lastTunConf = tunConf
lastTunAddressPrefix = tunAddressPrefix
if !tunConf.Enable { if !tunConf.Enable {
return return
@ -391,7 +380,7 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
udpIn: udpIn, udpIn: udpIn,
} }
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn, callback) tunStackListener, err = tun.New(tunConf, tcpIn, udpIn, callback)
if err != nil { if err != nil {
return return
} }
@ -535,7 +524,7 @@ func genAddr(host string, port int, allowLan bool) string {
return fmt.Sprintf("127.0.0.1:%d", port) return fmt.Sprintf("127.0.0.1:%d", port)
} }
func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) bool { func hasTunConfigChange(tunConf *config.Tun) bool {
if lastTunConf == nil { if lastTunConf == nil {
return true return true
} }
@ -558,11 +547,11 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
return true return true
} }
if (tunAddressPrefix != nil && lastTunAddressPrefix == nil) || (tunAddressPrefix == nil && lastTunAddressPrefix != nil) { if (lastTunConf.TunAddressPrefix != nil && tunConf.TunAddressPrefix == nil) || (lastTunConf.TunAddressPrefix == nil && tunConf.TunAddressPrefix != nil) {
return true return true
} }
if tunAddressPrefix != nil && lastTunAddressPrefix != nil && *tunAddressPrefix != *lastTunAddressPrefix { if lastTunConf.TunAddressPrefix != nil && tunConf.TunAddressPrefix != nil && *lastTunConf.TunAddressPrefix != *tunConf.TunAddressPrefix {
return true return true
} }

View File

@ -72,6 +72,11 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
if err != nil { if err != nil {
continue continue
} }
if rAddr.Addr().Is4() {
// try to unmap 4in6 address
lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port())
}
handlePacketConn(in, buf[:n], lAddr, rAddr) handlePacketConn(in, buf[:n], lAddr, rAddr)
} }
}() }()

View File

@ -41,6 +41,10 @@ func (f *FD) Name() string {
return strconv.Itoa(f.fd) return strconv.Itoa(f.fd)
} }
func (f *FD) MTU() uint32 {
return f.mtu
}
func (f *FD) Close() error { func (f *FD) Close() error {
err := unix.Close(f.fd) err := unix.Close(f.fd)
if f.file != nil { if f.file != nil {

View File

@ -4,8 +4,48 @@ import (
"errors" "errors"
"github.com/Dreamacro/clash/listener/tun/device" "github.com/Dreamacro/clash/listener/tun/device"
"gvisor.dev/gvisor/pkg/tcpip/stack"
) )
func Open(name string, mtu uint32) (device.Device, error) { type FD struct {
stack.LinkEndpoint
}
func Open(_ string, _ uint32) (device.Device, error) {
return nil, errors.New("not supported") return nil, errors.New("not supported")
} }
func (f *FD) Name() string {
return ""
}
func (f *FD) Type() string {
return Driver
}
func (f *FD) Read(_ []byte) (int, error) {
return 0, nil
}
func (f *FD) Write(_ []byte) (int, error) {
return 0, nil
}
func (f *FD) Close() error {
return nil
}
func (f *FD) UseEndpoint() error {
return nil
}
func (f *FD) UseIOBased() error {
return nil
}
func (f *FD) MTU() uint32 {
return 0
}
var _ device.Device = (*FD)(nil)

View File

@ -59,10 +59,13 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("get mtu: %w", err) return nil, fmt.Errorf("get mtu: %w", err)
} }
t.mtu = uint32(tunMTU)
if t.mtu == 0 {
t.mtu = uint32(tunMTU)
}
if t.offset > 0 { if t.offset > 0 {
t.cache = make([]byte, 65535) t.cache = make([]byte, int(t.mtu)+t.offset)
} }
return t, nil return t, nil
@ -108,6 +111,10 @@ func (t *TUN) Name() string {
return name return name
} }
func (t *TUN) MTU() uint32 {
return t.mtu
}
func (t *TUN) UseEndpoint() error { func (t *TUN) UseEndpoint() error {
ep, err := iobased.New(t, t.mtu, t.offset) ep, err := iobased.New(t, t.mtu, t.offset)
if err != nil { if err != nil {

View File

@ -5,8 +5,10 @@ import (
"net" "net"
"net/netip" "net/netip"
dev "github.com/Dreamacro/clash/listener/tun/device"
"github.com/Dreamacro/clash/listener/tun/device/fdbased"
"github.com/Dreamacro/clash/listener/tun/device/tun"
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip" "github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
"github.com/Dreamacro/clash/log"
) )
func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *UDP, error) { func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *UDP, error) {
@ -19,10 +21,27 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
return nil, nil, err return nil, nil, err
} }
var (
t dev.Device
mtu uint32
ok bool
)
if t, ok = device.(*tun.TUN); ok {
mtu = t.MTU()
} else if t, ok = device.(*fdbased.FD); ok {
mtu = t.MTU()
}
bufferSize := int(mtu)
if bufferSize == 0 {
bufferSize = 64 * 1024
}
tab := newTable() tab := newTable()
udp := &UDP{ udp := &UDP{
device: device, device: device,
buf: [0xffff]byte{}, buf: make([]byte, bufferSize),
} }
tcp := &TCP{ tcp := &TCP{
listener: listener, listener: listener,
@ -38,7 +57,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
_ = udp.Close() _ = udp.Close()
}() }()
buf := make([]byte, 0xffff) buf := make([]byte, bufferSize)
for { for {
n, err := device.Read(buf) n, err := device.Read(buf)
@ -133,11 +152,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
continue continue
} }
port, err = tab.newConn(tup) port = tab.newConn(tup)
if err != nil {
log.Warnln("[STACK] drop tcp packet by system stack: %v", err)
continue
}
} }
ip.SetSourceIP(portal) ip.SetSourceIP(portal)
@ -156,7 +171,7 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
continue continue
} }
go udp.handleUDPPacket(ip, u) udp.handleUDPPacket(ip, u)
case tcpip.ICMP: case tcpip.ICMP:
i := tcpip.ICMPPacket(ip.Payload()) i := tcpip.ICMPPacket(ip.Payload())

View File

@ -1,13 +1,9 @@
package nat package nat
import ( import (
"fmt"
"net/netip" "net/netip"
"sync"
"github.com/Dreamacro/clash/common/generics/list" "github.com/Dreamacro/clash/common/generics/list"
"golang.org/x/exp/maps"
) )
const ( const (
@ -31,8 +27,6 @@ type table struct {
tuples map[tuple]*list.Element[*binding] tuples map[tuple]*list.Element[*binding]
ports [portLength]*list.Element[*binding] ports [portLength]*list.Element[*binding]
available *list.List[*binding] available *list.List[*binding]
mux sync.Mutex
count uint16
} }
func (t *table) tupleOf(port uint16) tuple { func (t *table) tupleOf(port uint16) tuple {
@ -49,64 +43,27 @@ func (t *table) tupleOf(port uint16) tuple {
} }
func (t *table) portOf(tuple tuple) uint16 { func (t *table) portOf(tuple tuple) uint16 {
t.mux.Lock()
elm := t.tuples[tuple] elm := t.tuples[tuple]
if elm == nil { if elm == nil {
t.mux.Unlock()
return 0 return 0
} }
t.mux.Unlock()
t.available.MoveToFront(elm) t.available.MoveToFront(elm)
return portBegin + elm.Value.offset return portBegin + elm.Value.offset
} }
func (t *table) newConn(tuple tuple) (uint16, error) { func (t *table) newConn(tuple tuple) uint16 {
t.mux.Lock() elm := t.available.Back()
elm, err := t.availableConn() b := elm.Value
if err != nil {
t.mux.Unlock()
return 0, err
}
elm.Value.tuple = tuple delete(t.tuples, b.tuple)
t.tuples[tuple] = elm t.tuples[tuple] = elm
t.mux.Unlock() b.tuple = tuple
return portBegin + elm.Value.offset, nil t.available.MoveToFront(elm)
}
func (t *table) availableConn() (*list.Element[*binding], error) { return portBegin + b.offset
var elm *list.Element[*binding]
for i := 0; i < portLength; i++ {
elm = t.available.Back()
t.available.MoveToFront(elm)
offset := elm.Value.offset
tup := t.ports[offset].Value.tuple
if t.tuples[tup] != nil && tup.SourceAddr.IsValid() {
continue
}
if t.count == portLength { // resize
tuples := make(map[tuple]*list.Element[*binding], portLength)
maps.Copy(tuples, t.tuples)
t.tuples = tuples
t.count = 1
}
return elm, nil
}
return nil, fmt.Errorf("too many open files, limits [%d, %d]", portLength, len(t.tuples))
}
func (t *table) closeConn(tuple tuple) {
t.mux.Lock()
delete(t.tuples, tuple)
t.count++
t.mux.Unlock()
} }
func newTable() *table { func newTable() *table {
@ -114,7 +71,6 @@ func newTable() *table {
tuples: make(map[tuple]*list.Element[*binding], portLength), tuples: make(map[tuple]*list.Element[*binding], portLength),
ports: [portLength]*list.Element[*binding]{}, ports: [portLength]*list.Element[*binding]{},
available: list.New[*binding](), available: list.New[*binding](),
count: 1,
} }
for idx := range result.ports { for idx := range result.ports {

View File

@ -16,8 +16,6 @@ type conn struct {
net.Conn net.Conn
tuple tuple tuple tuple
close func()
} }
func (t *TCP) Accept() (net.Conn, error) { func (t *TCP) Accept() (net.Conn, error) {
@ -41,9 +39,6 @@ func (t *TCP) Accept() (net.Conn, error) {
return &conn{ return &conn{
Conn: c, Conn: c,
tuple: tup, tuple: tup,
close: func() {
t.table.closeConn(tup)
},
}, nil }, nil
} }
@ -60,7 +55,6 @@ func (t *TCP) SetDeadline(time time.Time) error {
} }
func (c *conn) Close() error { func (c *conn) Close() error {
c.close()
return c.Conn.Close() return c.Conn.Close()
} }

View File

@ -24,7 +24,7 @@ type UDP struct {
queueLock sync.Mutex queueLock sync.Mutex
queue []*call queue []*call
bufLock sync.Mutex bufLock sync.Mutex
buf [0xffff]byte buf []byte
} }
func (u *UDP) ReadFrom(buf []byte) (int, netip.AddrPort, netip.AddrPort, error) { func (u *UDP) ReadFrom(buf []byte) (int, netip.AddrPort, netip.AddrPort, error) {

View File

@ -9,7 +9,6 @@ import (
"github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/cmd" "github.com/Dreamacro/clash/common/cmd"
"github.com/Dreamacro/clash/component/process"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/tun/device" "github.com/Dreamacro/clash/listener/tun/device"
@ -24,7 +23,7 @@ import (
) )
// New TunAdapter // New TunAdapter
func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, tunChangeCallback C.TUNChangeCallback) (ipstack.Stack, error) { func New(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, tunChangeCallback C.TUNChangeCallback) (ipstack.Stack, error) {
var ( var (
tunAddress = netip.Prefix{} tunAddress = netip.Prefix{}
devName = tunConf.Device devName = tunConf.Device
@ -48,16 +47,14 @@ func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.Con
devName = generateDeviceName() devName = generateDeviceName()
} }
if tunAddressPrefix != nil { if tunConf.TunAddressPrefix != nil {
tunAddress = *tunAddressPrefix tunAddress = *tunConf.TunAddressPrefix
} }
if !tunAddress.IsValid() || !tunAddress.Addr().Is4() { if !tunAddress.IsValid() || !tunAddress.Addr().Is4() {
tunAddress = netip.MustParsePrefix("198.18.0.1/16") tunAddress = netip.MustParsePrefix("198.18.0.1/16")
} }
process.AppendLocalIPs(tunAddress.Masked().Addr().Next())
// open tun device // open tun device
tunDevice, err = parseDevice(devName, uint32(mtu)) tunDevice, err = parseDevice(devName, uint32(mtu))
if err != nil { if err != nil {

View File

@ -16,7 +16,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
// local resolve UDP dns // local resolve UDP dns
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveFirstIP(metadata.Host)
if err != nil { if err != nil {
return err return err
} }

View File

@ -190,19 +190,6 @@ func preHandleMetadata(metadata *C.Metadata) error {
} }
} }
// pre resolve process name
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
if err == nil && P.ShouldFindProcess(metadata) {
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
if err != nil {
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
} else {
log.Debugln("[Process] %s from process %s", metadata.String(), path)
metadata.Process = filepath.Base(path)
metadata.ProcessPath = path
}
}
return nil return nil
} }
@ -385,7 +372,10 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
configMux.RLock() configMux.RLock()
defer configMux.RUnlock() defer configMux.RUnlock()
var resolved bool var (
resolved bool
processFound bool
)
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil { if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
metadata.DstIP = node.Data metadata.DstIP = node.Data
@ -404,6 +394,22 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
resolved = true resolved = true
} }
if !processFound && rule.ShouldFindProcess() {
processFound = true
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
if err == nil {
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
if err != nil {
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
} else {
log.Debugln("[Process] %s from process %s", metadata.String(), path)
metadata.Process = filepath.Base(path)
metadata.ProcessPath = path
}
}
}
if rule.Match(metadata) { if rule.Match(metadata) {
adapter, ok := proxies[rule.Adapter()] adapter, ok := proxies[rule.Adapter()]
if !ok { if !ok {