From 5dfd3574e4c959609451274294a73cdc8ae35335 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Mon, 15 Jul 2024 20:24:26 +0200 Subject: [PATCH] Implement remote DNS This commit implements remote DNS. It introduces two new dependencies: ttlcache and dns. Remote DNS intercepts UDP DNS queries for A records on port 53. It replies with an unused IP address from an address pool, 198.18.0.0/15 by default. When obtaining a new address from the pool, tun2socks needs to memorize which name the address belongs to, so that when a client connects to the address, it can instruct the proxy to connect to the FQDN. To implement this IP to name mapping, ttlcache is used. To prevent using multiple addresses for the same name, ttlcache is also used to implement a name to IP mapping. If an IP address is already cached for a name, that address is returned instread. When building a connection, the connection metadata is inspected and if the destination address is associated with a DNS name, the proxy is instructed to use this name instead of the IP address. --- component/remotedns/handle.go | 88 +++++++++++++++++++++++++++++++++ component/remotedns/iputil.go | 36 ++++++++++++++ component/remotedns/pool.go | 88 +++++++++++++++++++++++++++++++++ component/remotedns/settings.go | 55 +++++++++++++++++++++ core/udp.go | 39 +++++++++------ engine/engine.go | 41 +++++++++++++++ engine/key.go | 4 ++ go.mod | 6 ++- go.sum | 13 ++++- main.go | 5 ++ metadata/metadata.go | 9 +++- proxy/socks5.go | 2 +- tunnel/tcp.go | 3 ++ tunnel/udp.go | 8 ++- 14 files changed, 376 insertions(+), 21 deletions(-) create mode 100644 component/remotedns/handle.go create mode 100644 component/remotedns/iputil.go create mode 100644 component/remotedns/pool.go create mode 100644 component/remotedns/settings.go diff --git a/component/remotedns/handle.go b/component/remotedns/handle.go new file mode 100644 index 00000000..ef5c3f24 --- /dev/null +++ b/component/remotedns/handle.go @@ -0,0 +1,88 @@ +package remotedns + +import ( + "net" + + "github.com/miekg/dns" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" + "gvisor.dev/gvisor/pkg/tcpip/stack" + "gvisor.dev/gvisor/pkg/waiter" + + "github.com/xjasonlyu/tun2socks/v2/log" + M "github.com/xjasonlyu/tun2socks/v2/metadata" +) + +func RewriteMetadata(metadata *M.Metadata) bool { + if !IsEnabled() { + return false + } + dstName, found := getCachedName(metadata.DstIP) + if !found { + return false + } + metadata.DstIP = nil + metadata.DstName = dstName + return true +} + +func HandleDNSQuery(s *stack.Stack, id stack.TransportEndpointID, ptr *stack.PacketBuffer) bool { + if !IsEnabled() { + return false + } + + msg := dns.Msg{} + err := msg.Unpack(ptr.Data().AsRange().ToSlice()) + + isCorrectEndpoint := id.LocalPort == 53 && (listenAddress.Equal(id.LocalAddress.AsSlice()) || listenAddress.IsUnspecified()) + + // Ignore UDP packets that are not matching the listen address and are not recursive queries + if !isCorrectEndpoint || err != nil || len(msg.Question) != 1 || msg.Question[0].Qtype != dns.TypeA && + msg.Question[0].Qtype != dns.TypeAAAA || msg.Question[0].Qclass != dns.ClassINET || !msg.RecursionDesired || + msg.Response { + return false + } + + qname := msg.Question[0].Name + qtype := msg.Question[0].Qtype + + log.Debugf("[DNS] query %s %s", dns.TypeToString[qtype], qname) + + var ip net.IP + if qtype == dns.TypeA { + rr := dns.A{} + ip = findOrInsertNameAndReturnIP(4, qname) + if ip == nil { + log.Warnf("[DNS] IP space exhausted") + return true + } + rr.A = ip + rr.Hdr.Name = qname + rr.Hdr.Ttl = dnsTTL + rr.Hdr.Class = dns.ClassINET + rr.Hdr.Rrtype = qtype + msg.Answer = append(msg.Answer, &rr) + } + + msg.Response = true + msg.RecursionDesired = false + msg.RecursionAvailable = true + + var wq waiter.Queue + + ep, err2 := s.NewEndpoint(ptr.TransportProtocolNumber, ptr.NetworkProtocolNumber, &wq) + if err2 != nil { + return true + } + defer ep.Close() + + ep.Bind(tcpip.FullAddress{NIC: ptr.NICID, Addr: id.LocalAddress, Port: id.LocalPort}) + conn := gonet.NewUDPConn(&wq, ep) + defer conn.Close() + packed, err := msg.Pack() + if err != nil { + return true + } + _, _ = conn.WriteTo(packed, &net.UDPAddr{IP: id.RemoteAddress.AsSlice(), Port: int(id.RemotePort)}) + return true +} diff --git a/component/remotedns/iputil.go b/component/remotedns/iputil.go new file mode 100644 index 00000000..6a238d4c --- /dev/null +++ b/component/remotedns/iputil.go @@ -0,0 +1,36 @@ +package remotedns + +import "net" + +func copyIP(ip net.IP) net.IP { + dup := make(net.IP, len(ip)) + copy(dup, ip) + return dup +} + +func incrementIP(ip net.IP) net.IP { + result := copyIP(ip) + for i := len(result) - 1; i >= 0; i-- { + result[i]++ + if result[i] != 0 { + break + } + } + return result +} + +func getBroadcastAddress(ipnet *net.IPNet) net.IP { + result := copyIP(ipnet.IP) + for i := 0; i < len(ipnet.IP); i++ { + result[i] |= ^ipnet.Mask[i] + } + return result +} + +func getNetworkAddress(ipnet *net.IPNet) net.IP { + result := copyIP(ipnet.IP) + for i := 0; i < len(ipnet.IP); i++ { + result[i] &= ipnet.Mask[i] + } + return result +} diff --git a/component/remotedns/pool.go b/component/remotedns/pool.go new file mode 100644 index 00000000..298acbfc --- /dev/null +++ b/component/remotedns/pool.go @@ -0,0 +1,88 @@ +package remotedns + +import ( + "net" + "sync" + "time" + + "github.com/jellydator/ttlcache/v3" +) + +var ( + ipToName = ttlcache.New[string, string]() + nameToIP = ttlcache.New[string, net.IP]() + mutex = sync.Mutex{} + + ip4NextAddress net.IP + ip4BroadcastAddress net.IP +) + +func findOrInsertNameAndReturnIP(ipVersion int, name string) net.IP { + if ipVersion != 4 { + panic("Method not implemented for IPv6") + } + mutex.Lock() + defer mutex.Unlock() + var result net.IP = nil + var ipnet *net.IPNet + var nextAddress *net.IP + var broadcastAddress net.IP + if ipVersion == 4 { + ipnet = ip4net + nextAddress = &ip4NextAddress + broadcastAddress = ip4BroadcastAddress + } + + entry := nameToIP.Get(name) + if entry != nil { + ip := entry.Value() + ipToName.Touch(ip.String()) + return ip + } + + // Beginning from the pointer to the next most likely free IP, loop through the IP address space + // until either a free IP is found or the space is exhausted + passedBroadcastAddress := false + for result == nil { + if nextAddress.Equal(broadcastAddress) { + *nextAddress = getNetworkAddress(ipnet) + *nextAddress = incrementIP(ipnet.IP) + + // We have seen the broadcast address twice during looping + // This means that our IP address space is exhausted + if passedBroadcastAddress { + return nil + } + passedBroadcastAddress = true + } + + // Skip the listen address if that is inside our pool range + if nextAddress.Equal(listenAddress) { + *nextAddress = incrementIP(*nextAddress) + continue + } + + // Do not touch entries that exist in the cache already. + hasKey := ipToName.Has((*nextAddress).String()) + if !hasKey { + _ = ipToName.Set((*nextAddress).String(), name, time.Duration(dnsTTL)*time.Second+cacheGraceTime) + _ = nameToIP.Set(name, *nextAddress, time.Duration(dnsTTL)*time.Second+cacheGraceTime) + result = *nextAddress + } + + *nextAddress = incrementIP(*nextAddress) + } + + return result +} + +func getCachedName(address net.IP) (string, bool) { + mutex.Lock() + defer mutex.Unlock() + entry := ipToName.Get(address.String()) + if entry == nil { + return "", false + } + nameToIP.Touch(entry.Value()) + return entry.Value(), true +} diff --git a/component/remotedns/settings.go b/component/remotedns/settings.go new file mode 100644 index 00000000..226f68f2 --- /dev/null +++ b/component/remotedns/settings.go @@ -0,0 +1,55 @@ +package remotedns + +import ( + "errors" + "net" + "time" +) + +// Timeouts are somewhat arbitrary. For example, netcat will resolve the DNS +// names upon startup and then stick to the resolved IP address. A timeout of 1 +// second may therefore be too low in cases where the first UDP packet is not +// sent immediately. +// cacheGraceTime defines how long an entry should still be retained in the cache +// after being resolved by DNS. +const ( + cacheGraceTime = 30 * time.Second +) + +var ( + enabled = false + dnsTTL uint32 = 0 + ip4net *net.IPNet + listenAddress net.IP +) + +func IsEnabled() bool { + return enabled +} + +func SetDNSTTL(timeout time.Duration) { + dnsTTL = uint32(timeout.Seconds()) +} + +func SetListenAddress(ip net.IP) { + listenAddress = ip +} + +func SetNetwork(ipnet *net.IPNet) error { + leadingOnes, _ := ipnet.Mask.Size() + if len(ipnet.IP) == 4 { + if leadingOnes > 30 { + return errors.New("IPv4 remote DNS subnet too small") + } + ip4net = ipnet + } else { + return errors.New("unsupported protocol") + } + return nil +} + +func Enable() { + ip4NextAddress = incrementIP(getNetworkAddress(ip4net)) + ip4BroadcastAddress = getBroadcastAddress(ip4net) + enabled = true +} diff --git a/core/udp.go b/core/udp.go index 4b89fcc9..b5001323 100644 --- a/core/udp.go +++ b/core/udp.go @@ -7,31 +7,38 @@ import ( "gvisor.dev/gvisor/pkg/tcpip/transport/udp" "gvisor.dev/gvisor/pkg/waiter" + "github.com/xjasonlyu/tun2socks/v2/component/remotedns" "github.com/xjasonlyu/tun2socks/v2/core/adapter" "github.com/xjasonlyu/tun2socks/v2/core/option" ) func withUDPHandler(handle func(adapter.UDPConn)) option.Option { return func(s *stack.Stack) error { - udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) { - var ( - wq waiter.Queue - id = r.ID() - ) - ep, err := r.CreateEndpoint(&wq) - if err != nil { - glog.Debugf("forward udp request: %s:%d->%s:%d: %s", - id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err) - return + s.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, ptr *stack.PacketBuffer) bool { + if remotedns.HandleDNSQuery(s, id, ptr) { + return true } - conn := &udpConn{ - UDPConn: gonet.NewUDPConn(&wq, ep), - id: id, - } - handle(conn) + udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) { + var ( + wq waiter.Queue + id = r.ID() + ) + ep, err := r.CreateEndpoint(&wq) + if err != nil { + glog.Debugf("forward udp request %s:%d->%s:%d: %s", + id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err) + return + } + + conn := &udpConn{ + UDPConn: gonet.NewUDPConn(&wq, ep), + id: id, + } + handle(conn) + }) + return udpForwarder.HandlePacket(id, ptr) }) - s.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket) return nil } } diff --git a/engine/engine.go b/engine/engine.go index 52c2a938..6fa7b1e7 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -2,6 +2,7 @@ package engine import ( "errors" + "fmt" "net" "os/exec" "sync" @@ -12,6 +13,7 @@ import ( "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/stack" + "github.com/xjasonlyu/tun2socks/v2/component/remotedns" "github.com/xjasonlyu/tun2socks/v2/core" "github.com/xjasonlyu/tun2socks/v2/core/device" "github.com/xjasonlyu/tun2socks/v2/core/option" @@ -19,6 +21,7 @@ import ( "github.com/xjasonlyu/tun2socks/v2/engine/mirror" "github.com/xjasonlyu/tun2socks/v2/log" "github.com/xjasonlyu/tun2socks/v2/proxy" + "github.com/xjasonlyu/tun2socks/v2/proxy/proto" "github.com/xjasonlyu/tun2socks/v2/restapi" "github.com/xjasonlyu/tun2socks/v2/tunnel" ) @@ -164,6 +167,38 @@ func restAPI(k *Key) error { return nil } +func remoteDNS(k *Key, proxy proxy.Proxy) (err error) { + if !k.RemoteDNS { + return + } + if proxy.Proto() != proto.Socks5 && proxy.Proto() != proto.HTTP && proxy.Proto() != proto.Shadowsocks && + proxy.Proto() != proto.Socks4 { + return errors.New("remote DNS not supported with this proxy protocol") + } + + _, ipnet, err := net.ParseCIDR(k.RemoteDNSNetIPv4) + if err != nil { + return err + } + + err = remotedns.SetNetwork(ipnet) + if err != nil { + return err + } + + ip := net.ParseIP(k.RemoteDNSListenAddress) + if ip == nil { + return fmt.Errorf("%s is not a valid IP", ip.String()) + } + remotedns.SetListenAddress(ip) + + remotedns.SetDNSTTL(k.RemoteDNSTTL) + + remotedns.Enable() + log.Infof("[DNS] Remote DNS enabled") + return +} + func netstack(k *Key) (err error) { if k.Proxy == "" { return errors.New("empty proxy") @@ -238,5 +273,11 @@ func netstack(k *Key) (err error) { _defaultDevice.Type(), _defaultDevice.Name(), _defaultProxy.Proto(), _defaultProxy.Addr(), ) + + err = remoteDNS(k, _defaultProxy) + if err != nil { + return err + } + return nil } diff --git a/engine/key.go b/engine/key.go index 5a86d53a..a29fe1a0 100644 --- a/engine/key.go +++ b/engine/key.go @@ -17,4 +17,8 @@ type Key struct { TUNPreUp string `yaml:"tun-pre-up"` TUNPostUp string `yaml:"tun-post-up"` UDPTimeout time.Duration `yaml:"udp-timeout"` + RemoteDNS bool `yaml:"remote-dns"` + RemoteDNSNetIPv4 string `yaml:"remote-dns-net-ipv4"` + RemoteDNSTTL time.Duration `yaml:"remote-dns-timeout"` + RemoteDNSListenAddress string `yaml:"remote-dns-listen-addr"` } diff --git a/go.mod b/go.mod index bc18e77c..dfa67efd 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,8 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/schema v1.4.1 github.com/gorilla/websocket v1.5.1 + github.com/jellydator/ttlcache/v3 v3.2.0 + github.com/miekg/dns v1.1.52 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 go.uber.org/atomic v1.11.0 @@ -28,8 +30,10 @@ require ( github.com/ajg/form v1.5.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/btree v1.1.2 // indirect - github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.24.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/tools v0.16.1 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect ) diff --git a/go.sum b/go.sum index 95e27fdb..8aff59a1 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -24,10 +23,14 @@ github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= +github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c= +github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= @@ -42,15 +45,23 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= diff --git a/main.go b/main.go index a7c08e4c..6893d37e 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "os" "os/signal" "syscall" + "time" "go.uber.org/automaxprocs/maxprocs" "gopkg.in/yaml.v3" @@ -40,6 +41,10 @@ func init() { flag.StringVar(&key.TUNPreUp, "tun-pre-up", "", "Execute a command before TUN device setup") flag.StringVar(&key.TUNPostUp, "tun-post-up", "", "Execute a command after TUN device setup") flag.BoolVar(&versionFlag, "version", false, "Show version and then quit") + flag.BoolVar(&key.RemoteDNS, "remote-dns", false, "Enable remote DNS (HTTP, Shadowsocks, SOCKS)") + flag.StringVar(&key.RemoteDNSNetIPv4, "remote-dns-net-ipv4", "198.18.0.0/15", "IPv4 network for remote DNS A records") + flag.StringVar(&key.RemoteDNSListenAddress, "remote-dns-listen-addr", "198.18.0.1", "IP to listen on for DNS requests") + flag.DurationVar(&key.RemoteDNSTTL, "remote-dns-ttl", 1*time.Second, "TTL for remote DNS") flag.Parse() } diff --git a/metadata/metadata.go b/metadata/metadata.go index 63755dbe..3908a6d5 100644 --- a/metadata/metadata.go +++ b/metadata/metadata.go @@ -11,13 +11,20 @@ type Metadata struct { SrcIP net.IP `json:"sourceIP"` MidIP net.IP `json:"dialerIP"` DstIP net.IP `json:"destinationIP"` + DstName string `json:"destinationName"` SrcPort uint16 `json:"sourcePort"` MidPort uint16 `json:"dialerPort"` DstPort uint16 `json:"destinationPort"` } func (m *Metadata) DestinationAddress() string { - return net.JoinHostPort(m.DstIP.String(), strconv.FormatUint(uint64(m.DstPort), 10)) + var remote string + if m.DstIP == nil { + remote = m.DstName + } else { + remote = m.DstIP.String() + } + return net.JoinHostPort(remote, strconv.FormatUint(uint64(m.DstPort), 10)) } func (m *Metadata) SourceAddress() string { diff --git a/proxy/socks5.go b/proxy/socks5.go index bdc9b04c..de91466f 100644 --- a/proxy/socks5.go +++ b/proxy/socks5.go @@ -186,5 +186,5 @@ func (pc *socksPacketConn) Close() error { } func serializeSocksAddr(m *M.Metadata) socks5.Addr { - return socks5.SerializeAddr("", m.DstIP, m.DstPort) + return socks5.SerializeAddr(m.DstName, m.DstIP, m.DstPort) } diff --git a/tunnel/tcp.go b/tunnel/tcp.go index 03cebab5..92cb2d6e 100644 --- a/tunnel/tcp.go +++ b/tunnel/tcp.go @@ -7,6 +7,7 @@ import ( "time" "github.com/xjasonlyu/tun2socks/v2/common/pool" + "github.com/xjasonlyu/tun2socks/v2/component/remotedns" "github.com/xjasonlyu/tun2socks/v2/core/adapter" "github.com/xjasonlyu/tun2socks/v2/log" M "github.com/xjasonlyu/tun2socks/v2/metadata" @@ -31,6 +32,8 @@ func handleTCPConn(originConn adapter.TCPConn) { DstPort: id.LocalPort, } + remotedns.RewriteMetadata(metadata) + remoteConn, err := proxy.Dial(metadata) if err != nil { log.Warnf("[TCP] dial %s: %v", metadata.DestinationAddress(), err) diff --git a/tunnel/udp.go b/tunnel/udp.go index a10e2d47..af2be014 100644 --- a/tunnel/udp.go +++ b/tunnel/udp.go @@ -7,6 +7,7 @@ import ( "time" "github.com/xjasonlyu/tun2socks/v2/common/pool" + "github.com/xjasonlyu/tun2socks/v2/component/remotedns" "github.com/xjasonlyu/tun2socks/v2/core/adapter" "github.com/xjasonlyu/tun2socks/v2/log" M "github.com/xjasonlyu/tun2socks/v2/metadata" @@ -34,6 +35,8 @@ func handleUDPConn(uc adapter.UDPConn) { DstPort: id.LocalPort, } + remotedns.RewriteMetadata(metadata) + pc, err := proxy.DialUDP(metadata) if err != nil { log.Warnf("[UDP] dial %s: %v", metadata.DestinationAddress(), err) @@ -113,7 +116,10 @@ func (pc *symmetricNATPacketConn) ReadFrom(p []byte) (int, net.Addr, error) { for { n, from, err := pc.PacketConn.ReadFrom(p) - if from != nil && from.String() != pc.dst { + // If pc.dst is not an IP address, it is a hostname. In that case, we + // do not know the source IP which packets should originate from and + // cannot drop them accordingly. + if from != nil && from.String() != pc.dst && net.ParseIP(pc.dst) != nil { log.Warnf("[UDP] symmetric NAT %s->%s: drop packet from %s", pc.src, pc.dst, from) continue }