mirror of
https://github.com/daeuniverse/dae.git
synced 2024-12-23 01:04:40 +07:00
feat: support reject in dns request routing
This commit is contained in:
parent
f2dc750dbb
commit
6657fb329c
@ -10,9 +10,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DnsRequestOutboundIndex uint8
|
type DnsRequestOutboundIndex int16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
DnsRequestOutboundIndex_Reject DnsRequestOutboundIndex = 0xFC
|
||||||
DnsRequestOutboundIndex_AsIs DnsRequestOutboundIndex = 0xFD
|
DnsRequestOutboundIndex_AsIs DnsRequestOutboundIndex = 0xFD
|
||||||
DnsRequestOutboundIndex_LogicalOr DnsRequestOutboundIndex = 0xFE
|
DnsRequestOutboundIndex_LogicalOr DnsRequestOutboundIndex = 0xFE
|
||||||
DnsRequestOutboundIndex_LogicalAnd DnsRequestOutboundIndex = 0xFF
|
DnsRequestOutboundIndex_LogicalAnd DnsRequestOutboundIndex = 0xFF
|
||||||
@ -23,6 +24,8 @@ const (
|
|||||||
|
|
||||||
func (i DnsRequestOutboundIndex) String() string {
|
func (i DnsRequestOutboundIndex) String() string {
|
||||||
switch i {
|
switch i {
|
||||||
|
case DnsRequestOutboundIndex_Reject:
|
||||||
|
return "reject"
|
||||||
case DnsRequestOutboundIndex_AsIs:
|
case DnsRequestOutboundIndex_AsIs:
|
||||||
return "asis"
|
return "asis"
|
||||||
case DnsRequestOutboundIndex_LogicalOr:
|
case DnsRequestOutboundIndex_LogicalOr:
|
||||||
|
@ -142,9 +142,9 @@ func (s *Dns) InitUpstreams() {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Dns) RequestSelect(msg *dnsmessage.Message) (upstream *Upstream, err error) {
|
func (s *Dns) RequestSelect(msg *dnsmessage.Message) (upstreamIndex consts.DnsRequestOutboundIndex, upstream *Upstream, err error) {
|
||||||
if msg.Response {
|
if msg.Response {
|
||||||
return nil, fmt.Errorf("DNS request expected but DNS response received")
|
return 0, nil, fmt.Errorf("DNS request expected but DNS response received")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare routing.
|
// Prepare routing.
|
||||||
@ -159,23 +159,24 @@ func (s *Dns) RequestSelect(msg *dnsmessage.Message) (upstream *Upstream, err er
|
|||||||
qtype = q.Type
|
qtype = q.Type
|
||||||
}
|
}
|
||||||
// Route.
|
// Route.
|
||||||
upstreamIndex, err := s.reqMatcher.Match(qname, qtype)
|
upstreamIndex, err = s.reqMatcher.Match(qname, qtype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
// nil indicates AsIs.
|
// nil indicates AsIs.
|
||||||
if upstreamIndex == consts.DnsRequestOutboundIndex_AsIs {
|
if upstreamIndex == consts.DnsRequestOutboundIndex_AsIs ||
|
||||||
return nil, nil
|
upstreamIndex == consts.DnsRequestOutboundIndex_Reject {
|
||||||
|
return upstreamIndex, nil, nil
|
||||||
}
|
}
|
||||||
if int(upstreamIndex) >= len(s.upstream) {
|
if int(upstreamIndex) >= len(s.upstream) {
|
||||||
return nil, fmt.Errorf("bad upstream index: %v not in [0, %v]", upstreamIndex, len(s.upstream)-1)
|
return 0, nil, fmt.Errorf("bad upstream index: %v not in [0, %v]", upstreamIndex, len(s.upstream)-1)
|
||||||
}
|
}
|
||||||
// Get corresponding upstream.
|
// Get corresponding upstream.
|
||||||
upstream, err = s.upstream[upstreamIndex].GetUpstream()
|
upstream, err = s.upstream[upstreamIndex].GetUpstream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
return upstream, nil
|
return upstreamIndex, upstream, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Dns) ResponseSelect(msg *dnsmessage.Message, fromUpstream *Upstream) (upstreamIndex consts.DnsResponseOutboundIndex, upstream *Upstream, err error) {
|
func (s *Dns) ResponseSelect(msg *dnsmessage.Message, fromUpstream *Upstream) (upstreamIndex consts.DnsResponseOutboundIndex, upstream *Upstream, err error) {
|
||||||
|
@ -43,6 +43,8 @@ func NewRequestMatcherBuilder(log *logrus.Logger, rules []*config_parser.Routing
|
|||||||
|
|
||||||
func (b *RequestMatcherBuilder) upstreamToId(upstream string) (upstreamId consts.DnsRequestOutboundIndex, err error) {
|
func (b *RequestMatcherBuilder) upstreamToId(upstream string) (upstreamId consts.DnsRequestOutboundIndex, err error) {
|
||||||
switch upstream {
|
switch upstream {
|
||||||
|
case consts.DnsRequestOutboundIndex_Reject.String():
|
||||||
|
upstreamId = consts.DnsRequestOutboundIndex_Reject
|
||||||
case consts.DnsRequestOutboundIndex_AsIs.String():
|
case consts.DnsRequestOutboundIndex_AsIs.String():
|
||||||
upstreamId = consts.DnsRequestOutboundIndex_AsIs
|
upstreamId = consts.DnsRequestOutboundIndex_AsIs
|
||||||
case consts.DnsRequestOutboundIndex_LogicalAnd.String():
|
case consts.DnsRequestOutboundIndex_LogicalAnd.String():
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/mohae/deepcopy"
|
"github.com/mohae/deepcopy"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,15 +20,16 @@ type DnsCache struct {
|
|||||||
|
|
||||||
func (c *DnsCache) FillInto(req *dnsmessage.Message) {
|
func (c *DnsCache) FillInto(req *dnsmessage.Message) {
|
||||||
req.Answers = deepcopy.Copy(c.Answers).([]dnsmessage.Resource)
|
req.Answers = deepcopy.Copy(c.Answers).([]dnsmessage.Resource)
|
||||||
// Align question and answer Name.
|
// No need to align because of no flipping now.
|
||||||
if len(req.Questions) > 0 {
|
//// Align question and answer Name.
|
||||||
q := req.Questions[0]
|
//if len(req.Questions) > 0 {
|
||||||
for i := range req.Answers {
|
// q := req.Questions[0]
|
||||||
if strings.EqualFold(req.Answers[i].Header.Name.String(), q.Name.String()) {
|
// for i := range req.Answers {
|
||||||
req.Answers[i].Header.Name.Data = q.Name.Data
|
// if strings.EqualFold(req.Answers[i].Header.Name.String(), q.Name.String()) {
|
||||||
}
|
// req.Answers[i].Header.Name.Data = q.Name.Data
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//}
|
||||||
req.RCode = dnsmessage.RCodeSuccess
|
req.RCode = dnsmessage.RCodeSuccess
|
||||||
req.Response = true
|
req.Response = true
|
||||||
req.RecursionAvailable = true
|
req.RecursionAvailable = true
|
||||||
|
@ -298,6 +298,41 @@ func (c *DnsController) Handle_(dnsMessage *dnsmessage.Message, req *udpRequest)
|
|||||||
RefineSourceToShow(req.realSrc, req.realDst.Addr(), req.lanWanFlag), req.realDst.String(), strings.ToLower(q.Name.String()), q.Type,
|
RefineSourceToShow(req.realSrc, req.realDst.Addr(), req.lanWanFlag), req.realDst.String(), strings.ToLower(q.Name.String()), q.Type,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//// NOTICE: Rush-answer detector was removed because it does not always work in all districts.
|
||||||
|
//// 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)
|
||||||
|
|
||||||
|
// Route request.
|
||||||
|
upstreamIndex, upstream, err := c.routing.RequestSelect(dnsMessage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if upstreamIndex == consts.DnsRequestOutboundIndex_Reject {
|
||||||
|
// Reject with empty answer.
|
||||||
|
dnsMessage.Answers = nil
|
||||||
|
dnsMessage.RCode = dnsmessage.RCodeSuccess
|
||||||
|
dnsMessage.Response = true
|
||||||
|
dnsMessage.RecursionAvailable = true
|
||||||
|
dnsMessage.Truncated = false
|
||||||
|
if c.log.IsLevelEnabled(logrus.TraceLevel) {
|
||||||
|
c.log.WithFields(logrus.Fields{
|
||||||
|
"question": dnsMessage.Questions,
|
||||||
|
}).Traceln("Reject with empty answer")
|
||||||
|
}
|
||||||
|
data, err := dnsMessage.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pack DNS packet: %w", err)
|
||||||
|
}
|
||||||
|
if err = sendPkt(data, req.realDst, req.realSrc, req.src, req.lConn, req.lanWanFlag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if resp := c.LookupDnsRespCache_(dnsMessage); resp != nil {
|
if resp := c.LookupDnsRespCache_(dnsMessage); resp != nil {
|
||||||
// Send cache to client directly.
|
// Send cache to client directly.
|
||||||
if err = sendPkt(resp, req.realDst, req.realSrc, req.src, req.lConn, req.lanWanFlag); err != nil {
|
if err = sendPkt(resp, req.realDst, req.realSrc, req.src, req.lConn, req.lanWanFlag); err != nil {
|
||||||
@ -312,20 +347,8 @@ func (c *DnsController) Handle_(dnsMessage *dnsmessage.Message, req *udpRequest)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//// NOTICE: Rush-answer detector was removed because it does not always work in all districts.
|
|
||||||
//// 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)
|
|
||||||
|
|
||||||
// Route request.
|
|
||||||
upstream, err := c.routing.RequestSelect(dnsMessage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.log.IsLevelEnabled(logrus.TraceLevel) {
|
if c.log.IsLevelEnabled(logrus.TraceLevel) {
|
||||||
upstreamName := "asis"
|
upstreamName := upstreamIndex.String()
|
||||||
if upstream != nil {
|
if upstream != nil {
|
||||||
upstreamName = upstream.String()
|
upstreamName = upstream.String()
|
||||||
}
|
}
|
||||||
@ -402,9 +425,6 @@ func (c *DnsController) dialSend(req *udpRequest, data []byte, id uint16, upstre
|
|||||||
// We should set a connClosed flag to avoid it.
|
// We should set a connClosed flag to avoid it.
|
||||||
var connClosed bool
|
var connClosed bool
|
||||||
var conn netproxy.Conn
|
var conn netproxy.Conn
|
||||||
// TODO: Rewritten domain should not use full-cone (such as VMess Packet Addr).
|
|
||||||
// Maybe we should set up a mapping for UDP: Dialer + Target Domain => Remote Resolved IP.
|
|
||||||
// However, games may not use QUIC for communication, thus we cannot use domain to dial, which is fine.
|
|
||||||
switch dialArgument.l4proto {
|
switch dialArgument.l4proto {
|
||||||
case consts.L4ProtoStr_UDP:
|
case consts.L4ProtoStr_UDP:
|
||||||
// Get udp endpoint.
|
// Get udp endpoint.
|
||||||
|
@ -23,12 +23,13 @@ dns {
|
|||||||
# According to the request of dns query, decide to use which DNS upstream.
|
# According to the request of dns query, decide to use which DNS upstream.
|
||||||
# Match rules from top to bottom.
|
# Match rules from top to bottom.
|
||||||
request {
|
request {
|
||||||
# Built-in upstream in 'request': asis.
|
# Built-in outbounds in 'request': asis, reject.
|
||||||
# You can also use user-defined upstreams.
|
# You can also use user-defined upstreams.
|
||||||
|
|
||||||
# Available functions: qname, qtype.
|
# Available functions: qname, qtype.
|
||||||
|
|
||||||
# DNS request name (omit suffix dot '.').
|
# DNS request name (omit suffix dot '.').
|
||||||
|
qname(geosite:category-ads-all) -> reject
|
||||||
qname(suffix: abc.com, keyword: google) -> googledns
|
qname(suffix: abc.com, keyword: google) -> googledns
|
||||||
qname(full: ok.com, regex: '^yes') -> googledns
|
qname(full: ok.com, regex: '^yes') -> googledns
|
||||||
# DNS request type
|
# DNS request type
|
||||||
@ -41,7 +42,7 @@ dns {
|
|||||||
# According to the response of dns query, decide to accept or re-lookup using another DNS upstream.
|
# According to the response of dns query, decide to accept or re-lookup using another DNS upstream.
|
||||||
# Match rules from top to bottom.
|
# Match rules from top to bottom.
|
||||||
response {
|
response {
|
||||||
# No built-in upstream in 'response'.
|
# Built-in outbounds in 'response': accept, reject.
|
||||||
# You can use user-defined upstreams.
|
# You can use user-defined upstreams.
|
||||||
|
|
||||||
# Available functions: qname, qtype, upstream, ip.
|
# Available functions: qname, qtype, upstream, ip.
|
||||||
|
Loading…
Reference in New Issue
Block a user