mirror of
https://github.com/daeuniverse/dae.git
synced 2025-07-08 23:08:50 +07:00
feat: use dns question flipping trick to reduce dns pollution
This commit is contained in:
@ -6,11 +6,14 @@
|
|||||||
package control
|
package control
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cilium/ebpf"
|
"github.com/cilium/ebpf"
|
||||||
"github.com/v2rayA/dae/common"
|
"github.com/v2rayA/dae/common"
|
||||||
"github.com/v2rayA/dae/common/consts"
|
"github.com/v2rayA/dae/common/consts"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"hash/fnv"
|
||||||
|
"math/rand"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -96,13 +99,39 @@ func (c *ControlPlane) LookupDnsRespCache(msg *dnsmessage.Message) (resp []byte)
|
|||||||
return nil
|
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.
|
// DnsRespHandler handle DNS resp. This function should be invoked when cache miss.
|
||||||
func (c *ControlPlane) DnsRespHandler(data []byte) (newData []byte, err error) {
|
func (c *ControlPlane) DnsRespHandler(data []byte) (newData []byte, err error) {
|
||||||
var msg dnsmessage.Message
|
var msg dnsmessage.Message
|
||||||
if err = msg.Unpack(data); err != nil {
|
if err = msg.Unpack(data); err != nil {
|
||||||
return nil, fmt.Errorf("unpack dns pkt: %w", err)
|
return nil, fmt.Errorf("unpack dns pkt: %w", err)
|
||||||
}
|
}
|
||||||
|
FlipDnsQuestionCase(&msg)
|
||||||
// Check healthy.
|
// Check healthy.
|
||||||
if !msg.Response || msg.RCode != dnsmessage.RCodeSuccess || len(msg.Questions) == 0 {
|
if !msg.Response || msg.RCode != dnsmessage.RCodeSuccess || len(msg.Questions) == 0 {
|
||||||
return data, nil
|
return data, nil
|
||||||
@ -140,8 +169,8 @@ func (c *ControlPlane) DnsRespHandler(data []byte) (newData []byte, err error) {
|
|||||||
|
|
||||||
// Update dnsCache.
|
// Update dnsCache.
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
fqdn := q.Name.String()
|
fqdn := strings.ToLower(q.Name.String())
|
||||||
cacheKey := strings.ToLower(fqdn) + q.Type.String()
|
cacheKey := fqdn + q.Type.String()
|
||||||
cache, ok := c.dnsCache[cacheKey]
|
cache, ok := c.dnsCache[cacheKey]
|
||||||
if ok {
|
if ok {
|
||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"time"
|
"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 {
|
if c.log.IsLevelEnabled(logrus.DebugLevel) && len(dnsMessage.Questions) > 0 {
|
||||||
q := dnsMessage.Questions[0]
|
q := dnsMessage.Questions[0]
|
||||||
c.log.Tracef("UDP(DNS) %v <-[%v]-> Cache: %v %v",
|
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
|
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.
|
// NOTICE: Routing was calculated in advance by the eBPF program.
|
||||||
dummyFrom = &addrHdr.Dest
|
dummyFrom = &addrHdr.Dest
|
||||||
dest = c.dnsUpstream
|
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)",
|
"l4proto": "UDP(DNS)",
|
||||||
"outbound": outbound.Name,
|
"outbound": outbound.Name,
|
||||||
"dialer": d.Name(),
|
"dialer": d.Name(),
|
||||||
"qname": q.Name,
|
"qname": strings.ToLower(q.Name.String()),
|
||||||
"qtype": q.Type,
|
"qtype": q.Type,
|
||||||
}).Infof("%v <-> %v",
|
}).Infof("%v <-> %v",
|
||||||
RefineSourceToShow(lAddrPort, dest.Addr()), RefineAddrPortToShow(dest),
|
RefineSourceToShow(lAddrPort, dest.Addr()), RefineAddrPortToShow(dest),
|
||||||
|
@ -9,7 +9,7 @@ global {
|
|||||||
# Now only support UDP and IP:Port.
|
# Now only support UDP and IP:Port.
|
||||||
# Please make sure DNS traffic will go through and be forwarded by dae.
|
# Please make sure DNS traffic will go through and be forwarded by dae.
|
||||||
# The request to dns upstream follows routing defined below.
|
# 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.
|
# The LAN interface to bind. Use it if you only want to proxy LAN instead of localhost.
|
||||||
# Multiple interfaces split by ",".
|
# Multiple interfaces split by ",".
|
||||||
@ -62,7 +62,7 @@ group {
|
|||||||
routing {
|
routing {
|
||||||
# See routing.md for full examples.
|
# 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(firefox) && domain(ip.sb) -> direct
|
||||||
pname(curl) && domain(ip.sb) -> my_group
|
pname(curl) && domain(ip.sb) -> my_group
|
||||||
|
Reference in New Issue
Block a user