diff --git a/dns/doh.go b/dns/doh.go index 97e01ea720..5970a1bba0 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -9,6 +9,7 @@ import ( "io" "net" "net/http" + "net/netip" "net/url" "runtime" "strconv" @@ -67,6 +68,8 @@ type dnsOverHTTPS struct { dialer *dnsDialer addr string skipCertVerify bool + ecsPrefix netip.Prefix + ecsOverride bool } // type check @@ -99,6 +102,28 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin doh.skipCertVerify = true } + if ecs := params["ecs"]; ecs != "" { + prefix, err := netip.ParsePrefix(ecs) + if err != nil { + addr, err := netip.ParseAddr(ecs) + if err != nil { + log.Warnln("DOH config with invalid ecs: %s", ecs) + } else { + doh.ecsPrefix = netip.PrefixFrom(addr, addr.BitLen()) + } + } else { + doh.ecsPrefix = prefix + } + } + + if doh.ecsPrefix.IsValid() { + log.Debugln("DOH [%s] config with ecs: %s", doh.addr, doh.ecsPrefix) + } + + if params["ecs-override"] == "true" { + doh.ecsOverride = true + } + runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close) return doh @@ -126,6 +151,10 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D. } }() + if doh.ecsPrefix.IsValid() { + setEdns0Subnet(m, doh.ecsPrefix, doh.ecsOverride) + } + // Check if there was already an active client before sending the request. // We'll only attempt to re-connect if there was one. client, isCached, err := doh.getClient(ctx) diff --git a/dns/edns0_subnet.go b/dns/edns0_subnet.go new file mode 100644 index 0000000000..2ed4f140b5 --- /dev/null +++ b/dns/edns0_subnet.go @@ -0,0 +1,51 @@ +package dns + +import ( + "net/netip" + + "github.com/miekg/dns" +) + +func setEdns0Subnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) bool { + var ( + optRecord *dns.OPT + subnetOption *dns.EDNS0_SUBNET + ) +findExists: + for _, record := range message.Extra { + var isOPTRecord bool + if optRecord, isOPTRecord = record.(*dns.OPT); isOPTRecord { + for _, option := range optRecord.Option { + var isEDNS0Subnet bool + if subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET); isEDNS0Subnet { + if !override { + return false + } + break findExists + } + } + } + } + if optRecord == nil { + optRecord = &dns.OPT{ + Hdr: dns.RR_Header{ + Name: ".", + Rrtype: dns.TypeOPT, + }, + } + message.Extra = append(message.Extra, optRecord) + } + if subnetOption == nil { + subnetOption = new(dns.EDNS0_SUBNET) + optRecord.Option = append(optRecord.Option, subnetOption) + } + subnetOption.Code = dns.EDNS0SUBNET + if clientSubnet.Addr().Is4() { + subnetOption.Family = 1 + } else { + subnetOption.Family = 2 + } + subnetOption.SourceNetmask = uint8(clientSubnet.Bits()) + subnetOption.Address = clientSubnet.Addr().AsSlice() + return true +}