From 4c50074a6bcd64d1aa6599060531dde11dfaeca2 Mon Sep 17 00:00:00 2001 From: Cuthead <183076078+Cuthead@users.noreply.github.com> Date: Thu, 2 Apr 2026 02:50:16 +0000 Subject: [PATCH 1/6] ipv4 fallback for hint ipv6 --- phantomtcp/dns.go | 109 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 25 deletions(-) diff --git a/phantomtcp/dns.go b/phantomtcp/dns.go index 56d482f..3680880 100755 --- a/phantomtcp/dns.go +++ b/phantomtcp/dns.go @@ -1090,7 +1090,8 @@ func GetDNSLie(index int) (string, *Outbound) { func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { hint := outbound.Hint var qtype uint16 = 1 - if hint&HINT_IPV6 != 0 { + dualQuery := hint&HINT_IPV6 != 0 + if dualQuery { qtype = 28 } @@ -1122,10 +1123,14 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { return records.Index, records.IPv4Hint.Addresses } case 28: - if records.IPv6Hint != nil { + if records.IPv6Hint != nil && len(records.IPv6Hint.Addresses) > 0 { logPrintln(3, "cached:", name, qtype, records.IPv6Hint.Addresses) return records.Index, records.IPv6Hint.Addresses } + if records.IPv4Hint != nil { + logPrintln(3, "cached:", name, 1, records.IPv4Hint.Addresses) + return records.Index, records.IPv4Hint.Addresses + } default: return 0, nil } @@ -1133,6 +1138,8 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { var request []byte var response []byte var err error + var response6 []byte + var err6 error var options ServerOptions u, err := url.Parse(outbound.DNS) @@ -1150,31 +1157,67 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { } if u.Host != "" { - switch u.Scheme { - case "udp": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = UDPlookup(request, u.Host) - case "tcp": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = TCPlookup(request, u.Host) - case "tls": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = TLSlookup(request, u.Host) - case "https": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = HTTPSlookup(request, u, options.Domain) - case "tfo": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = TFOlookup(request, u.Host) - default: - records.Index = AddDNSLie(name, outbound) - records.ALPN = hint - return records.Index, nil + if dualQuery { + done := make(chan struct{}, 1) + request := PackRequest(_name, 1, uint16(0), options.ECS, options.QType2) + request6 := PackRequest(_name, 28, uint16(0), options.ECS, options.QType2) + switch u.Scheme { + case "udp": + go func() { response6, err6 = UDPlookup(request6, u.Host); done <- struct{}{} }() + response, err = UDPlookup(request, u.Host) + case "tcp": + go func() { response6, err6 = TCPlookup(request6, u.Host); done <- struct{}{} }() + response, err = TCPlookup(request, u.Host) + case "tls": + go func() { response6, err6 = TLSlookup(request6, u.Host); done <- struct{}{} }() + response, err = TLSlookup(request, u.Host) + case "https": + go func() { response6, err6 = HTTPSlookup(request6, u, options.Domain); done <- struct{}{} }() + response, err = HTTPSlookup(request, u, options.Domain) + case "tfo": + go func() { response6, err6 = TFOlookup(request6, u.Host); done <- struct{}{} }() + response, err = TFOlookup(request, u.Host) + default: + records.Index = AddDNSLie(name, outbound) + records.ALPN = hint + return records.Index, nil + } + <-done + } else { + switch u.Scheme { + case "udp": + request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) + response, err = UDPlookup(request, u.Host) + case "tcp": + request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) + response, err = TCPlookup(request, u.Host) + case "tls": + request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) + response, err = TLSlookup(request, u.Host) + case "https": + request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) + response, err = HTTPSlookup(request, u, options.Domain) + case "tfo": + request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) + response, err = TFOlookup(request, u.Host) + default: + records.Index = AddDNSLie(name, outbound) + records.ALPN = hint + return records.Index, nil + } } } if err != nil { logPrintln(1, err) - return 0, nil + if !dualQuery { + return 0, nil + } + } + if err6 != nil { + logPrintln(1, err6) + if err != nil { + return 0, nil + } } if (hint&HINT_FAKEIP != 0) && records.Index == 0 { @@ -1183,6 +1226,7 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { } records.GetAnswers(response, options) + records.GetAnswers(response6, options) DNSRecordMutex.Lock() defer DNSRecordMutex.Unlock() @@ -1211,8 +1255,23 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { records.IPv6Hint = &RecordAddresses{0, []net.IP{}} } logPrintln(3, "nslookup", name, qtype, records.IPv6Hint.Addresses) - addresses := make([]net.IP, len(records.IPv6Hint.Addresses)) - copy(addresses, records.IPv6Hint.Addresses) + if len(records.IPv6Hint.Addresses) > 0 { + addresses := make([]net.IP, len(records.IPv6Hint.Addresses)) + copy(addresses, records.IPv6Hint.Addresses) + return records.Index, addresses + } + if records.IPv4Hint == nil && options.Fallback != nil { + if options.Fallback.To4() != nil { + logPrintln(4, "request:", name, "fallback", options.Fallback) + records.IPv4Hint = &RecordAddresses{0, []net.IP{options.Fallback}} + } + } + if records.IPv4Hint == nil { + records.IPv4Hint = &RecordAddresses{0, []net.IP{}} + } + logPrintln(3, "nslookup", name, 1, records.IPv4Hint.Addresses) + addresses := make([]net.IP, len(records.IPv4Hint.Addresses)) + copy(addresses, records.IPv4Hint.Addresses) return records.Index, addresses } From 5dee46d088a34a09148bc0ec28f4e29021c34104 Mon Sep 17 00:00:00 2001 From: Cuthead <183076078+Cuthead@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:16:14 +0000 Subject: [PATCH 2/6] dual by default --- phantomtcp/dns.go | 84 ++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/phantomtcp/dns.go b/phantomtcp/dns.go index 3680880..73f64ec 100755 --- a/phantomtcp/dns.go +++ b/phantomtcp/dns.go @@ -1089,9 +1089,9 @@ func GetDNSLie(index int) (string, *Outbound) { func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { hint := outbound.Hint + dual := hint&HINT_IPV6 == 0 && hint&HINT_IPV4 == 0 var qtype uint16 = 1 - dualQuery := hint&HINT_IPV6 != 0 - if dualQuery { + if hint&HINT_IPV6 != 0 { qtype = 28 } @@ -1116,23 +1116,30 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { offset++ } } - switch qtype { - case 1: - if records.IPv4Hint != nil { - logPrintln(3, "cached:", name, qtype, records.IPv4Hint.Addresses) - return records.Index, records.IPv4Hint.Addresses - } - case 28: + if dual { if records.IPv6Hint != nil && len(records.IPv6Hint.Addresses) > 0 { logPrintln(3, "cached:", name, qtype, records.IPv6Hint.Addresses) return records.Index, records.IPv6Hint.Addresses } if records.IPv4Hint != nil { - logPrintln(3, "cached:", name, 1, records.IPv4Hint.Addresses) + logPrintln(3, "cached:", name, qtype, records.IPv4Hint.Addresses) return records.Index, records.IPv4Hint.Addresses } - default: - return 0, nil + } else { + switch qtype { + case 1: + if records.IPv4Hint != nil { + logPrintln(3, "cached:", name, qtype, records.IPv4Hint.Addresses) + return records.Index, records.IPv4Hint.Addresses + } + case 28: + if records.IPv6Hint != nil { + logPrintln(3, "cached:", name, qtype, records.IPv6Hint.Addresses) + return records.Index, records.IPv6Hint.Addresses + } + default: + return 0, nil + } } var request []byte @@ -1157,7 +1164,7 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { } if u.Host != "" { - if dualQuery { + if dual { done := make(chan struct{}, 1) request := PackRequest(_name, 1, uint16(0), options.ECS, options.QType2) request6 := PackRequest(_name, 28, uint16(0), options.ECS, options.QType2) @@ -1209,7 +1216,7 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { } if err != nil { logPrintln(1, err) - if !dualQuery { + if !dual { return 0, nil } } @@ -1230,22 +1237,7 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { DNSRecordMutex.Lock() defer DNSRecordMutex.Unlock() - switch qtype { - case 1: - if records.IPv4Hint == nil && options.Fallback != nil { - if options.Fallback.To4() != nil { - logPrintln(4, "request:", name, "fallback", options.Fallback) - records.IPv4Hint = &RecordAddresses{0, []net.IP{options.Fallback}} - } - } - if records.IPv4Hint == nil { - records.IPv4Hint = &RecordAddresses{0, []net.IP{}} - } - logPrintln(3, "nslookup", name, qtype, records.IPv4Hint.Addresses) - addresses := make([]net.IP, len(records.IPv4Hint.Addresses)) - copy(addresses, records.IPv4Hint.Addresses) - return records.Index, addresses - case 28: + if dual { if records.IPv6Hint == nil && options.Fallback != nil { if options.Fallback.To4() == nil { records.IPv6Hint = &RecordAddresses{0, []net.IP{options.Fallback}} @@ -1254,7 +1246,7 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { if records.IPv6Hint == nil { records.IPv6Hint = &RecordAddresses{0, []net.IP{}} } - logPrintln(3, "nslookup", name, qtype, records.IPv6Hint.Addresses) + logPrintln(3, "nslookup", name, 28, records.IPv6Hint.Addresses) if len(records.IPv6Hint.Addresses) > 0 { addresses := make([]net.IP, len(records.IPv6Hint.Addresses)) copy(addresses, records.IPv6Hint.Addresses) @@ -1275,6 +1267,36 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { return records.Index, addresses } + switch qtype { + case 1: + if records.IPv4Hint == nil && options.Fallback != nil { + if options.Fallback.To4() != nil { + logPrintln(4, "request:", name, "fallback", options.Fallback) + records.IPv4Hint = &RecordAddresses{0, []net.IP{options.Fallback}} + } + } + if records.IPv4Hint == nil { + records.IPv4Hint = &RecordAddresses{0, []net.IP{}} + } + logPrintln(3, "nslookup", name, qtype, records.IPv4Hint.Addresses) + addresses := make([]net.IP, len(records.IPv4Hint.Addresses)) + copy(addresses, records.IPv4Hint.Addresses) + return records.Index, addresses + case 28: + if records.IPv6Hint == nil && options.Fallback != nil { + if options.Fallback.To4() == nil { + records.IPv6Hint = &RecordAddresses{0, []net.IP{options.Fallback}} + } + } + if records.IPv6Hint == nil { + records.IPv6Hint = &RecordAddresses{0, []net.IP{}} + } + logPrintln(3, "nslookup", name, qtype, records.IPv6Hint.Addresses) + addresses := make([]net.IP, len(records.IPv6Hint.Addresses)) + copy(addresses, records.IPv6Hint.Addresses) + return records.Index, addresses + } + return records.Index, nil } From 1e794693820a0945e623439a6eaa1f5c153981d6 Mon Sep 17 00:00:00 2001 From: Cuthead <183076078+Cuthead@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:53:43 +0000 Subject: [PATCH 3/6] skip dual if not hasIPv6 --- phantomtcp/dns.go | 2 +- phantomtcp/tcp.go | 31 ++++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/phantomtcp/dns.go b/phantomtcp/dns.go index 73f64ec..f3d9c69 100755 --- a/phantomtcp/dns.go +++ b/phantomtcp/dns.go @@ -1089,7 +1089,7 @@ func GetDNSLie(index int) (string, *Outbound) { func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { hint := outbound.Hint - dual := hint&HINT_IPV6 == 0 && hint&HINT_IPV4 == 0 + dual := outbound.hasIPv6() && hint&HINT_IPV6 == 0 && hint&HINT_IPV4 == 0 var qtype uint16 = 1 if hint&HINT_IPV6 != 0 { qtype = 28 diff --git a/phantomtcp/tcp.go b/phantomtcp/tcp.go index d028463..5369824 100755 --- a/phantomtcp/tcp.go +++ b/phantomtcp/tcp.go @@ -1,13 +1,13 @@ package phantomtcp import ( + "crypto/rand" "io" + mathrand "math/rand" "net" "os" - "crypto/rand" "strconv" "syscall" - mathrand "math/rand" ) const domainBytes = "abcdefghijklmnopqrstuvwxyz0123456789-" @@ -73,7 +73,7 @@ func GetLocalTCPAddr(name string, ipv6 bool) (*net.TCPAddr, error) { var laddr *net.TCPAddr ip4 := localAddr.IP.To4() if ipv6 { - if ip4 != nil || localAddr.IP.IsPrivate() { + if ip4 != nil || localAddr.IP.IsLinkLocalUnicast() { continue } ip := make([]byte, 16) @@ -95,6 +95,31 @@ func GetLocalTCPAddr(name string, ipv6 bool) (*net.TCPAddr, error) { return nil, nil } +func (outbound *Outbound) hasIPv6() bool { + if outbound.Device != "" { + addr, _ := GetLocalTCPAddr(outbound.Device, true) + return addr != nil + } + ifaces, err := net.Interfaces() + if err != nil { + return false + } + for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { + continue + } + addrs, _ := iface.Addrs() + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok { + if ipnet.IP.To4() == nil && !ipnet.IP.IsPrivate() { + return true + } + } + } + } + return false +} + func (outbound *Outbound) GetRemoteAddresses(host string, port int) ([]*net.TCPAddr, error) { switch outbound.Protocol { case DIRECT: From 74700b52894a6ebe5366805622b0fb32fc21e742 Mon Sep 17 00:00:00 2001 From: Cuthead <183076078+Cuthead@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:56:21 +0000 Subject: [PATCH 4/6] filter only fe80::/10, allow ULA --- phantomtcp/tcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phantomtcp/tcp.go b/phantomtcp/tcp.go index 5369824..02cfd7a 100755 --- a/phantomtcp/tcp.go +++ b/phantomtcp/tcp.go @@ -111,7 +111,7 @@ func (outbound *Outbound) hasIPv6() bool { addrs, _ := iface.Addrs() for _, addr := range addrs { if ipnet, ok := addr.(*net.IPNet); ok { - if ipnet.IP.To4() == nil && !ipnet.IP.IsPrivate() { + if ipnet.IP.To4() == nil && !ipnet.IP.IsLinkLocalUnicast() { return true } } From 5a4b6f2d3e202202c84df265090899f12b010534 Mon Sep 17 00:00:00 2001 From: Cuthead <183076078+Cuthead@users.noreply.github.com> Date: Sun, 5 Apr 2026 08:19:13 +0000 Subject: [PATCH 5/6] skip dual if nat64 --- phantomtcp/dns.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phantomtcp/dns.go b/phantomtcp/dns.go index f3d9c69..a4ed8af 100755 --- a/phantomtcp/dns.go +++ b/phantomtcp/dns.go @@ -1089,7 +1089,7 @@ func GetDNSLie(index int) (string, *Outbound) { func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { hint := outbound.Hint - dual := outbound.hasIPv6() && hint&HINT_IPV6 == 0 && hint&HINT_IPV4 == 0 + dual := outbound.Protocol != NAT64 && outbound.hasIPv6() && hint&HINT_IPV6 == 0 && hint&HINT_IPV4 == 0 var qtype uint16 = 1 if hint&HINT_IPV6 != 0 { qtype = 28 From 1b21f3be37689aba09ba82f9735c571a308be4a9 Mon Sep 17 00:00:00 2001 From: Cuthead <183076078+Cuthead@users.noreply.github.com> Date: Sat, 18 Apr 2026 10:03:07 +0000 Subject: [PATCH 6/6] reduce redundant code --- phantomtcp/dns.go | 60 +++++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/phantomtcp/dns.go b/phantomtcp/dns.go index a4ed8af..956ca00 100755 --- a/phantomtcp/dns.go +++ b/phantomtcp/dns.go @@ -1164,54 +1164,36 @@ func (outbound *Outbound) NSLookup(name string) (uint32, []net.IP) { } if u.Host != "" { - if dual { - done := make(chan struct{}, 1) - request := PackRequest(_name, 1, uint16(0), options.ECS, options.QType2) - request6 := PackRequest(_name, 28, uint16(0), options.ECS, options.QType2) + switch u.Scheme { + case "udp", "tcp", "tls", "https", "tfo": + default: + records.Index = AddDNSLie(name, outbound) + records.ALPN = hint + return records.Index, nil + } + doLookup := func(qt uint16) ([]byte, error) { + request = PackRequest(_name, qt, uint16(0), options.ECS, options.QType2) switch u.Scheme { case "udp": - go func() { response6, err6 = UDPlookup(request6, u.Host); done <- struct{}{} }() - response, err = UDPlookup(request, u.Host) + return UDPlookup(request, u.Host) case "tcp": - go func() { response6, err6 = TCPlookup(request6, u.Host); done <- struct{}{} }() - response, err = TCPlookup(request, u.Host) + return TCPlookup(request, u.Host) case "tls": - go func() { response6, err6 = TLSlookup(request6, u.Host); done <- struct{}{} }() - response, err = TLSlookup(request, u.Host) + return TLSlookup(request, u.Host) case "https": - go func() { response6, err6 = HTTPSlookup(request6, u, options.Domain); done <- struct{}{} }() - response, err = HTTPSlookup(request, u, options.Domain) + return HTTPSlookup(request, u, options.Domain) case "tfo": - go func() { response6, err6 = TFOlookup(request6, u.Host); done <- struct{}{} }() - response, err = TFOlookup(request, u.Host) - default: - records.Index = AddDNSLie(name, outbound) - records.ALPN = hint - return records.Index, nil + return TFOlookup(request, u.Host) } + return nil, nil + } + if dual { + done := make(chan struct{}, 1) + go func() { response6, err6 = doLookup(28); done <- struct{}{} }() + response, err = doLookup(1) <-done } else { - switch u.Scheme { - case "udp": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = UDPlookup(request, u.Host) - case "tcp": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = TCPlookup(request, u.Host) - case "tls": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = TLSlookup(request, u.Host) - case "https": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = HTTPSlookup(request, u, options.Domain) - case "tfo": - request = PackRequest(_name, qtype, uint16(0), options.ECS, options.QType2) - response, err = TFOlookup(request, u.Host) - default: - records.Index = AddDNSLie(name, outbound) - records.ALPN = hint - return records.Index, nil - } + response, err = doLookup(qtype) } } if err != nil {