diff --git a/component/control/dns.go b/component/control/dns.go index d11602a..a5859a7 100644 --- a/component/control/dns.go +++ b/component/control/dns.go @@ -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() diff --git a/component/control/udp.go b/component/control/udp.go index ebdc4c6..e896093 100644 --- a/component/control/udp.go +++ b/component/control/udp.go @@ -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), diff --git a/example.dae b/example.dae index e35c0c7..728d973 100644 --- a/example.dae +++ b/example.dae @@ -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