feat: support reject in dns request routing

This commit is contained in:
mzz2017 2023-03-31 15:46:53 +08:00
parent f2dc750dbb
commit 6657fb329c
6 changed files with 65 additions and 38 deletions

View File

@ -10,9 +10,10 @@ import (
"strings"
)
type DnsRequestOutboundIndex uint8
type DnsRequestOutboundIndex int16
const (
DnsRequestOutboundIndex_Reject DnsRequestOutboundIndex = 0xFC
DnsRequestOutboundIndex_AsIs DnsRequestOutboundIndex = 0xFD
DnsRequestOutboundIndex_LogicalOr DnsRequestOutboundIndex = 0xFE
DnsRequestOutboundIndex_LogicalAnd DnsRequestOutboundIndex = 0xFF
@ -23,6 +24,8 @@ const (
func (i DnsRequestOutboundIndex) String() string {
switch i {
case DnsRequestOutboundIndex_Reject:
return "reject"
case DnsRequestOutboundIndex_AsIs:
return "asis"
case DnsRequestOutboundIndex_LogicalOr:

View File

@ -142,9 +142,9 @@ func (s *Dns) InitUpstreams() {
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 {
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.
@ -159,23 +159,24 @@ func (s *Dns) RequestSelect(msg *dnsmessage.Message) (upstream *Upstream, err er
qtype = q.Type
}
// Route.
upstreamIndex, err := s.reqMatcher.Match(qname, qtype)
upstreamIndex, err = s.reqMatcher.Match(qname, qtype)
if err != nil {
return nil, err
return 0, nil, err
}
// nil indicates AsIs.
if upstreamIndex == consts.DnsRequestOutboundIndex_AsIs {
return nil, nil
if upstreamIndex == consts.DnsRequestOutboundIndex_AsIs ||
upstreamIndex == consts.DnsRequestOutboundIndex_Reject {
return upstreamIndex, nil, nil
}
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.
upstream, err = s.upstream[upstreamIndex].GetUpstream()
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) {

View File

@ -43,6 +43,8 @@ func NewRequestMatcherBuilder(log *logrus.Logger, rules []*config_parser.Routing
func (b *RequestMatcherBuilder) upstreamToId(upstream string) (upstreamId consts.DnsRequestOutboundIndex, err error) {
switch upstream {
case consts.DnsRequestOutboundIndex_Reject.String():
upstreamId = consts.DnsRequestOutboundIndex_Reject
case consts.DnsRequestOutboundIndex_AsIs.String():
upstreamId = consts.DnsRequestOutboundIndex_AsIs
case consts.DnsRequestOutboundIndex_LogicalAnd.String():

View File

@ -9,7 +9,6 @@ import (
"github.com/mohae/deepcopy"
"golang.org/x/net/dns/dnsmessage"
"net/netip"
"strings"
"time"
)
@ -21,15 +20,16 @@ type DnsCache struct {
func (c *DnsCache) FillInto(req *dnsmessage.Message) {
req.Answers = deepcopy.Copy(c.Answers).([]dnsmessage.Resource)
// Align question and answer Name.
if len(req.Questions) > 0 {
q := req.Questions[0]
for i := range req.Answers {
if strings.EqualFold(req.Answers[i].Header.Name.String(), q.Name.String()) {
req.Answers[i].Header.Name.Data = q.Name.Data
}
}
}
// No need to align because of no flipping now.
//// Align question and answer Name.
//if len(req.Questions) > 0 {
// q := req.Questions[0]
// for i := range req.Answers {
// 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.Response = true
req.RecursionAvailable = true

View File

@ -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,
)
}
//// 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 {
// Send cache to client directly.
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
}
//// 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) {
upstreamName := "asis"
upstreamName := upstreamIndex.String()
if upstream != nil {
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.
var connClosed bool
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 {
case consts.L4ProtoStr_UDP:
// Get udp endpoint.

View File

@ -23,12 +23,13 @@ dns {
# According to the request of dns query, decide to use which DNS upstream.
# Match rules from top to bottom.
request {
# Built-in upstream in 'request': asis.
# Built-in outbounds in 'request': asis, reject.
# You can also use user-defined upstreams.
# Available functions: qname, qtype.
# DNS request name (omit suffix dot '.').
qname(geosite:category-ads-all) -> reject
qname(suffix: abc.com, keyword: google) -> googledns
qname(full: ok.com, regex: '^yes') -> googledns
# 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.
# Match rules from top to bottom.
response {
# No built-in upstream in 'response'.
# Built-in outbounds in 'response': accept, reject.
# You can use user-defined upstreams.
# Available functions: qname, qtype, upstream, ip.