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/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
wireguard "github.com/metacubex/sing-wireguard"
@ -38,6 +39,7 @@ type WireGuard struct {
dialer *wgSingDialer
startOnce sync.Once
startErr error
resolver *dns.Resolver
}
type WireGuardOption struct {
@ -51,6 +53,9 @@ type WireGuardOption struct {
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
Dns []string `proxy:"dns,omitempty"`
}
type WireGuardPeerOption struct {
@ -298,6 +303,29 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
return nil, E.Cause(err, "setup wireguard")
}
//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
}
@ -318,8 +346,12 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
if w.startErr != nil {
return nil, w.startErr
}
if !metadata.Resolved() {
options = append(options, dialer.WithResolver(resolver.DefaultResolver))
if !metadata.Resolved() || w.resolver != nil {
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}))
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
} else {
@ -348,8 +380,12 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if err != nil {
return nil, err
}
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
r := resolver.DefaultResolver
if w.resolver != nil {
r = w.resolver
}
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
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())
}
proxyAdapter := u.Fragment
proxyName := u.Fragment
var addr, dnsNetType string
params := map[string]string{}
@ -913,7 +913,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
case "https":
addr, err = hostWithDefaultPort(u.Host, "443")
if err == nil {
proxyAdapter = ""
proxyName = ""
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path}
addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS
@ -923,7 +923,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
if len(arr) == 0 {
continue
} else if len(arr) == 1 {
proxyAdapter = arr[0]
proxyName = arr[0]
} else if len(arr) == 2 {
params[arr[0]] = arr[1]
} else {
@ -951,7 +951,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
dns.NameServer{
Net: dnsNetType,
Addr: addr,
ProxyAdapter: proxyAdapter,
ProxyName: proxyName,
Interface: dialer.DefaultInterface,
Params: params,
PreferH3: preferH3,
@ -961,6 +961,12 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
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 {
addPre := func(server string) string {
return "udp://" + server

View file

@ -8,14 +8,14 @@ import (
"net/netip"
"strings"
tlsC "github.com/Dreamacro/clash/component/tls"
"go.uber.org/atomic"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns"
"github.com/zhangyunhao116/fastrand"
"go.uber.org/atomic"
)
type client struct {
@ -24,7 +24,8 @@ type client struct {
port string
host string
iface *atomic.String
proxyAdapter string
proxyAdapter C.ProxyAdapter
proxyName 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()))
}
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 {
return nil, err
}

View file

@ -21,6 +21,7 @@ import (
"github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/http3"
D "github.com/miekg/dns"
"golang.org/x/exp/slices"
"golang.org/x/net/http2"
)
@ -63,7 +64,8 @@ type dnsOverHTTPS struct {
url *url.URL
r *Resolver
httpVersions []C.HTTPVersion
proxyAdapter string
proxyAdapter C.ProxyAdapter
proxyName string
addr string
}
@ -71,7 +73,7 @@ type dnsOverHTTPS struct {
var _ dnsClient = (*dnsOverHTTPS)(nil)
// 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)
httpVersions := DefaultHTTPVersions
if preferH3 {
@ -87,6 +89,7 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
addr: u.String(),
r: r,
proxyAdapter: proxyAdapter,
proxyName: proxyName,
quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(),
@ -390,7 +393,9 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
nextProtos = append(nextProtos, string(v))
}
tlsConfig.NextProtos = nextProtos
dialContext := getDialHandler(doh.r, doh.proxyAdapter)
dialContext := getDialHandler(doh.r, doh.proxyAdapter, doh.proxyName)
if slices.Contains(doh.httpVersions, C.HTTPVersion3) {
// First, we attempt to create an HTTP3 transport. If the probe QUIC
// connection is established successfully, we'll be using HTTP3 for this
// upstream.
@ -399,6 +404,7 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
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)
@ -533,7 +539,7 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
IP: net.ParseIP(ip),
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 {
return nil, err
}

View file

@ -13,9 +13,10 @@ import (
"time"
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/Dreamacro/clash/log"
D "github.com/miekg/dns"
)
@ -60,7 +61,8 @@ type dnsOverQUIC struct {
bytesPoolGuard sync.Mutex
addr string
proxyAdapter string
proxyAdapter C.ProxyAdapter
proxyName string
r *Resolver
}
@ -68,10 +70,11 @@ type dnsOverQUIC struct {
var _ dnsClient = (*dnsOverQUIC)(nil)
// 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{
addr: addr,
proxyAdapter: adapter,
proxyAdapter: proxyAdapter,
proxyName: proxyName,
r: resolver,
quicConfig: &quic.Config{
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
// it does not create an actual connection, but it helps us determine
// 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 {
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)
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 {
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)
var waitIPv6 *time.Timer
if r != nil {
if r != nil && r.ipv6Timeout > 0 {
waitIPv6 = time.NewTimer(r.ipv6Timeout)
} else {
waitIPv6 = time.NewTimer(100 * time.Millisecond)
@ -421,7 +421,8 @@ type NameServer struct {
Net string
Addr string
Interface *atomic.String
ProxyAdapter string
ProxyAdapter C.ProxyAdapter
ProxyName string
Params map[string]string
PreferH3 bool
}
@ -544,3 +545,5 @@ func NewProxyServerHostResolver(old *Resolver) *Resolver {
}
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 {
switch s.Net {
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
case "dhcp":
ret = append(ret, newDHCPClient(s.Addr))
continue
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)
} else {
log.Fatalln("DoQ format error: %v", err)
@ -103,6 +103,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
iface: s.Interface,
r: resolver,
proxyAdapter: s.ProxyAdapter,
proxyName: s.ProxyName,
})
}
return ret
@ -144,9 +145,9 @@ func msgToDomain(msg *D.Msg) string {
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) {
if len(proxyAdapter) == 0 {
if len(proxyName) == 0 && proxyAdapter == nil {
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...)
} else {
@ -154,10 +155,14 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
if err != nil {
return nil, err
}
adapter, ok := tunnel.Proxies()[proxyAdapter]
if proxyAdapter == nil {
var ok bool
proxyAdapter, ok = tunnel.Proxies()[proxyName]
if !ok {
opts = append(opts, dialer.WithInterface(proxyAdapter))
opts = append(opts, dialer.WithInterface(proxyName))
}
}
if strings.Contains(network, "tcp") {
// tcp can resolve host by remote
metadata := &C.Metadata{
@ -165,8 +170,8 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
Host: host,
DstPort: port,
}
if ok {
return adapter.DialContext(ctx, metadata, opts...)
if proxyAdapter != nil {
return proxyAdapter.DialContext(ctx, metadata, opts...)
}
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...)
@ -182,15 +187,15 @@ func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dia
DstIP: dstIP,
DstPort: port,
}
if !ok {
if proxyAdapter == nil {
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)
}
packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...)
packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil {
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)
if err != nil {
return nil, err
}
adapter, ok := tunnel.Proxies()[proxyAdapter]
if !ok && len(proxyAdapter) != 0 {
opts = append(opts, dialer.WithInterface(proxyAdapter))
if proxyAdapter == nil {
var ok bool
proxyAdapter, ok = tunnel.Proxies()[proxyName]
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
}
}
// udp must resolve host first
@ -222,15 +230,15 @@ func listenPacket(ctx context.Context, proxyAdapter string, network string, addr
DstIP: dstIP,
DstPort: port,
}
if !ok {
if proxyAdapter == nil {
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 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) {

View file

@ -631,6 +631,8 @@ proxies: # socks5
# reserved: [209,98,59]
# 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接
# 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:
# - server: 162.159.192.1