mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
net/dns/{publicdns,resolver}: add start of Control D support
Updates #7946 [@bradfitz fixed up version of #8417] Change-Id: I1dbf6fa8d525b25c0d7ad5c559a7f937c3cd142a Signed-off-by: alexelisenko <39712468+alexelisenko@users.noreply.github.com> Signed-off-by: Alex Paguis <alex@windscribe.com>
This commit is contained in:
parent
aa084a29c6
commit
fe22032fb3
@ -7,10 +7,13 @@
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@ -23,6 +26,11 @@
|
||||
var dohIPsOfBase = map[string][]netip.Addr{}
|
||||
var populateOnce sync.Once
|
||||
|
||||
const (
|
||||
nextDNSBase = "https://dns.nextdns.io/"
|
||||
controlDBase = "https://dns.controld.com/"
|
||||
)
|
||||
|
||||
// DoHEndpointFromIP returns the DNS-over-HTTPS base URL for a given IP
|
||||
// and whether it's DoH-only (not speaking DNS on port 53).
|
||||
//
|
||||
@ -39,14 +47,21 @@ func DoHEndpointFromIP(ip netip.Addr) (dohBase string, dohOnly bool, ok bool) {
|
||||
if nextDNSv6RangeA.Contains(ip) || nextDNSv6RangeB.Contains(ip) {
|
||||
a := ip.As16()
|
||||
var sb strings.Builder
|
||||
const base = "https://dns.nextdns.io/"
|
||||
sb.Grow(len(base) + 12)
|
||||
sb.WriteString(base)
|
||||
sb.Grow(len(nextDNSBase) + 12)
|
||||
sb.WriteString(nextDNSBase)
|
||||
for _, b := range bytes.TrimLeft(a[4:], "\x00") {
|
||||
fmt.Fprintf(&sb, "%02x", b)
|
||||
}
|
||||
return sb.String(), true, true
|
||||
}
|
||||
|
||||
// Control D DoH URLs are of the form "https://dns.controld.com/8yezwenugs"
|
||||
// where the path component is represented by 8 bytes (7-14) of the IPv6 address in base36
|
||||
if controlDv6RangeA.Contains(ip) || controlDv6RangeB.Contains(ip) {
|
||||
path := big.NewInt(0).SetBytes(ip.AsSlice()[6:14]).Text(36)
|
||||
return controlDBase + path, true, true
|
||||
}
|
||||
|
||||
return "", false, false
|
||||
}
|
||||
|
||||
@ -80,7 +95,7 @@ func DoHIPsOfBase(dohBase string) []netip.Addr {
|
||||
if s := dohIPsOfBase[dohBase]; len(s) > 0 {
|
||||
return s
|
||||
}
|
||||
if hexStr, ok := strings.CutPrefix(dohBase, "https://dns.nextdns.io/"); ok {
|
||||
if hexStr, ok := strings.CutPrefix(dohBase, nextDNSBase); ok {
|
||||
// The path is of the form /<profile-hex>[/<hostname>/<model>/<device id>...]
|
||||
// or /<profile-hex>?<query params>
|
||||
// but only the <profile-hex> is required. Ignore the rest:
|
||||
@ -106,6 +121,14 @@ func DoHIPsOfBase(dohBase string) []netip.Addr {
|
||||
}
|
||||
}
|
||||
}
|
||||
if pathStr, ok := strings.CutPrefix(dohBase, controlDBase); ok {
|
||||
return []netip.Addr{
|
||||
controlDv4One,
|
||||
controlDv4Two,
|
||||
controlDv6Gen(nextDNSv6RangeA.Addr(), pathStr),
|
||||
controlDv6Gen(nextDNSv6RangeB.Addr(), pathStr),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -213,6 +236,36 @@ func populate() {
|
||||
// Wikimedia
|
||||
addDoH(wikimediaDNSv4, "https://wikimedia-dns.org/dns-query")
|
||||
addDoH(wikimediaDNSv6, "https://wikimedia-dns.org/dns-query")
|
||||
|
||||
// Control D
|
||||
addDoH("76.76.2.0", "https://freedns.controld.com/p0")
|
||||
addDoH("76.76.10.0", "https://freedns.controld.com/p0")
|
||||
addDoH("2606:1a40::", "https://freedns.controld.com/p0")
|
||||
addDoH("2606:1a40:1::", "https://freedns.controld.com/p0")
|
||||
|
||||
// Control D -Malware
|
||||
addDoH("76.76.2.1", "https://freedns.controld.com/p1")
|
||||
addDoH("76.76.10.1", "https://freedns.controld.com/p1")
|
||||
addDoH("2606:1a40::1", "https://freedns.controld.com/p1")
|
||||
addDoH("2606:1a40:1::1", "https://freedns.controld.com/p1")
|
||||
|
||||
// Control D -Malware + Ads
|
||||
addDoH("76.76.2.2", "https://freedns.controld.com/p2")
|
||||
addDoH("76.76.10.2", "https://freedns.controld.com/p2")
|
||||
addDoH("2606:1a40::2", "https://freedns.controld.com/p2")
|
||||
addDoH("2606:1a40:1::2", "https://freedns.controld.com/p2")
|
||||
|
||||
// Control D -Malware + Ads + Social
|
||||
addDoH("76.76.2.3", "https://freedns.controld.com/p3")
|
||||
addDoH("76.76.10.3", "https://freedns.controld.com/p3")
|
||||
addDoH("2606:1a40::3", "https://freedns.controld.com/p3")
|
||||
addDoH("2606:1a40:1::3", "https://freedns.controld.com/p3")
|
||||
|
||||
// Control D -Malware + Ads + Adult
|
||||
addDoH("76.76.2.4", "https://freedns.controld.com/family")
|
||||
addDoH("76.76.10.4", "https://freedns.controld.com/family")
|
||||
addDoH("2606:1a40::4", "https://freedns.controld.com/family")
|
||||
addDoH("2606:1a40:1::4", "https://freedns.controld.com/family")
|
||||
}
|
||||
|
||||
var (
|
||||
@ -239,6 +292,13 @@ func populate() {
|
||||
// Wikimedia DNS server IPs (anycast)
|
||||
wikimediaDNSv4Addr = netip.MustParseAddr(wikimediaDNSv4)
|
||||
wikimediaDNSv6Addr = netip.MustParseAddr(wikimediaDNSv6)
|
||||
|
||||
// The Control D IPv6 ranges (primary and secondary). The customer ID is
|
||||
// encoded in the ipv6 address is used (in base 36 form) as the DoH query
|
||||
controlDv6RangeA = netip.MustParsePrefix("2606:1a40::/48")
|
||||
controlDv6RangeB = netip.MustParsePrefix("2606:1a40:1::/48")
|
||||
controlDv4One = netip.MustParseAddr("76.76.2.22")
|
||||
controlDv4Two = netip.MustParseAddr("76.76.10.22")
|
||||
)
|
||||
|
||||
// nextDNSv6Gen generates a NextDNS IPv6 address from the upper 8 bytes in the
|
||||
@ -252,10 +312,26 @@ func nextDNSv6Gen(ip netip.Addr, id []byte) netip.Addr {
|
||||
return netip.AddrFrom16(a)
|
||||
}
|
||||
|
||||
// controlDv6Gen generates a Control D IPv6 address from provided ip and id.
|
||||
//
|
||||
// The id is taken from the DoH query path component and represents a unique resolver configuration.
|
||||
// e.g. https://dns.controld.com/hyq3ipr2ct
|
||||
func controlDv6Gen(ip netip.Addr, id string) netip.Addr {
|
||||
b := make([]byte, 8)
|
||||
decoded, _ := strconv.ParseUint(id, 36, 64)
|
||||
binary.BigEndian.PutUint64(b, decoded)
|
||||
a := ip.AsSlice()
|
||||
copy(a[6:14], b)
|
||||
addr, _ := netip.AddrFromSlice(a)
|
||||
return addr
|
||||
}
|
||||
|
||||
// IPIsDoHOnlyServer reports whether ip is a DNS server that should only use
|
||||
// DNS-over-HTTPS (not regular port 53 DNS).
|
||||
func IPIsDoHOnlyServer(ip netip.Addr) bool {
|
||||
return nextDNSv6RangeA.Contains(ip) || nextDNSv6RangeB.Contains(ip) ||
|
||||
nextDNSv4RangeA.Contains(ip) || nextDNSv4RangeB.Contains(ip) ||
|
||||
ip == wikimediaDNSv4Addr || ip == wikimediaDNSv6Addr
|
||||
ip == wikimediaDNSv4Addr || ip == wikimediaDNSv6Addr ||
|
||||
controlDv6RangeA.Contains(ip) || controlDv6RangeB.Contains(ip) ||
|
||||
ip == controlDv4One || ip == controlDv4Two
|
||||
}
|
||||
|
@ -116,6 +116,24 @@ func TestDoHIPsOfBase(t *testing.T) {
|
||||
"2a07:a8c1::c3:a884",
|
||||
),
|
||||
},
|
||||
{
|
||||
base: "https://dns.controld.com/hyq3ipr2ct",
|
||||
want: ips(
|
||||
"76.76.2.22",
|
||||
"76.76.10.22",
|
||||
"2a07:a8c0:0:6:7b5b:5949:35ad:0",
|
||||
"2a07:a8c1:0:6:7b5b:5949:35ad:0",
|
||||
),
|
||||
},
|
||||
{
|
||||
base: "https://dns.controld.com/112233445566778899aabbcc",
|
||||
want: ips(
|
||||
"76.76.2.22",
|
||||
"76.76.10.22",
|
||||
"2a07:a8c0:0:ffff:ffff:ffff:ffff:0",
|
||||
"2a07:a8c1:0:ffff:ffff:ffff:ffff:0",
|
||||
),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := DoHIPsOfBase(tt.base)
|
||||
|
@ -101,6 +101,16 @@ func TestResolversWithDelays(t *testing.T) {
|
||||
in: q("https://dns.nextdns.io/c3a884"),
|
||||
want: o("https://dns.nextdns.io/c3a884"),
|
||||
},
|
||||
{
|
||||
name: "controld-ipv6-expand",
|
||||
in: q("2606:1a40:0:6:7b5b:5949:35ad:0"),
|
||||
want: o("https://dns.controld.com/hyq3ipr2ct"),
|
||||
},
|
||||
{
|
||||
name: "controld-doh-input",
|
||||
in: q("https://dns.controld.com/hyq3ipr2ct"),
|
||||
want: o("https://dns.controld.com/hyq3ipr2ct"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
Loading…
Reference in New Issue
Block a user