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 }