Skip to content

Commit

Permalink
all: support more $dnsrewrite rr types
Browse files Browse the repository at this point in the history
  • Loading branch information
ainar-g committed Dec 22, 2020
1 parent bdff46e commit 70b832c
Show file tree
Hide file tree
Showing 8 changed files with 401 additions and 44 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.14
require (
github.com/AdguardTeam/dnsproxy v0.33.7
github.com/AdguardTeam/golibs v0.4.4
github.com/AdguardTeam/urlfilter v0.14.0
github.com/AdguardTeam/urlfilter v0.14.1
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.0.1
github.com/fsnotify/fsnotify v1.4.9
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.14.0 h1:+aAhOvZDVGzl5gTERB4pOJCL1zxMyw7vLecJJ6TQTCw=
github.com/AdguardTeam/urlfilter v0.14.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
github.com/AdguardTeam/urlfilter v0.14.1 h1:imYls0fit9ojA6pP1hWFUEIjyoXbDF85ZM+G67bI48c=
github.com/AdguardTeam/urlfilter v0.14.1/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
Expand Down
2 changes: 1 addition & 1 deletion internal/dnsfilter/dnsrewrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (

// DNSRewriteResult is the result of application of $dnsrewrite rules.
type DNSRewriteResult struct {
RCode rules.RCode `json:",omitempty"`
Response DNSRewriteResultResponse `json:",omitempty"`
RCode rules.RCode `json:",omitempty"`
}

// DNSRewriteResultResponse is the collection of DNS response records
Expand Down
38 changes: 33 additions & 5 deletions internal/dnsforward/dnsrewrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,55 @@ import (
)

// filterDNSRewriteResponse handles a single DNS rewrite response entry.
// It returns the constructed answer resource record.
// It returns the properly constructed answer resource record.
func (s *Server) filterDNSRewriteResponse(req *dns.Msg, rr rules.RRType, v rules.RRValue) (ans dns.RR, err error) {
// TODO(a.garipov): As more types are added, we will probably want to
// use a handler-oriented approach here. So, think of a way to decouple
// the answer generation logic from the Server.

switch rr {
case dns.TypeA, dns.TypeAAAA:
ip, ok := v.(net.IP)
if !ok {
return nil, fmt.Errorf("value has type %T, not net.IP", v)
return nil, fmt.Errorf("value for rr type %d has type %T, not net.IP", rr, v)
}

if rr == dns.TypeA {
return s.genAAnswer(req, ip.To4()), nil
}

return s.genAAAAAnswer(req, ip), nil
case dns.TypeTXT:
case dns.TypePTR,
dns.TypeTXT:
str, ok := v.(string)
if !ok {
return nil, fmt.Errorf("value has type %T, not string", v)
return nil, fmt.Errorf("value for rr type %d has type %T, not string", rr, v)
}

if rr == dns.TypeTXT {
return s.genTXTAnswer(req, []string{str}), nil
}

return s.genPTRAnswer(req, str), nil
case dns.TypeMX:
mx, ok := v.(*rules.DNSMX)
if !ok {
return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSMX", rr, v)
}

return s.genMXAnswer(req, mx), nil
case dns.TypeHTTPS,
dns.TypeSVCB:
svcb, ok := v.(*rules.DNSSVCB)
if !ok {
return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSSVCB", rr, v)
}

if rr == dns.TypeHTTPS {
return s.genHTTPSAnswer(req, svcb), nil
}

return s.genTXTAnswer(req, []string{str}), nil
return s.genSVCBAnswer(req, svcb), nil
default:
log.Debug("don't know how to handle dns rr type %d, skipping", rr)

Expand Down
74 changes: 39 additions & 35 deletions internal/dnsforward/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
)

Expand Down Expand Up @@ -102,38 +103,54 @@ func (s *Server) genAAAARecord(request *dns.Msg, ip net.IP) *dns.Msg {
return resp
}

func (s *Server) genAAnswer(req *dns.Msg, ip net.IP) *dns.A {
answer := new(dns.A)
answer.Hdr = dns.RR_Header{
func (s *Server) hdr(req *dns.Msg, rrType rules.RRType) (h dns.RR_Header) {
return dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypeA,
Rrtype: rrType,
Ttl: s.conf.BlockedResponseTTL,
Class: dns.ClassINET,
}
answer.A = ip
return answer
}

func (s *Server) genAAAAAnswer(req *dns.Msg, ip net.IP) *dns.AAAA {
answer := new(dns.AAAA)
answer.Hdr = dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypeAAAA,
Ttl: s.conf.BlockedResponseTTL,
Class: dns.ClassINET,
func (s *Server) genAAnswer(req *dns.Msg, ip net.IP) (ans *dns.A) {
return &dns.A{
Hdr: s.hdr(req, dns.TypeA),
A: ip,
}
answer.AAAA = ip
return answer
}

func (s *Server) genTXTAnswer(req *dns.Msg, strs []string) (answer *dns.TXT) {
func (s *Server) genAAAAAnswer(req *dns.Msg, ip net.IP) (ans *dns.AAAA) {
return &dns.AAAA{
Hdr: s.hdr(req, dns.TypeAAAA),
AAAA: ip,
}
}

func (s *Server) genCNAMEAnswer(req *dns.Msg, cname string) (ans *dns.CNAME) {
return &dns.CNAME{
Hdr: s.hdr(req, dns.TypeCNAME),
Target: dns.Fqdn(cname),
}
}

func (s *Server) genMXAnswer(req *dns.Msg, mx *rules.DNSMX) (ans *dns.MX) {
return &dns.MX{
Hdr: s.hdr(req, dns.TypePTR),
Preference: mx.Preference,
Mx: mx.Exchange,
}
}

func (s *Server) genPTRAnswer(req *dns.Msg, ptr string) (ans *dns.PTR) {
return &dns.PTR{
Hdr: s.hdr(req, dns.TypePTR),
Ptr: ptr,
}
}

func (s *Server) genTXTAnswer(req *dns.Msg, strs []string) (ans *dns.TXT) {
return &dns.TXT{
Hdr: dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypeTXT,
Ttl: s.conf.BlockedResponseTTL,
Class: dns.ClassINET,
},
Hdr: s.hdr(req, dns.TypeTXT),
Txt: strs,
}
}
Expand Down Expand Up @@ -198,19 +215,6 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
return resp
}

// Make a CNAME response
func (s *Server) genCNAMEAnswer(req *dns.Msg, cname string) *dns.CNAME {
answer := new(dns.CNAME)
answer.Hdr = dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypeCNAME,
Ttl: s.conf.BlockedResponseTTL,
Class: dns.ClassINET,
}
answer.Target = dns.Fqdn(cname)
return answer
}

// Create REFUSED DNS response
func (s *Server) makeResponseREFUSED(request *dns.Msg) *dns.Msg {
resp := dns.Msg{}
Expand Down
168 changes: 168 additions & 0 deletions internal/dnsforward/svcbmsg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package dnsforward

import (
"encoding/base64"
"net"
"strconv"

"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
)

// genHTTPSAnswer returns a properly initialized HTTPS resource record.
//
// See the comment on genSVCBAnswer for a list of current restrictions on
// parameter values.
func (s *Server) genHTTPSAnswer(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns.HTTPS) {
ans = &dns.HTTPS{
SVCB: *s.genSVCBAnswer(req, svcb),
}

ans.Hdr.Rrtype = dns.TypeHTTPS

return ans
}

// strToSVCBKey is the string-to-svcb-key mapping.
//
// See https://github.com/miekg/dns/blob/23c4faca9d32b0abbb6e179aa1aadc45ac53a916/svcb.go#L27.
//
// TODO(a.garipov): Propose exporting this API or something similar in the
// github.com/miekg/dns module.
var strToSVCBKey = map[string]dns.SVCBKey{
"alpn": dns.SVCB_ALPN,
"echconfig": dns.SVCB_ECHCONFIG,
"ipv4hint": dns.SVCB_IPV4HINT,
"ipv6hint": dns.SVCB_IPV6HINT,
"mandatory": dns.SVCB_MANDATORY,
"no-default-alpn": dns.SVCB_NO_DEFAULT_ALPN,
"port": dns.SVCB_PORT,
}

// svcbKeyHandler is a handler for one SVCB parameter key.
type svcbKeyHandler func(valStr string) (val dns.SVCBKeyValue)

// svcbKeyHandlers are the supported SVCB parameters handlers.
var svcbKeyHandlers = map[string]svcbKeyHandler{
"alpn": func(valStr string) (val dns.SVCBKeyValue) {
return &dns.SVCBAlpn{
Alpn: []string{valStr},
}
},

"echconfig": func(valStr string) (val dns.SVCBKeyValue) {
ech, err := base64.StdEncoding.DecodeString(valStr)
if err != nil {
log.Debug("can't parse svcb/https echconfig: %s; ignoring", err)

return nil
}

return &dns.SVCBECHConfig{
ECH: ech,
}
},

"ipv4hint": func(valStr string) (val dns.SVCBKeyValue) {
ip := net.ParseIP(valStr)
if ip4 := ip.To4(); ip == nil || ip4 == nil {
log.Debug("can't parse svcb/https ipv4 hint %q; ignoring", valStr)

return nil
}

return &dns.SVCBIPv4Hint{
Hint: []net.IP{ip},
}
},

"ipv6hint": func(valStr string) (val dns.SVCBKeyValue) {
ip := net.ParseIP(valStr)
if ip == nil {
log.Debug("can't parse svcb/https ipv6 hint %q; ignoring", valStr)

return nil
}

return &dns.SVCBIPv6Hint{
Hint: []net.IP{ip},
}
},

"mandatory": func(valStr string) (val dns.SVCBKeyValue) {
code, ok := strToSVCBKey[valStr]
if !ok {
log.Debug("unknown svcb/https mandatory key %q, ignoring", valStr)

return nil
}

return &dns.SVCBMandatory{
Code: []dns.SVCBKey{code},
}
},

"no-default-alpn": func(_ string) (val dns.SVCBKeyValue) {
return &dns.SVCBNoDefaultAlpn{}
},

"port": func(valStr string) (val dns.SVCBKeyValue) {
port64, err := strconv.ParseUint(valStr, 10, 16)
if err != nil {
log.Debug("can't parse svcb/https port: %s; ignoring", err)

return nil
}

return &dns.SVCBPort{
Port: uint16(port64),
}
},
}

// genSVCBAnswer returns a properly initialized SVCB resource record.
//
// Currently, there are several restrictions on how the parameters are parsed.
// Firstly, the parsing of non-contiguous values isn't supported. Secondly, the
// parsing of value-lists is not supported either.
//
// ipv4hint=127.0.0.1 // Supported.
// ipv4hint="127.0.0.1" // Unsupported.
// ipv4hint=127.0.0.1,127.0.0.2 // Unsupported.
// ipv4hint="127.0.0.1,127.0.0.2" // Unsupported.
//
// TODO(a.garipov): Support all of these.
func (s *Server) genSVCBAnswer(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns.SVCB) {
ans = &dns.SVCB{
Hdr: s.hdr(req, dns.TypeSVCB),
Priority: svcb.Priority,
Target: svcb.Target,
}
if len(svcb.Params) == 0 {
return ans
}

values := make([]dns.SVCBKeyValue, 0, len(svcb.Params))
for k, valStr := range svcb.Params {
handler, ok := svcbKeyHandlers[k]
if !ok {
log.Debug("unknown svcb/https key %q, ignoring", k)

continue
}

val := handler(valStr)
if val == nil {
continue
}

values = append(values, val)
}

if len(values) > 0 {
ans.Value = values
}

return ans
}
Loading

0 comments on commit 70b832c

Please sign in to comment.