forked from xjasonlyu/tun2socks
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit implements the ability to resolve IP addresses remotely via SOCKS5 or HTTP. It does so by intercepting DNS A and AAAA queries and replying with IP addresses from a virtual IP space which is reserved for DNS resolves. When an application performs a DNS lookup, a mapping from the virtual IP to a DNS name is created and cached for a short period of time. If the application subsequently connects to the virtual IP, the dialer will use the DNS name from the mapping instead of the destination IP address.
- Loading branch information
1 parent
6cfc253
commit 894f087
Showing
14 changed files
with
385 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package remotedns | ||
|
||
import ( | ||
"net" | ||
"sync" | ||
|
||
"github.com/jellydator/ttlcache/v2" | ||
) | ||
|
||
var ( | ||
cache = ttlcache.NewCache() | ||
mutex = sync.Mutex{} | ||
ttl uint32 = 0 | ||
) | ||
|
||
func insertNameIntoCache(ipVersion int, name string) net.IP { | ||
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 | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
// This method is protected by a mutex, and we are only inserting elements into the cache here. | ||
_, err := cache.Get((*nextAddress).String()) | ||
if err == ttlcache.ErrNotFound { | ||
_ = cache.Set((*nextAddress).String(), name) | ||
result = *nextAddress | ||
} else if err != nil { // Should never happen | ||
panic(nil) | ||
} | ||
|
||
*nextAddress = incrementIp(*nextAddress) | ||
} | ||
|
||
return result | ||
} | ||
|
||
func getCachedName(address net.IP) (interface{}, bool) { | ||
name, err := cache.Get(address.String()) | ||
return name, err == nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.VirtualIP = metadata.DstIP | ||
metadata.DstIP = nil | ||
metadata.DstName = dstName.(string) | ||
return true | ||
} | ||
|
||
func HandleDNSQuery(s *stack.Stack, id stack.TransportEndpointID, ptr stack.PacketBufferPtr) bool { | ||
if !IsEnabled() { | ||
return false | ||
} | ||
|
||
msg := dns.Msg{} | ||
err := msg.Unpack(ptr.Data().AsRange().ToSlice()) | ||
|
||
// Ignore UDP packets that are not IP queries to a recursive resolver | ||
if id.LocalPort != 53 || 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.Infof("[DNS] query %s %s", dns.TypeToString[qtype], qname) | ||
|
||
msg.RecursionDesired = false | ||
msg.RecursionAvailable = true | ||
var ip net.IP | ||
if qtype == dns.TypeA { | ||
rr := dns.A{} | ||
ip = insertNameIntoCache(4, qname) | ||
if ip == nil { | ||
log.Warnf("[DNS] IP space exhausted") | ||
return true | ||
} | ||
rr.A = ip | ||
rr.Hdr.Name = qname | ||
rr.Hdr.Ttl = ttl | ||
rr.Hdr.Class = dns.ClassINET | ||
rr.Hdr.Rrtype = qtype | ||
msg.Answer = append(msg.Answer, &rr) | ||
} | ||
|
||
msg.Response = true | ||
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(s, &wq, ep) | ||
defer conn.Close() | ||
packed, err := msg.Pack() | ||
if err != nil { | ||
return true | ||
} | ||
_, _ = conn.WriteTo(packed, &net.UDPAddr{IP: net.IP(id.RemoteAddress), Port: int(id.RemotePort)}) | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package remotedns | ||
|
||
import ( | ||
"errors" | ||
"net" | ||
"time" | ||
) | ||
|
||
// The timeout is 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. | ||
const ( | ||
minTimeout = 30 * time.Second | ||
) | ||
|
||
var ( | ||
enabled = false | ||
ip4net *net.IPNet | ||
ip4NextAddress net.IP | ||
ip4BroadcastAddress net.IP | ||
) | ||
|
||
func IsEnabled() bool { | ||
return enabled | ||
} | ||
|
||
func SetCacheTimeout(timeout time.Duration) error { | ||
if timeout < minTimeout { | ||
timeout = minTimeout | ||
} | ||
ttl = uint32(timeout.Seconds()) | ||
|
||
// Keep the value a little longer in cache than propagated via DNS | ||
return cache.SetTTL(timeout + 10*time.Second) | ||
} | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.