feat: wireguard add remote-dns-resolve
and dns
settings
This commit is contained in:
parent
ecdde647b1
commit
ab3fce29ab
8 changed files with 121 additions and 56 deletions
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
@ -949,18 +949,24 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
|||
nameservers = append(
|
||||
nameservers,
|
||||
dns.NameServer{
|
||||
Net: dnsNetType,
|
||||
Addr: addr,
|
||||
ProxyAdapter: proxyAdapter,
|
||||
Interface: dialer.DefaultInterface,
|
||||
Params: params,
|
||||
PreferH3: preferH3,
|
||||
Net: dnsNetType,
|
||||
Addr: addr,
|
||||
ProxyName: proxyName,
|
||||
Interface: dialer.DefaultInterface,
|
||||
Params: params,
|
||||
PreferH3: preferH3,
|
||||
},
|
||||
)
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
28
dns/doh.go
28
dns/doh.go
|
@ -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,14 +393,17 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
|
|||
nextProtos = append(nextProtos, string(v))
|
||||
}
|
||||
tlsConfig.NextProtos = nextProtos
|
||||
dialContext := getDialHandler(doh.r, doh.proxyAdapter)
|
||||
// First, we attempt to create an HTTP3 transport. If the probe QUIC
|
||||
// connection is established successfully, we'll be using HTTP3 for this
|
||||
// upstream.
|
||||
transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext)
|
||||
if err == nil {
|
||||
log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
|
||||
return transportH3, nil
|
||||
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.
|
||||
transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext)
|
||||
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)
|
||||
|
@ -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
|
||||
}
|
||||
|
|
15
dns/doq.go
15
dns/doq.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
46
dns/util.go
46
dns/util.go
|
@ -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 !ok {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue