feat: use dns question flipping trick to reduce dns pollution

This commit is contained in:
mzz2017 2023-02-04 17:11:01 +08:00
parent e42730ed81
commit 53872a24a5
3 changed files with 43 additions and 7 deletions

View File

@ -6,11 +6,14 @@
package control
import (
"encoding/binary"
"fmt"
"github.com/cilium/ebpf"
"github.com/v2rayA/dae/common"
"github.com/v2rayA/dae/common/consts"
"golang.org/x/net/dns/dnsmessage"
"hash/fnv"
"math/rand"
"net/netip"
"strings"
"time"
@ -96,13 +99,39 @@ func (c *ControlPlane) LookupDnsRespCache(msg *dnsmessage.Message) (resp []byte)
return nil
}
// FlipDnsQuestionCase is used to reduce dns pollution.
func FlipDnsQuestionCase(dm *dnsmessage.Message) {
if len(dm.Questions) == 0 {
return
}
q := &dm.Questions[0]
// For reproducibility, we use dm.ID as input and add some entropy to make the results more discrete.
h := fnv.New64()
var buf [4]byte
binary.BigEndian.PutUint16(buf[:], dm.ID)
h.Write(buf[:2])
binary.BigEndian.PutUint32(buf[:], 20230204) // entropy
h.Write(buf[:])
r := rand.New(rand.NewSource(int64(h.Sum64())))
perm := r.Perm(int(q.Name.Length))
for i := 0; i < int(q.Name.Length/3); i++ {
j := perm[i]
// Upper to lower; lower to upper.
if q.Name.Data[j] >= 'a' && q.Name.Data[j] <= 'z' {
q.Name.Data[j] -= 'a' - 'A'
} else if q.Name.Data[j] >= 'A' && q.Name.Data[j] <= 'Z' {
q.Name.Data[j] += 'a' - 'A'
}
}
}
// DnsRespHandler handle DNS resp. This function should be invoked when cache miss.
func (c *ControlPlane) DnsRespHandler(data []byte) (newData []byte, err error) {
var msg dnsmessage.Message
if err = msg.Unpack(data); err != nil {
return nil, fmt.Errorf("unpack dns pkt: %w", err)
}
FlipDnsQuestionCase(&msg)
// Check healthy.
if !msg.Response || msg.RCode != dnsmessage.RCodeSuccess || len(msg.Questions) == 0 {
return data, nil
@ -140,8 +169,8 @@ func (c *ControlPlane) DnsRespHandler(data []byte) (newData []byte, err error) {
// Update dnsCache.
c.mutex.Lock()
fqdn := q.Name.String()
cacheKey := strings.ToLower(fqdn) + q.Type.String()
fqdn := strings.ToLower(q.Name.String())
cacheKey := fqdn + q.Type.String()
cache, ok := c.dnsCache[cacheKey]
if ok {
c.mutex.Unlock()

View File

@ -15,6 +15,7 @@ import (
"golang.org/x/net/dns/dnsmessage"
"net"
"net/netip"
"strings"
"time"
)
@ -122,7 +123,7 @@ func (c *ControlPlane) handlePkt(data []byte, lConn *net.UDPConn, lAddrPort neti
if c.log.IsLevelEnabled(logrus.DebugLevel) && len(dnsMessage.Questions) > 0 {
q := dnsMessage.Questions[0]
c.log.Tracef("UDP(DNS) %v <-[%v]-> Cache: %v %v",
RefineSourceToShow(lAddrPort, dest.Addr()), outbound.Name, q.Name, q.Type,
RefineSourceToShow(lAddrPort, dest.Addr()), outbound.Name, strings.ToLower(q.Name.String()), q.Type,
)
}
return nil
@ -132,6 +133,12 @@ func (c *ControlPlane) handlePkt(data []byte, lConn *net.UDPConn, lAddrPort neti
// NOTICE: Routing was calculated in advance by the eBPF program.
dummyFrom = &addrHdr.Dest
dest = c.dnsUpstream
// Flip dns question to reduce dns pollution.
FlipDnsQuestionCase(dnsMessage)
if data, err = dnsMessage.Pack(); err != nil {
return fmt.Errorf("pack flipped dns packet: %w", err)
}
}
}
@ -159,7 +166,7 @@ func (c *ControlPlane) handlePkt(data []byte, lConn *net.UDPConn, lAddrPort neti
"l4proto": "UDP(DNS)",
"outbound": outbound.Name,
"dialer": d.Name(),
"qname": q.Name,
"qname": strings.ToLower(q.Name.String()),
"qtype": q.Type,
}).Infof("%v <-> %v",
RefineSourceToShow(lAddrPort, dest.Addr()), RefineAddrPortToShow(dest),

View File

@ -9,7 +9,7 @@ global {
# Now only support UDP and IP:Port.
# Please make sure DNS traffic will go through and be forwarded by dae.
# The request to dns upstream follows routing defined below.
dns_upstream: '1.1.1.1:53'
dns_upstream: '8.8.8.8:53'
# The LAN interface to bind. Use it if you only want to proxy LAN instead of localhost.
# Multiple interfaces split by ",".
@ -62,7 +62,7 @@ group {
routing {
# See routing.md for full examples.
ip(1.1.1.1) && port(53) -> my_group
pname(dae) && ip(8.8.8.8) && port(53) -> direct
pname(firefox) && domain(ip.sb) -> direct
pname(curl) && domain(ip.sb) -> my_group