mirror of
https://github.com/daeuniverse/dae.git
synced 2025-02-22 12:38:34 +07:00
feat: add DNS rush-answer filter
This commit is contained in:
parent
dcf8021500
commit
5118a80bca
@ -7,6 +7,7 @@ package control
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/mohae/deepcopy"
|
||||
@ -21,6 +22,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
SuspectedRushAnswerError = fmt.Errorf("suspected DNS rush-answer")
|
||||
NotAdapableQuestionTypeError = fmt.Errorf("not adaptable question type")
|
||||
)
|
||||
|
||||
type dnsCache struct {
|
||||
DomainBitmap [consts.MaxMatchSetLen / 32]uint32
|
||||
Answers []dnsmessage.Resource
|
||||
@ -135,23 +141,79 @@ func FlipDnsQuestionCase(dm *dnsmessage.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureAdditionalOpt makes sure there is additional record OPT in the request.
|
||||
func EnsureAdditionalOpt(dm *dnsmessage.Message, isReqAdd bool) (bool, error) {
|
||||
// Check healthy resp.
|
||||
if isReqAdd == dm.Response || dm.RCode != dnsmessage.RCodeSuccess || len(dm.Questions) == 0 {
|
||||
return false, NotAdapableQuestionTypeError
|
||||
}
|
||||
q := dm.Questions[0]
|
||||
switch q.Type {
|
||||
case dnsmessage.TypeA, dnsmessage.TypeAAAA:
|
||||
default:
|
||||
return false, NotAdapableQuestionTypeError
|
||||
}
|
||||
|
||||
for _, ad := range dm.Additionals {
|
||||
if ad.Header.Type == dnsmessage.TypeOPT {
|
||||
// Already has additional record OPT.
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
if !isReqAdd {
|
||||
return false, nil
|
||||
}
|
||||
// Add one.
|
||||
dm.Additionals = append(dm.Additionals, dnsmessage.Resource{
|
||||
Header: dnsmessage.ResourceHeader{
|
||||
Name: dnsmessage.MustNewName("."),
|
||||
Type: dnsmessage.TypeOPT,
|
||||
Class: 512, TTL: 0, Length: 0,
|
||||
},
|
||||
Body: &dnsmessage.OPTResource{
|
||||
Options: nil,
|
||||
},
|
||||
})
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
exist, e := EnsureAdditionalOpt(&msg, false)
|
||||
if e != nil && !errors.Is(e, NotAdapableQuestionTypeError) {
|
||||
c.log.Warnf("EnsureAdditionalOpt: %v", e)
|
||||
}
|
||||
if e == nil && !exist {
|
||||
// Additional record OPT in the request was ensured, and in normal case the resp should also set it.
|
||||
// This DNS packet may be a rush-answer, and we should reject it.
|
||||
// Note that additional record OPT may not be supported by home router either.
|
||||
err = SuspectedRushAnswerError
|
||||
}
|
||||
}
|
||||
}()
|
||||
FlipDnsQuestionCase(&msg)
|
||||
// Check healthy resp.
|
||||
if !msg.Response || msg.RCode != dnsmessage.RCodeSuccess || len(msg.Questions) == 0 {
|
||||
return data, nil
|
||||
}
|
||||
q := msg.Questions[0]
|
||||
// Align question and answer Name.
|
||||
// Align Name.
|
||||
if len(msg.Answers) > 0 &&
|
||||
strings.EqualFold(msg.Answers[0].Header.Name.String(), q.Name.String()) {
|
||||
msg.Answers[0].Header.Name.Data = q.Name.Data
|
||||
}
|
||||
for i := range msg.Additionals {
|
||||
if strings.EqualFold(msg.Additionals[i].Header.Name.String(), q.Name.String()) {
|
||||
msg.Additionals[i].Header.Name.Data = q.Name.Data
|
||||
}
|
||||
}
|
||||
|
||||
// Check req type.
|
||||
switch q.Type {
|
||||
case dnsmessage.TypeA, dnsmessage.TypeAAAA:
|
||||
|
@ -7,6 +7,7 @@ package control
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mzz2017/softwind/pool"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -84,9 +85,21 @@ func (c *ControlPlane) RelayToUDP(lConn *net.UDPConn, to netip.AddrPort, isDNS b
|
||||
if isDNS {
|
||||
data, err = c.DnsRespHandler(data)
|
||||
if err != nil {
|
||||
if errors.Is(err, SuspectedRushAnswerError) {
|
||||
if from.Addr().IsPrivate() {
|
||||
// Additional record OPT may not be supported by home router.
|
||||
// And we should trust home devices even if they make rush-answer.
|
||||
c.log.Tracef("DnsRespHandler: received %v", err)
|
||||
err = nil
|
||||
goto sendToClient
|
||||
}
|
||||
// Reject DNS rush-answer.
|
||||
return err
|
||||
}
|
||||
c.log.Debugf("DnsRespHandler: %v", err)
|
||||
}
|
||||
}
|
||||
sendToClient:
|
||||
if dummyFrom != nil {
|
||||
from = *dummyFrom
|
||||
}
|
||||
@ -117,6 +130,7 @@ func (c *ControlPlane) handlePkt(data []byte, lConn *net.UDPConn, lAddrPort neti
|
||||
dest := addrHdr.Dest
|
||||
if isDns {
|
||||
if resp := c.LookupDnsRespCache(dnsMessage); resp != nil {
|
||||
// Send cache to client directly.
|
||||
if err = sendPktWithHdr(resp, dest, lConn, lAddrPort); err != nil {
|
||||
return fmt.Errorf("failed to write cached DNS resp: %w", err)
|
||||
}
|
||||
@ -127,18 +141,25 @@ func (c *ControlPlane) handlePkt(data []byte, lConn *net.UDPConn, lAddrPort neti
|
||||
)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
c.log.Tracef("Modify dns target %v to upstream: %v", RefineAddrPortToShow(dest), c.dnsUpstream)
|
||||
// Modify dns target to upstream.
|
||||
// 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)
|
||||
}
|
||||
// Need to make a DNS request.
|
||||
c.log.Tracef("Modify dns target %v to upstream: %v", RefineAddrPortToShow(dest), c.dnsUpstream)
|
||||
// Modify dns target to upstream.
|
||||
// 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)
|
||||
// Make sure there is additional record OPT in the request to filter DNS rush-answer in the response process.
|
||||
// Because rush-answer has no resp OPT. We can distinguish them from multiple responses.
|
||||
// Note that additional record OPT may not be supported by home router either.
|
||||
_, _ = EnsureAdditionalOpt(dnsMessage, true)
|
||||
|
||||
// Re-pack DNS packet.
|
||||
if data, err = dnsMessage.Pack(); err != nil {
|
||||
return fmt.Errorf("pack flipped dns packet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mzz2017/softwind/pool"
|
||||
"github.com/v2rayA/dae/component/outbound/dialer"
|
||||
@ -40,6 +41,9 @@ func (ue *UdpEndpoint) start() {
|
||||
ue.deadlineTimer.Reset(ue.NatTimeout)
|
||||
ue.mu.Unlock()
|
||||
if err = ue.handler(buf[:n], from.(*net.UDPAddr).AddrPort()); err != nil {
|
||||
if errors.Is(err, SuspectedRushAnswerError) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ global {
|
||||
# lan_interface: docker0
|
||||
|
||||
# The WAN interface to bind. Use it if you want to proxy localhost
|
||||
# Multiple interfaces split by ","
|
||||
# Multiple interfaces split by ",".
|
||||
wan_interface: wlp5s0
|
||||
}
|
||||
|
||||
@ -63,7 +63,8 @@ group {
|
||||
routing {
|
||||
# See routing.md for full examples.
|
||||
|
||||
ip(8.8.8.8, 1.1.1.1) && port(53) -> my_group
|
||||
# dae arms DNS rush-answer filter so we can use 8.8.8.8 regardless of DNS pollution.
|
||||
ip(8.8.8.8) && port(53) -> direct
|
||||
|
||||
pname(firefox) && domain(ip.sb) -> direct
|
||||
pname(curl) && domain(ip.sb) -> my_group
|
||||
|
Loading…
Reference in New Issue
Block a user