mirror of
https://github.com/folbricht/routedns.git
synced 2026-01-06 01:30:00 -06:00
Implement Socks5 support (#317)
* Implement Socks5 support * Support SOCKS5 for DoT * Socks5 for DoH * Return interface, not nil pointer of specific type * Support resolving DNS server names locally instead of through the proxy * Resolve IP4 only * Fix go.mod after rebase * Support LocalAddr directly in the SOCKS5 dialer * Update docs for SOCKS5
This commit is contained in:
@@ -20,6 +20,7 @@ Features:
|
||||
- EDNS0 Client Subnet (ECS) manipulation ([RFC7871](https://tools.ietf.org/html/rfc7871))
|
||||
- Support for bootstrap addresses to avoid the initial service name lookup
|
||||
- Support for 0-RTT Quic queries if the upstream server supports it
|
||||
- SOCKS5 proxy support
|
||||
- Optional metrics export (expvar) to support monitoring and graphing
|
||||
- Written in Go - Platform independent
|
||||
|
||||
|
||||
@@ -50,7 +50,13 @@ type resolver struct {
|
||||
BootstrapAddr string `toml:"bootstrap-address"`
|
||||
LocalAddr string `toml:"local-address"`
|
||||
EDNS0UDPSize uint16 `toml:"edns0-udp-size"` // UDP resolver option
|
||||
QueryTimeout int `toml:"query-timeout"` // Query timout in seconds
|
||||
QueryTimeout int `toml:"query-timeout"` // Query timeout in seconds
|
||||
|
||||
// Proxy configuration
|
||||
Socks5Address string `toml:"socks5-address"`
|
||||
Socks5Username string `toml:"socks5-username"`
|
||||
Socks5Password string `toml:"socks5-password"`
|
||||
Socks5ResolveLocal bool `toml:"socks5-resolve-local"` // Resolve DNS server address locally (i.e. bootstrap-resolver), not on the SOCK5 proxy
|
||||
}
|
||||
|
||||
// DoH-specific resolver options
|
||||
|
||||
13
cmd/routedns/example-config/socks5-doh.toml
Normal file
13
cmd/routedns/example-config/socks5-doh.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
# DoH coonfiguration that connects to the upstream server via SOCKS5 proxy.
|
||||
|
||||
[resolvers.cloudflare-doh]
|
||||
address = "https://cloudflare-dns.com/dns-query"
|
||||
protocol = "doh"
|
||||
socks5-address = "127.0.0.1:1080"
|
||||
socks5-username = "test"
|
||||
socks5-password = "test"
|
||||
|
||||
[listeners.local-udp]
|
||||
address = "127.0.0.1:53"
|
||||
protocol = "udp"
|
||||
resolver = "cloudflare-doh"
|
||||
13
cmd/routedns/example-config/socks5-dot.toml
Normal file
13
cmd/routedns/example-config/socks5-dot.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
# DoT coonfiguration that connects to the upstream server via SOCKS5 proxy.
|
||||
|
||||
[resolvers.cloudflare-dot]
|
||||
address = "1.1.1.1:853"
|
||||
protocol = "dot"
|
||||
socks5-address = "127.0.0.1:1080"
|
||||
socks5-username = "test"
|
||||
socks5-password = "test"
|
||||
|
||||
[listeners.local-udp]
|
||||
address = "127.0.0.1:53"
|
||||
protocol = "udp"
|
||||
resolver = "cloudflare-dot"
|
||||
13
cmd/routedns/example-config/socks5-udp.toml
Normal file
13
cmd/routedns/example-config/socks5-udp.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Simple DNS queries routed through a SOCKS5 proxy.
|
||||
|
||||
[resolvers.cloudflare-udp]
|
||||
address = "1.1.1.1:53"
|
||||
protocol = "udp"
|
||||
socks5-address = "127.0.0.1:1080"
|
||||
socks5-username = "test"
|
||||
socks5-password = "test"
|
||||
|
||||
[listeners.local-udp]
|
||||
address = "127.0.0.1:53"
|
||||
protocol = "udp"
|
||||
resolver = "cloudflare-udp"
|
||||
@@ -42,6 +42,7 @@ func instantiateResolver(id string, r resolver, resolvers map[string]rdns.Resolv
|
||||
LocalAddr: net.ParseIP(r.LocalAddr),
|
||||
TLSConfig: tlsConfig,
|
||||
QueryTimeout: time.Duration(r.QueryTimeout) * time.Second,
|
||||
Dialer: socks5DialerFromConfig(r),
|
||||
}
|
||||
resolvers[id], err = rdns.NewDoTClient(id, r.Address, opt)
|
||||
if err != nil {
|
||||
@@ -79,6 +80,7 @@ func instantiateResolver(id string, r resolver, resolvers map[string]rdns.Resolv
|
||||
Transport: r.Transport,
|
||||
LocalAddr: net.ParseIP(r.LocalAddr),
|
||||
QueryTimeout: time.Duration(r.QueryTimeout) * time.Second,
|
||||
Dialer: socks5DialerFromConfig(r),
|
||||
}
|
||||
resolvers[id], err = rdns.NewDoHClient(id, r.Address, opt)
|
||||
if err != nil {
|
||||
@@ -91,6 +93,7 @@ func instantiateResolver(id string, r resolver, resolvers map[string]rdns.Resolv
|
||||
LocalAddr: net.ParseIP(r.LocalAddr),
|
||||
UDPSize: r.EDNS0UDPSize,
|
||||
QueryTimeout: time.Duration(r.QueryTimeout) * time.Second,
|
||||
Dialer: socks5DialerFromConfig(r),
|
||||
}
|
||||
resolvers[id], err = rdns.NewDNSClient(id, r.Address, r.Protocol, opt)
|
||||
if err != nil {
|
||||
@@ -101,3 +104,21 @@ func instantiateResolver(id string, r resolver, resolvers map[string]rdns.Resolv
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns a dialer if a socks5 proxy is configured, nil otherwise
|
||||
func socks5DialerFromConfig(cfg resolver) rdns.Dialer {
|
||||
if cfg.Socks5Address == "" {
|
||||
return nil
|
||||
}
|
||||
r := rdns.NewSocks5Dialer(
|
||||
cfg.Socks5Address,
|
||||
rdns.Socks5DialerOptions{
|
||||
Username: cfg.Socks5Username,
|
||||
Password: cfg.Socks5Password,
|
||||
TCPTimeout: 0,
|
||||
UDPTimeout: 5 * time.Second,
|
||||
ResolveLocal: cfg.Socks5ResolveLocal,
|
||||
LocalAddr: net.ParseIP(cfg.LocalAddr),
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
114
dnsclient.go
114
dnsclient.go
@@ -3,6 +3,7 @@ package rdns
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
@@ -18,6 +19,10 @@ type DNSClient struct {
|
||||
opt DNSClientOptions
|
||||
}
|
||||
|
||||
type Dialer interface {
|
||||
Dial(net string, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type DNSClientOptions struct {
|
||||
// Local IP to use for outbound connections. If nil, a local address is chosen.
|
||||
LocalAddr net.IP
|
||||
@@ -27,6 +32,9 @@ type DNSClientOptions struct {
|
||||
UDPSize uint16
|
||||
|
||||
QueryTimeout time.Duration
|
||||
|
||||
// Optional dialer, e.g. proxy
|
||||
Dialer Dialer
|
||||
}
|
||||
|
||||
var _ Resolver = &DNSClient{}
|
||||
@@ -37,21 +45,11 @@ func NewDNSClient(id, endpoint, network string, opt DNSClientOptions) (*DNSClien
|
||||
if err := validEndpoint(endpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use a custom dialer if a local address was provided
|
||||
var dialer *net.Dialer
|
||||
if opt.LocalAddr != nil {
|
||||
switch network {
|
||||
case "tcp":
|
||||
dialer = &net.Dialer{LocalAddr: &net.TCPAddr{IP: opt.LocalAddr}, Timeout: opt.QueryTimeout}
|
||||
case "udp":
|
||||
dialer = &net.Dialer{LocalAddr: &net.UDPAddr{IP: opt.LocalAddr}, Timeout: opt.QueryTimeout}
|
||||
}
|
||||
}
|
||||
client := &dns.Client{
|
||||
client := GenericDNSClient{
|
||||
Net: network,
|
||||
Dialer: dialer,
|
||||
Dialer: opt.Dialer,
|
||||
TLSConfig: &tls.Config{},
|
||||
UDPSize: 4096,
|
||||
LocalAddr: opt.LocalAddr,
|
||||
Timeout: opt.QueryTimeout,
|
||||
}
|
||||
return &DNSClient{
|
||||
@@ -83,3 +81,93 @@ func (d *DNSClient) Resolve(q *dns.Msg, ci ClientInfo) (*dns.Msg, error) {
|
||||
func (d *DNSClient) String() string {
|
||||
return d.id
|
||||
}
|
||||
|
||||
// GenericDNSClient is a workaround for dns.Client not supporting custom dialers
|
||||
// (only *net.Dialer) which prevents the use of proxies. It implements the same
|
||||
// Dial functionality, while supporting custom dialers.
|
||||
type GenericDNSClient struct {
|
||||
Dialer Dialer
|
||||
Net string
|
||||
TLSConfig *tls.Config
|
||||
LocalAddr net.IP
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (d GenericDNSClient) Dial(address string) (*dns.Conn, error) {
|
||||
network := d.Net
|
||||
|
||||
// If we want TLS on it, perform the handshake
|
||||
useTLS := strings.HasPrefix(network, "tcp") && strings.HasSuffix(network, "-tls")
|
||||
network = strings.TrimSuffix(network, "-tls")
|
||||
|
||||
dialer := d.Dialer
|
||||
if dialer == nil {
|
||||
// Use a custom dialer if a local address was provided
|
||||
if d.LocalAddr != nil {
|
||||
switch network {
|
||||
case "tcp":
|
||||
dialer = &net.Dialer{LocalAddr: &net.TCPAddr{IP: d.LocalAddr}, Timeout: d.Timeout}
|
||||
case "udp":
|
||||
dialer = &net.Dialer{LocalAddr: &net.UDPAddr{IP: d.LocalAddr}, Timeout: d.Timeout}
|
||||
}
|
||||
} else {
|
||||
dialer = &net.Dialer{}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
conn = &dns.Conn{
|
||||
UDPSize: 4096,
|
||||
}
|
||||
err error
|
||||
)
|
||||
// Open a raw connection
|
||||
conn.Conn, err = dialer.Dial(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Trick dns.Conn.ReadMsg() into thinking this is a packet connection (udp) so it
|
||||
// correctly handles any length-prefixes
|
||||
if network == "udp" {
|
||||
conn.Conn = packetConnWrapper{conn.Conn}
|
||||
}
|
||||
|
||||
if useTLS {
|
||||
hostname, _, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := d.TLSConfig
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
// Make sure a servername is set
|
||||
if tlsConfig.ServerName == "" {
|
||||
c := tlsConfig.Clone()
|
||||
c.ServerName = hostname
|
||||
tlsConfig = c
|
||||
}
|
||||
conn.Conn = tls.Client(conn.Conn, tlsConfig)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// packetConnWrapper is another workaround for dns.Conn which checks if the Conn
|
||||
// it has implements net.PacketConn and based on that distinguishes between a UDP
|
||||
// connection (don't need length prefix) and TCP (need length prefix). This doesn't
|
||||
// actually implement these, but dns.Conn.ReadMsg() doesn't use them either.
|
||||
type packetConnWrapper struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
var _ net.PacketConn = packetConnWrapper{}
|
||||
|
||||
func (c packetConnWrapper) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c packetConnWrapper) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
- [DNS-over-DTLS](#DNS-over-DTLS-Resolver)
|
||||
- [DNS-over-QUIC](#DNS-over-QUIC-Resolver)
|
||||
- [Bootstrap Resolver](#Bootstrap-Resolver)
|
||||
- [SOCKS5 Proxy Support](#SOCKS5-Proxy-Support)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -1578,3 +1579,29 @@ protocol = "dot"
|
||||
```
|
||||
|
||||
Example config files: [bootstrap-resolver.toml](../cmd/routedns/example-config/bootstrap-resolver.toml), [use-case-6.toml](../cmd/routedns/example-config/use-case-6.toml)
|
||||
|
||||
### SOCKS5 Proxy Support
|
||||
|
||||
Several resolver types support connecting to upstream servers through a SOCKS5 proxy. This includes:
|
||||
|
||||
- [Plain DNS](#Plain-DNS-Resolver)
|
||||
- [DNS-over-TLS](#DNS-over-TLS-Resolver)
|
||||
- [DNS-over-HTTPS](#DNS-over-HTTPS-Resolver)
|
||||
|
||||
If SOCKS5 is available, the following options can be used to configure it:
|
||||
|
||||
- `socks5-address` - SOCKS5 server address, including port.
|
||||
- `socks5-username` - SOCKS5 server username.
|
||||
- `socks5-password` - SOCKS5 server password.
|
||||
- `socks5-resolve-local` - Experimental: Resolve the upstream DNS server name locally before connecting through the proxy.
|
||||
|
||||
Examples:
|
||||
|
||||
```toml
|
||||
[resolvers.cloudflare-doh]
|
||||
address = "https://cloudflare-dns.com/dns-query"
|
||||
protocol = "doh"
|
||||
socks5-address = "1.2.3.4:1080"
|
||||
socks5-username = "test"
|
||||
socks5-password = "test"
|
||||
```
|
||||
|
||||
@@ -41,6 +41,9 @@ type DoHClientOptions struct {
|
||||
TLSConfig *tls.Config
|
||||
|
||||
QueryTimeout time.Duration
|
||||
|
||||
// Optional dialer, e.g. proxy
|
||||
Dialer Dialer
|
||||
}
|
||||
|
||||
// DoHClient is a DNS-over-HTTP resolver with support fot HTTP/2.
|
||||
@@ -235,7 +238,7 @@ func dohTcpTransport(opt DoHClientOptions) (http.RoundTripper, error) {
|
||||
}
|
||||
|
||||
// Use a custom dialer if a bootstrap address or local address was provided
|
||||
if opt.BootstrapAddr != "" || opt.LocalAddr != nil {
|
||||
if opt.BootstrapAddr != "" || opt.LocalAddr != nil || opt.Dialer != nil {
|
||||
d := net.Dialer{LocalAddr: &net.TCPAddr{IP: opt.LocalAddr}}
|
||||
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if opt.BootstrapAddr != "" {
|
||||
@@ -245,6 +248,9 @@ func dohTcpTransport(opt DoHClientOptions) (http.RoundTripper, error) {
|
||||
}
|
||||
addr = net.JoinHostPort(opt.BootstrapAddr, port)
|
||||
}
|
||||
if opt.Dialer != nil {
|
||||
return opt.Dialer.Dial(network, addr)
|
||||
}
|
||||
return d.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
13
dotclient.go
13
dotclient.go
@@ -30,6 +30,9 @@ type DoTClientOptions struct {
|
||||
TLSConfig *tls.Config
|
||||
|
||||
QueryTimeout time.Duration
|
||||
|
||||
// Optional dialer, e.g. proxy
|
||||
Dialer Dialer
|
||||
}
|
||||
|
||||
var _ Resolver = &DoTClient{}
|
||||
@@ -40,15 +43,11 @@ func NewDoTClient(id, endpoint string, opt DoTClientOptions) (*DoTClient, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Use a custom dialer if a local address was provided
|
||||
var dialer *net.Dialer
|
||||
if opt.LocalAddr != nil {
|
||||
dialer = &net.Dialer{LocalAddr: &net.TCPAddr{IP: opt.LocalAddr}}
|
||||
}
|
||||
client := &dns.Client{
|
||||
client := GenericDNSClient{
|
||||
Net: "tcp-tls",
|
||||
TLSConfig: opt.TLSConfig,
|
||||
Dialer: dialer,
|
||||
Dialer: opt.Dialer,
|
||||
LocalAddr: opt.LocalAddr,
|
||||
}
|
||||
// If a bootstrap address was provided, we need to use the IP for the connection but the
|
||||
// hostname in the TLS handshake. The DNS library doesn't support custom dialers, so
|
||||
|
||||
5
go.mod
5
go.mod
@@ -7,7 +7,7 @@ require (
|
||||
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91
|
||||
github.com/heimdalr/dag v1.2.1
|
||||
github.com/jtacoma/uritemplates v1.0.0
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/miekg/dns v1.1.51
|
||||
github.com/oschwald/maxminddb-golang v1.10.0
|
||||
github.com/pion/dtls/v2 v2.2.4
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -16,6 +16,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301
|
||||
golang.org/x/net v0.17.0
|
||||
)
|
||||
|
||||
@@ -31,6 +32,7 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/transport/v2 v2.0.0 // indirect
|
||||
github.com/pion/udp v0.1.4 // indirect
|
||||
@@ -38,6 +40,7 @@ require (
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
|
||||
20
go.sum
20
go.sum
@@ -40,13 +40,15 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
|
||||
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pion/dtls/v2 v2.2.4 h1:YSfYwDQgrxMYXLBc/m7PFY5BVtWlNm/DN4qoU2CbcWg=
|
||||
github.com/pion/dtls/v2 v2.2.4/go.mod h1:WGKfxqhrddne4Kg3p11FUMJrynkOY4lb25zHNO49wuw=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
@@ -86,6 +88,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0=
|
||||
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
|
||||
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 h1:d/Wr/Vl/wiJHc3AHYbYs5I3PucJvRuw3SvbmlIRf+oM=
|
||||
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -98,30 +104,30 @@ golang.org/x/exp v0.0.0-20221227203929-1b447090c38c h1:Govq2W3bnHJimHT2ium65kXcI
|
||||
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -133,10 +139,10 @@ golang.org/x/sys v0.13.0/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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
@@ -145,8 +151,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
91
socks5.go
Normal file
91
socks5.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package rdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/txthinking/socks5"
|
||||
)
|
||||
|
||||
type Socks5Dialer struct {
|
||||
*socks5.Client
|
||||
opt Socks5DialerOptions
|
||||
|
||||
once sync.Once
|
||||
addr string
|
||||
}
|
||||
|
||||
type Socks5DialerOptions struct {
|
||||
Username string
|
||||
Password string
|
||||
UDPTimeout time.Duration
|
||||
TCPTimeout time.Duration
|
||||
LocalAddr net.IP
|
||||
|
||||
// When the resolver is configured with a name, not an IP, e.g. one.one.one.one:53
|
||||
// this setting will resolve that name locally rather than on the SOCKS proxy. The
|
||||
// name will be resolved either on the local system, or via the bootstrap-resolver
|
||||
// if one is setup.
|
||||
ResolveLocal bool
|
||||
}
|
||||
|
||||
var _ Dialer = (*Socks5Dialer)(nil)
|
||||
|
||||
func NewSocks5Dialer(addr string, opt Socks5DialerOptions) *Socks5Dialer {
|
||||
client, _ := socks5.NewClient(
|
||||
addr,
|
||||
opt.Username,
|
||||
opt.Password,
|
||||
int(opt.TCPTimeout.Seconds()),
|
||||
int(opt.UDPTimeout.Seconds()),
|
||||
)
|
||||
return &Socks5Dialer{Client: client, opt: opt}
|
||||
}
|
||||
|
||||
func (d *Socks5Dialer) Dial(network string, address string) (net.Conn, error) {
|
||||
d.once.Do(func() {
|
||||
d.addr = address
|
||||
|
||||
// If the address uses a hostname and ResolveLocal is enabled, lookup
|
||||
// the IP for it locally and use that when talking to the proxy going
|
||||
// forward. This avoids the DNS server's address leaking out from the
|
||||
// proxy.
|
||||
if d.opt.ResolveLocal {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
Log.WithError(err).Error("failed to parse socks5 address")
|
||||
return
|
||||
}
|
||||
Log.WithField("addr", host).Debug("resolving dns server locally")
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
// Already an IP
|
||||
return
|
||||
}
|
||||
timeout := d.opt.UDPTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
ips, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
|
||||
if err != nil {
|
||||
Log.WithError(err).Errorf("failed to lookup %q locally", host)
|
||||
return
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
Log.WithError(err).Error("failed to resolve dns server locally, forwarding to socks5 proxy")
|
||||
return
|
||||
}
|
||||
d.addr = net.JoinHostPort(ips[0].String(), port)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
if d.opt.LocalAddr != nil {
|
||||
return d.Client.DialWithLocalAddr(network, d.opt.LocalAddr.String(), d.addr, nil)
|
||||
}
|
||||
return d.Client.Dial(network, d.addr)
|
||||
}
|
||||
Reference in New Issue
Block a user