feat: wireguard add remote-dns-resolve and dns settings

This commit is contained in:
wwqgtxx 2023-04-11 10:29:55 +08:00
parent ecdde647b1
commit ab3fce29ab
8 changed files with 121 additions and 56 deletions

View file

@ -18,6 +18,7 @@ import (
"github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
wireguard "github.com/metacubex/sing-wireguard" wireguard "github.com/metacubex/sing-wireguard"
@ -38,6 +39,7 @@ type WireGuard struct {
dialer *wgSingDialer dialer *wgSingDialer
startOnce sync.Once startOnce sync.Once
startErr error startErr error
resolver *dns.Resolver
} }
type WireGuardOption struct { type WireGuardOption struct {
@ -51,6 +53,9 @@ type WireGuardOption struct {
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"` PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
Peers []WireGuardPeerOption `proxy:"peers,omitempty"` Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
Dns []string `proxy:"dns,omitempty"`
} }
type WireGuardPeerOption struct { type WireGuardPeerOption struct {
@ -298,6 +303,29 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
return nil, E.Cause(err, "setup wireguard") return nil, E.Cause(err, "setup wireguard")
} }
//err = outbound.tunDevice.Start() //err = outbound.tunDevice.Start()
var has6 bool
for _, address := range localPrefixes {
if !address.Addr().Unmap().Is4() {
has6 = true
break
}
}
if option.RemoteDnsResolve && len(option.Dns) > 0 {
nss, err := dns.ParseNameServer(option.Dns)
if err != nil {
return nil, err
}
for i := range nss {
nss[i].ProxyAdapter = outbound
}
outbound.resolver = dns.NewResolver(dns.Config{
Main: nss,
IPv6: has6,
})
}
return outbound, nil return outbound, nil
} }
@ -318,8 +346,12 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
if w.startErr != nil { if w.startErr != nil {
return nil, w.startErr return nil, w.startErr
} }
if !metadata.Resolved() { if !metadata.Resolved() || w.resolver != nil {
options = append(options, dialer.WithResolver(resolver.DefaultResolver)) r := resolver.DefaultResolver
if w.resolver != nil {
r = w.resolver
}
options = append(options, dialer.WithResolver(r))
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice})) options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress()) conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
} else { } else {
@ -348,8 +380,12 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !metadata.Resolved() { if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
ip, err := resolver.ResolveIP(ctx, metadata.Host) r := resolver.DefaultResolver
if w.resolver != nil {
r = w.resolver
}
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil { if err != nil {
return nil, errors.New("can't resolve ip") return nil, errors.New("can't resolve ip")
} }

View file

@ -896,7 +896,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
} }
proxyAdapter := u.Fragment proxyName := u.Fragment
var addr, dnsNetType string var addr, dnsNetType string
params := map[string]string{} params := map[string]string{}
@ -913,7 +913,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
case "https": case "https":
addr, err = hostWithDefaultPort(u.Host, "443") addr, err = hostWithDefaultPort(u.Host, "443")
if err == nil { if err == nil {
proxyAdapter = "" proxyName = ""
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path} clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path}
addr = clearURL.String() addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS dnsNetType = "https" // DNS over HTTPS
@ -923,7 +923,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
if len(arr) == 0 { if len(arr) == 0 {
continue continue
} else if len(arr) == 1 { } else if len(arr) == 1 {
proxyAdapter = arr[0] proxyName = arr[0]
} else if len(arr) == 2 { } else if len(arr) == 2 {
params[arr[0]] = arr[1] params[arr[0]] = arr[1]
} else { } else {
@ -949,18 +949,24 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
nameservers = append( nameservers = append(
nameservers, nameservers,
dns.NameServer{ dns.NameServer{
Net: dnsNetType, Net: dnsNetType,
Addr: addr, Addr: addr,
ProxyAdapter: proxyAdapter, ProxyName: proxyName,
Interface: dialer.DefaultInterface, Interface: dialer.DefaultInterface,
Params: params, Params: params,
PreferH3: preferH3, PreferH3: preferH3,
}, },
) )
} }
return nameservers, nil return nameservers, nil
} }
func init() {
dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard
return parseNameServer(servers, false)
}
}
func parsePureDNSServer(server string) string { func parsePureDNSServer(server string) string {
addPre := func(server string) string { addPre := func(server string) string {
return "udp://" + server return "udp://" + server

View file

@ -8,14 +8,14 @@ import (
"net/netip" "net/netip"
"strings" "strings"
tlsC "github.com/Dreamacro/clash/component/tls"
"go.uber.org/atomic"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"github.com/zhangyunhao116/fastrand" "github.com/zhangyunhao116/fastrand"
"go.uber.org/atomic"
) )
type client struct { type client struct {
@ -24,7 +24,8 @@ type client struct {
port string port string
host string host string
iface *atomic.String iface *atomic.String
proxyAdapter string proxyAdapter C.ProxyAdapter
proxyName string
addr string addr string
} }
@ -81,7 +82,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
options = append(options, dialer.WithInterface(c.iface.Load())) options = append(options, dialer.WithInterface(c.iface.Load()))
} }
conn, err := getDialHandler(c.r, c.proxyAdapter, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port)) conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -21,6 +21,7 @@ import (
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/http3" "github.com/metacubex/quic-go/http3"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"golang.org/x/exp/slices"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@ -63,7 +64,8 @@ type dnsOverHTTPS struct {
url *url.URL url *url.URL
r *Resolver r *Resolver
httpVersions []C.HTTPVersion httpVersions []C.HTTPVersion
proxyAdapter string proxyAdapter C.ProxyAdapter
proxyName string
addr string addr string
} }
@ -71,7 +73,7 @@ type dnsOverHTTPS struct {
var _ dnsClient = (*dnsOverHTTPS)(nil) var _ dnsClient = (*dnsOverHTTPS)(nil)
// newDoH returns the DNS-over-HTTPS Upstream. // newDoH returns the DNS-over-HTTPS Upstream.
func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[string]string, proxyAdapter string) dnsClient { func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) dnsClient {
u, _ := url.Parse(urlString) u, _ := url.Parse(urlString)
httpVersions := DefaultHTTPVersions httpVersions := DefaultHTTPVersions
if preferH3 { if preferH3 {
@ -87,6 +89,7 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
addr: u.String(), addr: u.String(),
r: r, r: r,
proxyAdapter: proxyAdapter, proxyAdapter: proxyAdapter,
proxyName: proxyName,
quicConfig: &quic.Config{ quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod, KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(), TokenStore: newQUICTokenStore(),
@ -390,14 +393,17 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
nextProtos = append(nextProtos, string(v)) nextProtos = append(nextProtos, string(v))
} }
tlsConfig.NextProtos = nextProtos tlsConfig.NextProtos = nextProtos
dialContext := getDialHandler(doh.r, doh.proxyAdapter) dialContext := getDialHandler(doh.r, doh.proxyAdapter, doh.proxyName)
// First, we attempt to create an HTTP3 transport. If the probe QUIC
// connection is established successfully, we'll be using HTTP3 for this if slices.Contains(doh.httpVersions, C.HTTPVersion3) {
// upstream. // First, we attempt to create an HTTP3 transport. If the probe QUIC
transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext) // connection is established successfully, we'll be using HTTP3 for this
if err == nil { // upstream.
log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String()) transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext)
return transportH3, nil if err == nil {
log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
return transportH3, nil
}
} }
log.Debugln("[%s] using HTTP/2 for this upstream: %v", doh.url.String(), err) log.Debugln("[%s] using HTTP/2 for this upstream: %v", doh.url.String(), err)
@ -533,7 +539,7 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
IP: net.ParseIP(ip), IP: net.ParseIP(ip),
Port: portInt, Port: portInt,
} }
conn, err := listenPacket(ctx, doh.proxyAdapter, "udp", addr, doh.r) conn, err := listenPacket(ctx, doh.proxyAdapter, doh.proxyName, "udp", addr, doh.r)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -13,9 +13,10 @@ import (
"time" "time"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -60,7 +61,8 @@ type dnsOverQUIC struct {
bytesPoolGuard sync.Mutex bytesPoolGuard sync.Mutex
addr string addr string
proxyAdapter string proxyAdapter C.ProxyAdapter
proxyName string
r *Resolver r *Resolver
} }
@ -68,10 +70,11 @@ type dnsOverQUIC struct {
var _ dnsClient = (*dnsOverQUIC)(nil) var _ dnsClient = (*dnsOverQUIC)(nil)
// newDoQ returns the DNS-over-QUIC Upstream. // newDoQ returns the DNS-over-QUIC Upstream.
func newDoQ(resolver *Resolver, addr string, adapter string) (dnsClient, error) { func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) {
doq := &dnsOverQUIC{ doq := &dnsOverQUIC{
addr: addr, addr: addr,
proxyAdapter: adapter, proxyAdapter: proxyAdapter,
proxyName: proxyName,
r: resolver, r: resolver,
quicConfig: &quic.Config{ quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod, KeepAlivePeriod: QUICKeepAlivePeriod,
@ -310,7 +313,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
// we're using bootstrapped address instead of what's passed to the function // we're using bootstrapped address instead of what's passed to the function
// it does not create an actual connection, but it helps us determine // it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there're v4/v6 addresses). // what IP is actually reachable (when there're v4/v6 addresses).
rawConn, err := getDialHandler(doq.r, doq.proxyAdapter)(ctx, "udp", doq.addr) rawConn, err := getDialHandler(doq.r, doq.proxyAdapter, doq.proxyName)(ctx, "udp", doq.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open a QUIC connection: %w", err) return nil, fmt.Errorf("failed to open a QUIC connection: %w", err)
} }
@ -325,7 +328,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
p, err := strconv.Atoi(port) p, err := strconv.Atoi(port)
udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p} udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p}
udp, err := listenPacket(ctx, doq.proxyAdapter, "udp", addr, doq.r) udp, err := listenPacket(ctx, doq.proxyAdapter, doq.proxyName, "udp", addr, doq.r)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -100,7 +100,7 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ips []netip.Addr,
ips, err = r.lookupIP(ctx, host, D.TypeA) ips, err = r.lookupIP(ctx, host, D.TypeA)
var waitIPv6 *time.Timer var waitIPv6 *time.Timer
if r != nil { if r != nil && r.ipv6Timeout > 0 {
waitIPv6 = time.NewTimer(r.ipv6Timeout) waitIPv6 = time.NewTimer(r.ipv6Timeout)
} else { } else {
waitIPv6 = time.NewTimer(100 * time.Millisecond) waitIPv6 = time.NewTimer(100 * time.Millisecond)
@ -421,7 +421,8 @@ type NameServer struct {
Net string Net string
Addr string Addr string
Interface *atomic.String Interface *atomic.String
ProxyAdapter string ProxyAdapter C.ProxyAdapter
ProxyName string
Params map[string]string Params map[string]string
PreferH3 bool PreferH3 bool
} }
@ -544,3 +545,5 @@ func NewProxyServerHostResolver(old *Resolver) *Resolver {
} }
return r return r
} }
var ParseNameServer func(servers []string) ([]NameServer, error) // define in config/config.go

View file

@ -74,13 +74,13 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
for _, s := range servers { for _, s := range servers {
switch s.Net { switch s.Net {
case "https": case "https":
ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter)) ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName))
continue continue
case "dhcp": case "dhcp":
ret = append(ret, newDHCPClient(s.Addr)) ret = append(ret, newDHCPClient(s.Addr))
continue continue
case "quic": case "quic":
if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter); err == nil { if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil {
ret = append(ret, doq) ret = append(ret, doq)
} else { } else {
log.Fatalln("DoQ format error: %v", err) log.Fatalln("DoQ format error: %v", err)
@ -103,6 +103,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
iface: s.Interface, iface: s.Interface,
r: resolver, r: resolver,
proxyAdapter: s.ProxyAdapter, proxyAdapter: s.ProxyAdapter,
proxyName: s.ProxyName,
}) })
} }
return ret return ret
@ -144,9 +145,9 @@ func msgToDomain(msg *D.Msg) string {
type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error) type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error)
func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dialHandler { func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) dialHandler {
return func(ctx context.Context, network, addr string) (net.Conn, error) { return func(ctx context.Context, network, addr string) (net.Conn, error) {
if len(proxyAdapter) == 0 { if len(proxyName) == 0 && proxyAdapter == nil {
opts = append(opts, dialer.WithResolver(r)) opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...) return dialer.DialContext(ctx, network, addr, opts...)
} else { } else {
@ -154,10 +155,14 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
if err != nil { if err != nil {
return nil, err return nil, err
} }
adapter, ok := tunnel.Proxies()[proxyAdapter] if proxyAdapter == nil {
if !ok { var ok bool
opts = append(opts, dialer.WithInterface(proxyAdapter)) proxyAdapter, ok = tunnel.Proxies()[proxyName]
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
}
} }
if strings.Contains(network, "tcp") { if strings.Contains(network, "tcp") {
// tcp can resolve host by remote // tcp can resolve host by remote
metadata := &C.Metadata{ metadata := &C.Metadata{
@ -165,8 +170,8 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
Host: host, Host: host,
DstPort: port, DstPort: port,
} }
if ok { if proxyAdapter != nil {
return adapter.DialContext(ctx, metadata, opts...) return proxyAdapter.DialContext(ctx, metadata, opts...)
} }
opts = append(opts, dialer.WithResolver(r)) opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...) return dialer.DialContext(ctx, network, addr, opts...)
@ -182,15 +187,15 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
DstIP: dstIP, DstIP: dstIP,
DstPort: port, DstPort: port,
} }
if !ok { if proxyAdapter == nil {
return dialer.DialContext(ctx, network, addr, opts...) return dialer.DialContext(ctx, network, addr, opts...)
} }
if !adapter.SupportUDP() { if !proxyAdapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter) return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
} }
packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...) packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -201,14 +206,17 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
} }
} }
func listenPacket(ctx context.Context, proxyAdapter string, network string, addr string, r *Resolver, opts ...dialer.Option) (net.PacketConn, error) { func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName string, network string, addr string, r *Resolver, opts ...dialer.Option) (net.PacketConn, error) {
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
adapter, ok := tunnel.Proxies()[proxyAdapter] if proxyAdapter == nil {
if !ok && len(proxyAdapter) != 0 { var ok bool
opts = append(opts, dialer.WithInterface(proxyAdapter)) proxyAdapter, ok = tunnel.Proxies()[proxyName]
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
}
} }
// udp must resolve host first // udp must resolve host first
@ -222,15 +230,15 @@ func listenPacket(ctx context.Context, proxyAdapter string, network string, addr
DstIP: dstIP, DstIP: dstIP,
DstPort: port, DstPort: port,
} }
if !ok { if proxyAdapter == nil {
return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...) return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...)
} }
if !adapter.SupportUDP() { if !proxyAdapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter) return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
} }
return adapter.ListenPacketContext(ctx, metadata, opts...) return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
} }
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {

View file

@ -631,6 +631,8 @@ proxies: # socks5
# reserved: [209,98,59] # reserved: [209,98,59]
# 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接 # 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接
# dialer-proxy: "ss1" # dialer-proxy: "ss1"
# remote-dns-resolve: true # 强制dns远程解析默认值为false
# dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效
# 如果peers不为空该段落中的allowed_ips不可为空前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略但private-key会被保留且只能在顶层指定 # 如果peers不为空该段落中的allowed_ips不可为空前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略但private-key会被保留且只能在顶层指定
# peers: # peers:
# - server: 162.159.192.1 # - server: 162.159.192.1