我的心里除了露娜大人已经装不下其他女人了

NextDNS“匿名 EDNS 客户端子网”功能的简单实现

NextDNS 有一个很神奇的匿名 EDNS 客户端子网功能

他们也写了一篇文章来说明这个功能是怎样实现的,具体可以参考 https://medium.com/nextdns/how-we-made-dns-both-fast-and-private-with-ecs-4970d70401e5

简单来说这个功能可以在不暴露你的 IP 段的情况下提供准确的 ECS,对于追求隐私的人(隐私怪)来说这是一个非常好用的功能

我就在想,能不能自己实现一下这个功能呢

NextDNS 使用的递归解析器是 Unbound,Unbound 是不存在这种功能的,因此他们肯定是在 DNS 转发器上实现了这个功能(不过也有可能是魔改了 Unbound,不过我感觉可能性不大)

于是,在 GPT-4 的帮助下我得到了下面的 Go 代码

package main

import (
    "log"
    "net"

    "github.com/miekg/dns"
    "github.com/oschwald/geoip2-golang"
)

const (
    upstreamDNS  = "8.8.8.8:53"
    listenAddr   = ":53"
    geoIPDBPath  = "GeoLite2-City.mmdb"
)

func main() {
    geoIP, err := geoip2.Open(geoIPDBPath)
    if err != nil {
        log.Fatalf("Failed to open GeoIP database: %v", err)
    }
    defer geoIP.Close()

    handler := func(w dns.ResponseWriter, req *dns.Msg) {
        clientIP, _, _ := net.SplitHostPort(w.RemoteAddr().String())
        log.Printf("Received request from %s", clientIP)

        // Log the incoming DNS request
        log.Printf("Incoming DNS request: %s", req.String())

        opt := req.IsEdns0()
        if opt != nil {
            log.Printf("EDNS0 options: %v", opt.Option)
        } else {
            opt = &dns.OPT{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT}}
            req.Extra = append(req.Extra, opt)
        }

        // Lookup client's city from GeoIP database
        clientCity := getCityFromIP(geoIP, clientIP)
        log.Printf("Client city: %s", clientCity)

        // Replace client's EDNS0 options with new subnet
        newSubnet, newMask := getNewSubnetForCity(clientCity)
        _, ipNet, _ := net.ParseCIDR(newSubnet)
        newOpt := &dns.EDNS0_SUBNET{
            Code:          dns.EDNS0SUBNET,
            Family:        1, // IPv4
            SourceNetmask: uint8(newMask), // 设置子网掩码
            Address:       ipNet.IP,
        }
        opt.Option = append(opt.Option, newOpt)

        // Forward the request to upstream
        c := new(dns.Client)
        res, _, err := c.Exchange(req, upstreamDNS)
        if err != nil {
            log.Printf("Failed to forward request: %v", err)
            dns.HandleFailed(w, req)
            return
        }

        // Log the received DNS response
        log.Printf("Received DNS response: %s", res.String())

        // Write response back to the client
        w.WriteMsg(res)
    }

    for _, domain := range []string{"com.", "org.", "net.", "io.", "app.", "dev.", "example."} {
        dns.HandleFunc(domain, handler)
    }

    log.Printf("Listening on %s...", listenAddr)
    err = dns.ListenAndServe(listenAddr, "udp", nil)
    if err != nil {
        log.Fatalf("Failed to start server: %v", err)
    }
}

func getCityFromIP(db *geoip2.Reader, ipStr string) string {
    ip := net.ParseIP(ipStr)
    record, err := db.City(ip)
    if err != nil {
        log.Printf("Failed to get city from IP %s: %v", ipStr, err)
        return ""
    }
    city := record.City.Names["en"]
    log.Printf("IP %s belongs to city %s", ipStr, city)
    return city
}

var cityToSubnetMap = map[string]string{
    "Taipei":        "220.229.69.0",
    "Central":       "123.255.88.0",
    // Add more cities and subnets here
}

func getNewSubnetForCity(city string) (string, int) {
    subnet, ok := cityToSubnetMap[city]
    if !ok {
        // If the city is not in the map, return a default subnet
        return "123.255.88.0/24", 24 // 修改为24位子网掩码
    }
    return subnet + "/24", 24 // 添加24位子网掩码
}

这段代码实现了从 GeoIP 数据库中获取到客户端的 IP 所对应的城市,然后从cityToSubnetMap中获取到城市所对应的 ECS,如果匹配不到则将 ECS 变为123.255.88.0/24(DNS 出口所在的地区)

不过 NextDNS 的匿名 EDNS 可以做到获取到同一 ASN 下的相同地区的 IP 段,应该是专门写了个 API 接口,我这里仅做测试就不弄得那么麻烦了,感兴趣的可以自己修改代码

效果

dig o-o.myaddr.l.google.com TXT

dig www.google.com

NextDNS“匿名 EDNS 客户端子网”功能的简单实现

https://www.9bingyin.com/archives/simple-nextdns-anonymized-edns.html

作者

bingyin

发布时间

2023-07-01

许可协议

CC BY 4.0

添加新评论