7f2f8de922
Merge in DNS/adguard-home from imp-naming to master Squashed commit of the following: commit d9e75c37af9a738384393735c141a41406d22eeb Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Thu May 13 15:52:14 2021 +0300 all: less annoying pkg names
232 lines
4.9 KiB
Go
232 lines
4.9 KiB
Go
// DNS Rewrites
|
|
|
|
package filtering
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/AdguardTeam/golibs/log"
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// RewriteEntry is a rewrite array element
|
|
type RewriteEntry struct {
|
|
Domain string `yaml:"domain"`
|
|
Answer string `yaml:"answer"` // IP address or canonical name
|
|
Type uint16 `yaml:"-"` // DNS record type: CNAME, A or AAAA
|
|
IP net.IP `yaml:"-"` // Parsed IP address (if Type is A or AAAA)
|
|
}
|
|
|
|
func (r *RewriteEntry) equals(b RewriteEntry) bool {
|
|
return r.Domain == b.Domain && r.Answer == b.Answer
|
|
}
|
|
|
|
func isWildcard(host string) bool {
|
|
return len(host) >= 2 &&
|
|
host[0] == '*' && host[1] == '.'
|
|
}
|
|
|
|
// Return TRUE of host name matches a wildcard pattern
|
|
func matchDomainWildcard(host, wildcard string) bool {
|
|
return isWildcard(wildcard) &&
|
|
strings.HasSuffix(host, wildcard[1:])
|
|
}
|
|
|
|
type rewritesArray []RewriteEntry
|
|
|
|
func (a rewritesArray) Len() int { return len(a) }
|
|
|
|
func (a rewritesArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
// Priority:
|
|
// . CNAME < A/AAAA;
|
|
// . exact < wildcard;
|
|
// . higher level wildcard < lower level wildcard
|
|
func (a rewritesArray) Less(i, j int) bool {
|
|
if a[i].Type == dns.TypeCNAME && a[j].Type != dns.TypeCNAME {
|
|
return true
|
|
} else if a[i].Type != dns.TypeCNAME && a[j].Type == dns.TypeCNAME {
|
|
return false
|
|
}
|
|
|
|
if isWildcard(a[i].Domain) {
|
|
if !isWildcard(a[j].Domain) {
|
|
return false
|
|
}
|
|
} else {
|
|
if isWildcard(a[j].Domain) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// both are wildcards
|
|
return len(a[i].Domain) > len(a[j].Domain)
|
|
}
|
|
|
|
// Prepare entry for use
|
|
func (r *RewriteEntry) prepare() {
|
|
if r.Answer == "AAAA" {
|
|
r.IP = nil
|
|
r.Type = dns.TypeAAAA
|
|
return
|
|
} else if r.Answer == "A" {
|
|
r.IP = nil
|
|
r.Type = dns.TypeA
|
|
return
|
|
}
|
|
|
|
ip := net.ParseIP(r.Answer)
|
|
if ip == nil {
|
|
r.Type = dns.TypeCNAME
|
|
return
|
|
}
|
|
|
|
r.IP = ip
|
|
r.Type = dns.TypeAAAA
|
|
|
|
ip4 := ip.To4()
|
|
if ip4 != nil {
|
|
r.IP = ip4
|
|
r.Type = dns.TypeA
|
|
}
|
|
}
|
|
|
|
func (d *DNSFilter) prepareRewrites() {
|
|
for i := range d.Rewrites {
|
|
d.Rewrites[i].prepare()
|
|
}
|
|
}
|
|
|
|
// Get the list of matched rewrite entries.
|
|
// Priority: CNAME, A/AAAA; exact, wildcard.
|
|
// If matched exactly, don't return wildcard entries.
|
|
// If matched by several wildcards, select the more specific one
|
|
func findRewrites(a []RewriteEntry, host string) []RewriteEntry {
|
|
rr := rewritesArray{}
|
|
for _, r := range a {
|
|
if r.Domain != host {
|
|
if !matchDomainWildcard(host, r.Domain) {
|
|
continue
|
|
}
|
|
}
|
|
rr = append(rr, r)
|
|
}
|
|
|
|
if len(rr) == 0 {
|
|
return nil
|
|
}
|
|
|
|
sort.Sort(rr)
|
|
|
|
for i, r := range rr {
|
|
if isWildcard(r.Domain) {
|
|
// Don't use rr[:0], because we need to return at least
|
|
// one item here.
|
|
rr = rr[:max(1, i)]
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
return rr
|
|
}
|
|
|
|
func max(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
|
|
return b
|
|
}
|
|
|
|
func rewriteArrayDup(a []RewriteEntry) []RewriteEntry {
|
|
a2 := make([]RewriteEntry, len(a))
|
|
copy(a2, a)
|
|
return a2
|
|
}
|
|
|
|
type rewriteEntryJSON struct {
|
|
Domain string `json:"domain"`
|
|
Answer string `json:"answer"`
|
|
}
|
|
|
|
func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
|
arr := []*rewriteEntryJSON{}
|
|
|
|
d.confLock.Lock()
|
|
for _, ent := range d.Config.Rewrites {
|
|
jsent := rewriteEntryJSON{
|
|
Domain: ent.Domain,
|
|
Answer: ent.Answer,
|
|
}
|
|
arr = append(arr, &jsent)
|
|
}
|
|
d.confLock.Unlock()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
err := json.NewEncoder(w).Encode(arr)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusInternalServerError, "json.Encode: %s", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
|
jsent := rewriteEntryJSON{}
|
|
err := json.NewDecoder(r.Body).Decode(&jsent)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
|
return
|
|
}
|
|
|
|
ent := RewriteEntry{
|
|
Domain: jsent.Domain,
|
|
Answer: jsent.Answer,
|
|
}
|
|
ent.prepare()
|
|
d.confLock.Lock()
|
|
d.Config.Rewrites = append(d.Config.Rewrites, ent)
|
|
d.confLock.Unlock()
|
|
log.Debug("Rewrites: added element: %s -> %s [%d]",
|
|
ent.Domain, ent.Answer, len(d.Config.Rewrites))
|
|
|
|
d.Config.ConfigModified()
|
|
}
|
|
|
|
func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
|
|
jsent := rewriteEntryJSON{}
|
|
err := json.NewDecoder(r.Body).Decode(&jsent)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
|
return
|
|
}
|
|
|
|
entDel := RewriteEntry{
|
|
Domain: jsent.Domain,
|
|
Answer: jsent.Answer,
|
|
}
|
|
arr := []RewriteEntry{}
|
|
d.confLock.Lock()
|
|
for _, ent := range d.Config.Rewrites {
|
|
if ent.equals(entDel) {
|
|
log.Debug("Rewrites: removed element: %s -> %s", ent.Domain, ent.Answer)
|
|
continue
|
|
}
|
|
arr = append(arr, ent)
|
|
}
|
|
d.Config.Rewrites = arr
|
|
d.confLock.Unlock()
|
|
|
|
d.Config.ConfigModified()
|
|
}
|
|
|
|
func (d *DNSFilter) registerRewritesHandlers() {
|
|
d.Config.HTTPRegister(http.MethodGet, "/control/rewrite/list", d.handleRewriteList)
|
|
d.Config.HTTPRegister(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd)
|
|
d.Config.HTTPRegister(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete)
|
|
}
|