all: sync with master
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
@@ -162,10 +163,10 @@ type TLSConfig struct {
|
||||
|
||||
// DNSCryptConfig is the DNSCrypt server configuration struct.
|
||||
type DNSCryptConfig struct {
|
||||
ResolverCert *dnscrypt.Cert
|
||||
ProviderName string
|
||||
UDPListenAddrs []*net.UDPAddr
|
||||
TCPListenAddrs []*net.TCPAddr
|
||||
ProviderName string
|
||||
ResolverCert *dnscrypt.Cert
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
@@ -213,68 +214,67 @@ var defaultValues = ServerConfig{
|
||||
FilteringConfig: FilteringConfig{BlockedResponseTTL: 3600},
|
||||
}
|
||||
|
||||
// createProxyConfig creates and validates configuration for the main proxy
|
||||
func (s *Server) createProxyConfig() (proxy.Config, error) {
|
||||
proxyConfig := proxy.Config{
|
||||
UDPListenAddr: s.conf.UDPListenAddrs,
|
||||
TCPListenAddr: s.conf.TCPListenAddrs,
|
||||
Ratelimit: int(s.conf.Ratelimit),
|
||||
RatelimitWhitelist: s.conf.RatelimitWhitelist,
|
||||
RefuseAny: s.conf.RefuseAny,
|
||||
TrustedProxies: s.conf.TrustedProxies,
|
||||
CacheMinTTL: s.conf.CacheMinTTL,
|
||||
CacheMaxTTL: s.conf.CacheMaxTTL,
|
||||
CacheOptimistic: s.conf.CacheOptimistic,
|
||||
UpstreamConfig: s.conf.UpstreamConfig,
|
||||
// createProxyConfig creates and validates configuration for the main proxy.
|
||||
func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
|
||||
srvConf := s.conf
|
||||
conf = proxy.Config{
|
||||
UDPListenAddr: srvConf.UDPListenAddrs,
|
||||
TCPListenAddr: srvConf.TCPListenAddrs,
|
||||
Ratelimit: int(srvConf.Ratelimit),
|
||||
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
||||
RefuseAny: srvConf.RefuseAny,
|
||||
TrustedProxies: srvConf.TrustedProxies,
|
||||
CacheMinTTL: srvConf.CacheMinTTL,
|
||||
CacheMaxTTL: srvConf.CacheMaxTTL,
|
||||
CacheOptimistic: srvConf.CacheOptimistic,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
BeforeRequestHandler: s.beforeRequestHandler,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
EnableEDNSClientSubnet: s.conf.EnableEDNSClientSubnet,
|
||||
MaxGoroutines: int(s.conf.MaxGoroutines),
|
||||
EnableEDNSClientSubnet: srvConf.EnableEDNSClientSubnet,
|
||||
MaxGoroutines: int(srvConf.MaxGoroutines),
|
||||
}
|
||||
|
||||
if s.conf.CacheSize != 0 {
|
||||
proxyConfig.CacheEnabled = true
|
||||
proxyConfig.CacheSizeBytes = int(s.conf.CacheSize)
|
||||
if srvConf.CacheSize != 0 {
|
||||
conf.CacheEnabled = true
|
||||
conf.CacheSizeBytes = int(srvConf.CacheSize)
|
||||
}
|
||||
|
||||
proxyConfig.UpstreamMode = proxy.UModeLoadBalance
|
||||
if s.conf.AllServers {
|
||||
proxyConfig.UpstreamMode = proxy.UModeParallel
|
||||
} else if s.conf.FastestAddr {
|
||||
proxyConfig.UpstreamMode = proxy.UModeFastestAddr
|
||||
proxyConfig.FastestPingTimeout = s.conf.FastestTimeout.Duration
|
||||
}
|
||||
setProxyUpstreamMode(
|
||||
&conf,
|
||||
srvConf.AllServers,
|
||||
srvConf.FastestAddr,
|
||||
srvConf.FastestTimeout.Duration,
|
||||
)
|
||||
|
||||
for i, s := range s.conf.BogusNXDomain {
|
||||
subnet, err := netutil.ParseSubnet(s)
|
||||
for i, s := range srvConf.BogusNXDomain {
|
||||
var subnet *net.IPNet
|
||||
subnet, err = netutil.ParseSubnet(s)
|
||||
if err != nil {
|
||||
log.Error("subnet at index %d: %s", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
proxyConfig.BogusNXDomain = append(proxyConfig.BogusNXDomain, subnet)
|
||||
conf.BogusNXDomain = append(conf.BogusNXDomain, subnet)
|
||||
}
|
||||
|
||||
// TLS settings
|
||||
err := s.prepareTLS(&proxyConfig)
|
||||
err = s.prepareTLS(&conf)
|
||||
if err != nil {
|
||||
return proxyConfig, err
|
||||
return conf, fmt.Errorf("validating tls: %w", err)
|
||||
}
|
||||
|
||||
if s.conf.DNSCryptConfig.Enabled {
|
||||
proxyConfig.DNSCryptUDPListenAddr = s.conf.DNSCryptConfig.UDPListenAddrs
|
||||
proxyConfig.DNSCryptTCPListenAddr = s.conf.DNSCryptConfig.TCPListenAddrs
|
||||
proxyConfig.DNSCryptProviderName = s.conf.DNSCryptConfig.ProviderName
|
||||
proxyConfig.DNSCryptResolverCert = s.conf.DNSCryptConfig.ResolverCert
|
||||
if c := srvConf.DNSCryptConfig; c.Enabled {
|
||||
conf.DNSCryptUDPListenAddr = c.UDPListenAddrs
|
||||
conf.DNSCryptTCPListenAddr = c.TCPListenAddrs
|
||||
conf.DNSCryptProviderName = c.ProviderName
|
||||
conf.DNSCryptResolverCert = c.ResolverCert
|
||||
}
|
||||
|
||||
// Validate proxy config
|
||||
if proxyConfig.UpstreamConfig == nil || len(proxyConfig.UpstreamConfig.Upstreams) == 0 {
|
||||
return proxyConfig, errors.Error("no default upstream servers configured")
|
||||
if conf.UpstreamConfig == nil || len(conf.UpstreamConfig.Upstreams) == 0 {
|
||||
return conf, errors.Error("no default upstream servers configured")
|
||||
}
|
||||
|
||||
return proxyConfig, nil
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -337,7 +337,7 @@ func (s *Server) prepareUpstreamSettings() error {
|
||||
if s.conf.UpstreamDNSFileName != "" {
|
||||
data, err := os.ReadFile(s.conf.UpstreamDNSFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading upstream from file: %w", err)
|
||||
}
|
||||
|
||||
upstreams = stringutil.SplitTrimmed(string(data), "\n")
|
||||
@@ -356,7 +356,7 @@ func (s *Server) prepareUpstreamSettings() error {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dns: proxy.ParseUpstreamsConfig: %w", err)
|
||||
return fmt.Errorf("parsing upstream config: %w", err)
|
||||
}
|
||||
|
||||
if len(upstreamConfig.Upstreams) == 0 {
|
||||
@@ -370,8 +370,9 @@ func (s *Server) prepareUpstreamSettings() error {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dns: failed to parse default upstreams: %v", err)
|
||||
return fmt.Errorf("parsing default upstreams: %w", err)
|
||||
}
|
||||
|
||||
upstreamConfig.Upstreams = uc.Upstreams
|
||||
}
|
||||
|
||||
@@ -380,14 +381,21 @@ func (s *Server) prepareUpstreamSettings() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareIntlProxy - initializes DNS proxy that we use for internal DNS queries
|
||||
func (s *Server) prepareIntlProxy() {
|
||||
s.internalProxy = &proxy.Proxy{
|
||||
Config: proxy.Config{
|
||||
CacheEnabled: true,
|
||||
CacheSizeBytes: 4096,
|
||||
UpstreamConfig: s.conf.UpstreamConfig,
|
||||
},
|
||||
// setProxyUpstreamMode sets the upstream mode and related settings in conf
|
||||
// based on provided parameters.
|
||||
func setProxyUpstreamMode(
|
||||
conf *proxy.Config,
|
||||
allServers bool,
|
||||
fastestAddr bool,
|
||||
fastestTimeout time.Duration,
|
||||
) {
|
||||
if allServers {
|
||||
conf.UpstreamMode = proxy.UModeParallel
|
||||
} else if fastestAddr {
|
||||
conf.UpstreamMode = proxy.UModeFastestAddr
|
||||
conf.FastestPingTimeout = fastestTimeout
|
||||
} else {
|
||||
conf.UpstreamMode = proxy.UModeLoadBalance
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,13 +409,15 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.conf.TLSListenAddrs != nil {
|
||||
proxyConfig.TLSListenAddr = s.conf.TLSListenAddrs
|
||||
}
|
||||
proxyConfig.TLSListenAddr = aghalg.CoalesceSlice(
|
||||
s.conf.TLSListenAddrs,
|
||||
proxyConfig.TLSListenAddr,
|
||||
)
|
||||
|
||||
if s.conf.QUICListenAddrs != nil {
|
||||
proxyConfig.QUICListenAddr = s.conf.QUICListenAddrs
|
||||
}
|
||||
proxyConfig.QUICListenAddr = aghalg.CoalesceSlice(
|
||||
s.conf.QUICListenAddrs,
|
||||
proxyConfig.QUICListenAddr,
|
||||
)
|
||||
|
||||
var err error
|
||||
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
||||
|
||||
@@ -81,9 +81,9 @@ const (
|
||||
const ddrHostFQDN = "_dns.resolver.arpa."
|
||||
|
||||
// handleDNSRequest filters the incoming DNS requests and writes them to the query log
|
||||
func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
ctx := &dnsContext{
|
||||
proxyCtx: d,
|
||||
func (s *Server) handleDNSRequest(_ *proxy.Proxy, pctx *proxy.DNSContext) error {
|
||||
dctx := &dnsContext{
|
||||
proxyCtx: pctx,
|
||||
result: &filtering.Result{},
|
||||
startTime: time.Now(),
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
s.processQueryLogsAndStats,
|
||||
}
|
||||
for _, process := range mods {
|
||||
r := process(ctx)
|
||||
r := process(dctx)
|
||||
switch r {
|
||||
case resultCodeSuccess:
|
||||
// continue: call the next filter
|
||||
@@ -120,13 +120,15 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
return nil
|
||||
|
||||
case resultCodeError:
|
||||
return ctx.err
|
||||
return dctx.err
|
||||
}
|
||||
}
|
||||
|
||||
if d.Res != nil {
|
||||
d.Res.Compress = true // some devices require DNS message compression
|
||||
if pctx.Res != nil {
|
||||
// Some devices require DNS message compression.
|
||||
pctx.Res.Compress = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -149,34 +151,38 @@ func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
|
||||
// needed and enriches the ctx with some client-specific information.
|
||||
//
|
||||
// TODO(e.burkov): Decompose into less general processors.
|
||||
func (s *Server) processInitial(ctx *dnsContext) (rc resultCode) {
|
||||
d := ctx.proxyCtx
|
||||
if s.conf.AAAADisabled && d.Req.Question[0].Qtype == dns.TypeAAAA {
|
||||
_ = proxy.CheckDisabledAAAARequest(d, true)
|
||||
func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
|
||||
pctx := dctx.proxyCtx
|
||||
q := pctx.Req.Question[0]
|
||||
qt := q.Qtype
|
||||
if s.conf.AAAADisabled && qt == dns.TypeAAAA {
|
||||
_ = proxy.CheckDisabledAAAARequest(pctx, true)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
if s.conf.OnDNSRequest != nil {
|
||||
s.conf.OnDNSRequest(d)
|
||||
s.conf.OnDNSRequest(pctx)
|
||||
}
|
||||
|
||||
// disable Mozilla DoH
|
||||
// https://support.mozilla.org/en-US/kb/canary-domain-use-application-dnsnet
|
||||
if (d.Req.Question[0].Qtype == dns.TypeA || d.Req.Question[0].Qtype == dns.TypeAAAA) &&
|
||||
d.Req.Question[0].Name == "use-application-dns.net." {
|
||||
d.Res = s.genNXDomain(d.Req)
|
||||
// Disable Mozilla DoH.
|
||||
//
|
||||
// See https://support.mozilla.org/en-US/kb/canary-domain-use-application-dnsnet.
|
||||
if (qt == dns.TypeA || qt == dns.TypeAAAA) && q.Name == "use-application-dns.net." {
|
||||
pctx.Res = s.genNXDomain(pctx.Req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
// Get the client's ID if any. It should be performed before getting
|
||||
// client-specific filtering settings.
|
||||
// Get the ClientID, if any, before getting client-specific filtering
|
||||
// settings.
|
||||
var key [8]byte
|
||||
binary.BigEndian.PutUint64(key[:], d.RequestID)
|
||||
ctx.clientID = string(s.clientIDCache.Get(key[:]))
|
||||
binary.BigEndian.PutUint64(key[:], pctx.RequestID)
|
||||
dctx.clientID = string(s.clientIDCache.Get(key[:]))
|
||||
|
||||
// Get the client-specific filtering settings.
|
||||
ctx.protectionEnabled = s.conf.ProtectionEnabled
|
||||
ctx.setts = s.getClientRequestFilteringSettings(ctx)
|
||||
dctx.protectionEnabled = s.conf.ProtectionEnabled
|
||||
dctx.setts = s.getClientRequestFilteringSettings(dctx)
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
@@ -244,28 +250,28 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||
s.setTableIPToHost(ipToHost)
|
||||
}
|
||||
|
||||
// processDDRQuery responds to SVCB query for a special use domain name
|
||||
// ‘_dns.resolver.arpa’. The result contains different types of encryption
|
||||
// supported by current user configuration.
|
||||
// processDDRQuery responds to Discovery of Designated Resolvers (DDR) SVCB
|
||||
// queries. The response contains different types of encryption supported by
|
||||
// current user configuration.
|
||||
//
|
||||
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html.
|
||||
func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
|
||||
d := ctx.proxyCtx
|
||||
question := d.Req.Question[0]
|
||||
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
|
||||
func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
|
||||
pctx := dctx.proxyCtx
|
||||
q := pctx.Req.Question[0]
|
||||
|
||||
if !s.conf.HandleDDR {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
if question.Name == ddrHostFQDN {
|
||||
if q.Name == ddrHostFQDN {
|
||||
if s.dnsProxy.TLSListenAddr == nil && s.conf.HTTPSListenAddrs == nil &&
|
||||
s.dnsProxy.QUICListenAddr == nil || question.Qtype != dns.TypeSVCB {
|
||||
d.Res = s.makeResponse(d.Req)
|
||||
s.dnsProxy.QUICListenAddr == nil || q.Qtype != dns.TypeSVCB {
|
||||
pctx.Res = s.makeResponse(pctx.Req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
d.Res = s.makeDDRResponse(d.Req)
|
||||
pctx.Res = s.makeDDRResponse(pctx.Req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
@@ -273,11 +279,13 @@ func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// makeDDRResponse creates DDR answer according to server configuration. The
|
||||
// contructed SVCB resource records have the priority of 1 for each entry,
|
||||
// similar to examples provided by https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html.
|
||||
// makeDDRResponse creates a DDR answer based on the server configuration. The
|
||||
// constructed SVCB resource records have the priority of 1 for each entry,
|
||||
// similar to examples provided by the [draft standard].
|
||||
//
|
||||
// TODO(a.meshkov): Consider setting the priority values based on the protocol.
|
||||
//
|
||||
// [draft standard]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
|
||||
func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||
resp = s.makeResponse(req)
|
||||
// TODO(e.burkov): Think about storing the FQDN version of the server's
|
||||
@@ -351,10 +359,10 @@ func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
|
||||
return rc
|
||||
}
|
||||
|
||||
// hostToIP tries to get an IP leased by DHCP and returns the copy of address
|
||||
// since the data inside the internal table may be changed while request
|
||||
// dhcpHostToIP tries to get an IP leased by DHCP and returns the copy of
|
||||
// address since the data inside the internal table may be changed while request
|
||||
// processing. It's safe for concurrent use.
|
||||
func (s *Server) hostToIP(host string) (ip net.IP, ok bool) {
|
||||
func (s *Server) dhcpHostToIP(host string) (ip net.IP, ok bool) {
|
||||
s.tableHostToIPLock.Lock()
|
||||
defer s.tableHostToIPLock.Unlock()
|
||||
|
||||
@@ -379,46 +387,32 @@ func (s *Server) hostToIP(host string) (ip net.IP, ok bool) {
|
||||
//
|
||||
// TODO(a.garipov): Adapt to AAAA as well.
|
||||
func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
|
||||
if !s.dhcpServer.Enabled() {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
req := dctx.proxyCtx.Req
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
|
||||
// Go on processing the AAAA request despite the fact that we don't support
|
||||
// it yet. The expected behavior here is to respond with an empty answer
|
||||
// and not NXDOMAIN.
|
||||
if q.Qtype != dns.TypeA && q.Qtype != dns.TypeAAAA {
|
||||
reqHost, ok := s.isDHCPClientHostQ(q)
|
||||
if !ok {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
reqHost := strings.ToLower(q.Name[:len(q.Name)-1])
|
||||
// TODO(a.garipov): Move everything related to DHCP local domain to the DHCP
|
||||
// server.
|
||||
if !strings.HasSuffix(reqHost, s.localDomainSuffix) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
d := dctx.proxyCtx
|
||||
if !dctx.isLocalClient {
|
||||
log.Debug("dns: %q requests for internal host", d.Addr)
|
||||
d.Res = s.genNXDomain(req)
|
||||
log.Debug("dns: %q requests for dhcp host %q", pctx.Addr, reqHost)
|
||||
pctx.Res = s.genNXDomain(req)
|
||||
|
||||
// Do not even put into query log.
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
ip, ok := s.hostToIP(reqHost)
|
||||
ip, ok := s.dhcpHostToIP(reqHost)
|
||||
if !ok {
|
||||
// TODO(e.burkov): Inspect special cases when user want to apply some
|
||||
// rules handled by other processors to the hosts with TLD.
|
||||
d.Res = s.genNXDomain(req)
|
||||
// Go on and process them with filters, including dnsrewrite ones, and
|
||||
// possibly route them to a domain-specific upstream.
|
||||
log.Debug("dns: no dhcp record for %q", reqHost)
|
||||
|
||||
return resultCodeFinish
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
log.Debug("dns: internal record: %s -> %s", q.Name, ip)
|
||||
log.Debug("dns: dhcp record for %q is %s", reqHost, ip)
|
||||
|
||||
resp := s.makeResponse(req)
|
||||
if q.Qtype == dns.TypeA {
|
||||
@@ -435,9 +429,9 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
|
||||
|
||||
// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
|
||||
// in locally-served network from external clients.
|
||||
func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
||||
d := ctx.proxyCtx
|
||||
req := d.Req
|
||||
func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
if q.Qtype != dns.TypePTR {
|
||||
// No need for restriction.
|
||||
@@ -473,32 +467,32 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
if !ctx.isLocalClient {
|
||||
log.Debug("dns: %q requests an internal ip", d.Addr)
|
||||
d.Res = s.genNXDomain(req)
|
||||
if !dctx.isLocalClient {
|
||||
log.Debug("dns: %q requests an internal ip", pctx.Addr)
|
||||
pctx.Res = s.genNXDomain(req)
|
||||
|
||||
// Do not even put into query log.
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
// Do not perform unreversing ever again.
|
||||
ctx.unreversedReqIP = ip
|
||||
dctx.unreversedReqIP = ip
|
||||
|
||||
// There is no need to filter request from external addresses since this
|
||||
// code is only executed when the request is for locally-served ARPA
|
||||
// hostname so disable redundant filters.
|
||||
ctx.setts.ParentalEnabled = false
|
||||
ctx.setts.SafeBrowsingEnabled = false
|
||||
ctx.setts.SafeSearchEnabled = false
|
||||
ctx.setts.ServicesRules = nil
|
||||
dctx.setts.ParentalEnabled = false
|
||||
dctx.setts.SafeBrowsingEnabled = false
|
||||
dctx.setts.SafeSearchEnabled = false
|
||||
dctx.setts.ServicesRules = nil
|
||||
|
||||
// Nothing to restrict.
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// ipToHost tries to get a hostname leased by DHCP. It's safe for concurrent
|
||||
// use.
|
||||
func (s *Server) ipToHost(ip net.IP) (host string, ok bool) {
|
||||
// ipToDHCPHost tries to get a hostname leased by DHCP. It's safe for
|
||||
// concurrent use.
|
||||
func (s *Server) ipToDHCPHost(ip net.IP) (host string, ok bool) {
|
||||
s.tableIPToHostLock.Lock()
|
||||
defer s.tableIPToHostLock.Unlock()
|
||||
|
||||
@@ -521,27 +515,27 @@ func (s *Server) ipToHost(ip net.IP) (host string, ok bool) {
|
||||
return host, true
|
||||
}
|
||||
|
||||
// Respond to PTR requests if the target IP is leased by our DHCP server and the
|
||||
// requestor is inside the local network.
|
||||
func (s *Server) processDHCPAddrs(ctx *dnsContext) (rc resultCode) {
|
||||
d := ctx.proxyCtx
|
||||
if d.Res != nil {
|
||||
// processDHCPAddrs responds to PTR requests if the target IP is leased by the
|
||||
// DHCP server.
|
||||
func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
|
||||
pctx := dctx.proxyCtx
|
||||
if pctx.Res != nil {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
ip := ctx.unreversedReqIP
|
||||
ip := dctx.unreversedReqIP
|
||||
if ip == nil {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
host, ok := s.ipToHost(ip)
|
||||
host, ok := s.ipToDHCPHost(ip)
|
||||
if !ok {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
log.Debug("dns: reverse-lookup: %s -> %s", ip, host)
|
||||
log.Debug("dns: dhcp reverse record for %s is %q", ip, host)
|
||||
|
||||
req := d.Req
|
||||
req := pctx.Req
|
||||
resp := s.makeResponse(req)
|
||||
ptr := &dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
@@ -553,20 +547,20 @@ func (s *Server) processDHCPAddrs(ctx *dnsContext) (rc resultCode) {
|
||||
Ptr: dns.Fqdn(host),
|
||||
}
|
||||
resp.Answer = append(resp.Answer, ptr)
|
||||
d.Res = resp
|
||||
pctx.Res = resp
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// processLocalPTR responds to PTR requests if the target IP is detected to be
|
||||
// inside the local network and the query was not answered from DHCP.
|
||||
func (s *Server) processLocalPTR(ctx *dnsContext) (rc resultCode) {
|
||||
d := ctx.proxyCtx
|
||||
if d.Res != nil {
|
||||
func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
|
||||
pctx := dctx.proxyCtx
|
||||
if pctx.Res != nil {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
ip := ctx.unreversedReqIP
|
||||
ip := dctx.unreversedReqIP
|
||||
if ip == nil {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
@@ -579,16 +573,16 @@ func (s *Server) processLocalPTR(ctx *dnsContext) (rc resultCode) {
|
||||
}
|
||||
|
||||
if s.conf.UsePrivateRDNS {
|
||||
s.recDetector.add(*d.Req)
|
||||
if err := s.localResolvers.Resolve(d); err != nil {
|
||||
ctx.err = err
|
||||
s.recDetector.add(*pctx.Req)
|
||||
if err := s.localResolvers.Resolve(pctx); err != nil {
|
||||
dctx.err = err
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
}
|
||||
|
||||
if d.Res == nil {
|
||||
d.Res = s.genNXDomain(d.Req)
|
||||
if pctx.Res == nil {
|
||||
pctx.Res = s.genNXDomain(pctx.Req)
|
||||
|
||||
// Do not even put into query log.
|
||||
return resultCodeFinish
|
||||
@@ -633,24 +627,25 @@ func ipStringFromAddr(addr net.Addr) (ipStr string) {
|
||||
// processUpstream passes request to upstream servers and handles the response.
|
||||
func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
if pctx.Res != nil {
|
||||
// The response has already been set.
|
||||
return resultCodeSuccess
|
||||
} else if reqHost, ok := s.isDHCPClientHostQ(q); ok {
|
||||
// A DHCP client hostname query that hasn't been handled or filtered.
|
||||
// Respond with an NXDOMAIN.
|
||||
//
|
||||
// TODO(a.garipov): Route such queries to a custom upstream for the
|
||||
// local domain name if there is one.
|
||||
log.Debug("dns: dhcp client hostname %q was not filtered", reqHost)
|
||||
pctx.Res = s.genNXDomain(req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
if pctx.Addr != nil && s.conf.GetCustomUpstreamByClient != nil {
|
||||
// Use the ClientID first, since it has a higher priority.
|
||||
id := stringutil.Coalesce(dctx.clientID, ipStringFromAddr(pctx.Addr))
|
||||
upsConf, err := s.conf.GetCustomUpstreamByClient(id)
|
||||
if err != nil {
|
||||
log.Error("dns: getting custom upstreams for client %s: %s", id, err)
|
||||
} else if upsConf != nil {
|
||||
log.Debug("dns: using custom upstreams for client %s", id)
|
||||
pctx.CustomUpstreamConfig = upsConf
|
||||
}
|
||||
}
|
||||
s.setCustomUpstream(pctx, dctx.clientID)
|
||||
|
||||
req := pctx.Req
|
||||
origReqAD := false
|
||||
if s.conf.EnableDNSSEC {
|
||||
if req.AuthenticatedData {
|
||||
@@ -683,52 +678,100 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// Apply filtering logic after we have received response from upstream servers
|
||||
func (s *Server) processFilteringAfterResponse(ctx *dnsContext) (rc resultCode) {
|
||||
d := ctx.proxyCtx
|
||||
// isDHCPClientHostQ returns true if q is from a request for a DHCP client
|
||||
// hostname. If ok is true, reqHost contains the requested hostname.
|
||||
func (s *Server) isDHCPClientHostQ(q dns.Question) (reqHost string, ok bool) {
|
||||
if !s.dhcpServer.Enabled() {
|
||||
return "", false
|
||||
}
|
||||
|
||||
switch res := ctx.result; res.Reason {
|
||||
// Include AAAA here, because despite the fact that we don't support it yet,
|
||||
// the expected behavior here is to respond with an empty answer and not
|
||||
// NXDOMAIN.
|
||||
if qt := q.Qtype; qt != dns.TypeA && qt != dns.TypeAAAA {
|
||||
return "", false
|
||||
}
|
||||
|
||||
reqHost = strings.ToLower(q.Name[:len(q.Name)-1])
|
||||
if strings.HasSuffix(reqHost, s.localDomainSuffix) {
|
||||
return reqHost, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// setCustomUpstream sets custom upstream settings in pctx, if necessary.
|
||||
func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
|
||||
customUpsByClient := s.conf.GetCustomUpstreamByClient
|
||||
if pctx.Addr == nil || customUpsByClient == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Use the ClientID first, since it has a higher priority.
|
||||
id := stringutil.Coalesce(clientID, ipStringFromAddr(pctx.Addr))
|
||||
upsConf, err := customUpsByClient(id)
|
||||
if err != nil {
|
||||
log.Error("dns: getting custom upstreams for client %s: %s", id, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if upsConf != nil {
|
||||
log.Debug("dns: using custom upstreams for client %s", id)
|
||||
}
|
||||
|
||||
pctx.CustomUpstreamConfig = upsConf
|
||||
}
|
||||
|
||||
// Apply filtering logic after we have received response from upstream servers
|
||||
func (s *Server) processFilteringAfterResponse(dctx *dnsContext) (rc resultCode) {
|
||||
pctx := dctx.proxyCtx
|
||||
switch res := dctx.result; res.Reason {
|
||||
case filtering.NotFilteredAllowList:
|
||||
// Go on.
|
||||
return resultCodeSuccess
|
||||
case
|
||||
filtering.Rewritten,
|
||||
filtering.RewrittenRule:
|
||||
|
||||
if len(ctx.origQuestion.Name) == 0 {
|
||||
if dctx.origQuestion.Name == "" {
|
||||
// origQuestion is set in case we get only CNAME without IP from
|
||||
// rewrites table.
|
||||
break
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
d.Req.Question[0], d.Res.Question[0] = ctx.origQuestion, ctx.origQuestion
|
||||
if len(d.Res.Answer) > 0 {
|
||||
answer := append([]dns.RR{s.genAnswerCNAME(d.Req, res.CanonName)}, d.Res.Answer...)
|
||||
d.Res.Answer = answer
|
||||
pctx.Req.Question[0], pctx.Res.Question[0] = dctx.origQuestion, dctx.origQuestion
|
||||
if len(pctx.Res.Answer) > 0 {
|
||||
rr := s.genAnswerCNAME(pctx.Req, res.CanonName)
|
||||
answer := append([]dns.RR{rr}, pctx.Res.Answer...)
|
||||
pctx.Res.Answer = answer
|
||||
}
|
||||
|
||||
return resultCodeSuccess
|
||||
default:
|
||||
// Check the response only if it's from an upstream. Don't check the
|
||||
// response if the protection is disabled since dnsrewrite rules aren't
|
||||
// applied to it anyway.
|
||||
if !ctx.protectionEnabled || !ctx.responseFromUpstream || s.dnsFilter == nil {
|
||||
break
|
||||
}
|
||||
return s.filterAfterResponse(dctx, pctx)
|
||||
}
|
||||
}
|
||||
|
||||
origResp := d.Res
|
||||
result, err := s.filterDNSResponse(ctx)
|
||||
if err != nil {
|
||||
ctx.err = err
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
ctx.result = result
|
||||
ctx.origResp = origResp
|
||||
}
|
||||
// filterAfterResponse returns the result of filtering the response that wasn't
|
||||
// explicitly allowed or rewritten.
|
||||
func (s *Server) filterAfterResponse(dctx *dnsContext, pctx *proxy.DNSContext) (res resultCode) {
|
||||
// Check the response only if it's from an upstream. Don't check the
|
||||
// response if the protection is disabled since dnsrewrite rules aren't
|
||||
// applied to it anyway.
|
||||
if !dctx.protectionEnabled || !dctx.responseFromUpstream || s.dnsFilter == nil {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
if ctx.result == nil {
|
||||
ctx.result = &filtering.Result{}
|
||||
result, err := s.filterDNSResponse(pctx, dctx.setts)
|
||||
if err != nil {
|
||||
dctx.err = err
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
dctx.result = result
|
||||
dctx.origResp = pctx.Res
|
||||
}
|
||||
|
||||
return resultCodeSuccess
|
||||
|
||||
@@ -248,7 +248,7 @@ func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
|
||||
name: "local_client_unknown_host",
|
||||
host: "wronghost.lan",
|
||||
wantIP: nil,
|
||||
wantRes: resultCodeFinish,
|
||||
wantRes: resultCodeSuccess,
|
||||
isLocalCli: true,
|
||||
}, {
|
||||
name: "external_client_known_host",
|
||||
@@ -358,7 +358,7 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
|
||||
host: "example-new.lan",
|
||||
suffix: defaultLocalDomainSuffix,
|
||||
wantIP: nil,
|
||||
wantRes: resultCodeFinish,
|
||||
wantRes: resultCodeSuccess,
|
||||
qtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "success_internal_aaaa",
|
||||
|
||||
@@ -49,11 +49,12 @@ type hostToIPTable = map[string]net.IP
|
||||
// Server is the main way to start a DNS server.
|
||||
//
|
||||
// Example:
|
||||
// s := dnsforward.Server{}
|
||||
// err := s.Start(nil) // will start a DNS server listening on default port 53, in a goroutine
|
||||
// err := s.Reconfigure(ServerConfig{UDPListenAddr: &net.UDPAddr{Port: 53535}}) // will reconfigure running DNS server to listen on UDP port 53535
|
||||
// err := s.Stop() // will stop listening on port 53535 and cancel all goroutines
|
||||
// err := s.Start(nil) // will start listening again, on port 53535, in a goroutine
|
||||
//
|
||||
// s := dnsforward.Server{}
|
||||
// err := s.Start(nil) // will start a DNS server listening on default port 53, in a goroutine
|
||||
// err := s.Reconfigure(ServerConfig{UDPListenAddr: &net.UDPAddr{Port: 53535}}) // will reconfigure running DNS server to listen on UDP port 53535
|
||||
// err := s.Stop() // will stop listening on port 53535 and cancel all goroutines
|
||||
// err := s.Start(nil) // will start listening again, on port 53535, in a goroutine
|
||||
//
|
||||
// The zero Server is empty and ready for use.
|
||||
type Server struct {
|
||||
@@ -176,18 +177,6 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NewCustomServer creates a new instance of *Server with custom internal proxy.
|
||||
func NewCustomServer(internalProxy *proxy.Proxy) *Server {
|
||||
s := &Server{
|
||||
recDetector: newRecursionDetector(0, 1),
|
||||
}
|
||||
if internalProxy != nil {
|
||||
s.internalProxy = internalProxy
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Close gracefully closes the server. It is safe for concurrent use.
|
||||
//
|
||||
// TODO(e.burkov): A better approach would be making Stop method waiting for all
|
||||
@@ -445,65 +434,54 @@ func (s *Server) setupResolvers(localAddrs []string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prepare the object
|
||||
func (s *Server) Prepare(config *ServerConfig) error {
|
||||
// Initialize the server configuration
|
||||
// --
|
||||
if config != nil {
|
||||
s.conf = *config
|
||||
if s.conf.BlockingMode == "custom_ip" {
|
||||
if s.conf.BlockingIPv4 == nil || s.conf.BlockingIPv6 == nil {
|
||||
return fmt.Errorf("dns: invalid custom blocking IP address specified")
|
||||
}
|
||||
}
|
||||
// Prepare initializes parameters of s using data from conf. conf must not be
|
||||
// nil.
|
||||
func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
s.conf = *conf
|
||||
|
||||
err = validateBlockingMode(s.conf.BlockingMode, s.conf.BlockingIPv4, s.conf.BlockingIPv6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking blocking mode: %w", err)
|
||||
}
|
||||
|
||||
// Set default values in the case if nothing is configured
|
||||
// --
|
||||
s.initDefaultSettings()
|
||||
|
||||
// Initialize ipset configuration
|
||||
// --
|
||||
err := s.ipset.init(s.conf.IpsetList)
|
||||
err = s.ipset.init(s.conf.IpsetList)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("inited ipset")
|
||||
|
||||
// Prepare DNS servers settings
|
||||
// --
|
||||
err = s.prepareUpstreamSettings()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("preparing upstream settings: %w", err)
|
||||
}
|
||||
|
||||
// Create DNS proxy configuration
|
||||
// --
|
||||
var proxyConfig proxy.Config
|
||||
proxyConfig, err = s.createProxyConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("preparing proxy: %w", err)
|
||||
}
|
||||
|
||||
// Prepare a DNS proxy instance that we use for internal DNS queries
|
||||
// --
|
||||
s.prepareIntlProxy()
|
||||
|
||||
s.access, err = newAccessCtx(s.conf.AllowedClients, s.conf.DisallowedClients, s.conf.BlockedHosts)
|
||||
err = s.prepareInternalProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("preparing internal proxy: %w", err)
|
||||
}
|
||||
|
||||
s.access, err = newAccessCtx(
|
||||
s.conf.AllowedClients,
|
||||
s.conf.DisallowedClients,
|
||||
s.conf.BlockedHosts,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing access: %w", err)
|
||||
}
|
||||
|
||||
// Register web handlers if necessary
|
||||
// --
|
||||
if !webRegistered && s.conf.HTTPRegister != nil {
|
||||
webRegistered = true
|
||||
s.registerHandlers()
|
||||
}
|
||||
|
||||
// Create the main DNS proxy instance
|
||||
// --
|
||||
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
|
||||
|
||||
err = s.setupResolvers(s.conf.LocalPTRResolvers)
|
||||
@@ -516,6 +494,61 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateBlockingMode returns an error if the blocking mode data aren't valid.
|
||||
func validateBlockingMode(mode BlockingMode, blockingIPv4, blockingIPv6 net.IP) (err error) {
|
||||
switch mode {
|
||||
case
|
||||
BlockingModeDefault,
|
||||
BlockingModeNXDOMAIN,
|
||||
BlockingModeREFUSED,
|
||||
BlockingModeNullIP:
|
||||
return nil
|
||||
case BlockingModeCustomIP:
|
||||
if blockingIPv4 == nil {
|
||||
return fmt.Errorf("blocking_ipv4 must be set when blocking_mode is custom_ip")
|
||||
} else if blockingIPv6 == nil {
|
||||
return fmt.Errorf("blocking_ipv6 must be set when blocking_mode is custom_ip")
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("bad blocking mode %q", mode)
|
||||
}
|
||||
}
|
||||
|
||||
// prepareInternalProxy initializes the DNS proxy that is used for internal DNS
|
||||
// queries, such at client PTR resolving and updater hostname resolving.
|
||||
func (s *Server) prepareInternalProxy() (err error) {
|
||||
conf := &proxy.Config{
|
||||
CacheEnabled: true,
|
||||
CacheSizeBytes: 4096,
|
||||
UpstreamConfig: s.conf.UpstreamConfig,
|
||||
MaxGoroutines: int(s.conf.MaxGoroutines),
|
||||
}
|
||||
|
||||
srvConf := s.conf
|
||||
setProxyUpstreamMode(
|
||||
conf,
|
||||
srvConf.AllServers,
|
||||
srvConf.FastestAddr,
|
||||
srvConf.FastestTimeout.Duration,
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Make a proper constructor for proxy.Proxy.
|
||||
p := &proxy.Proxy{
|
||||
Config: *conf,
|
||||
}
|
||||
|
||||
err = p.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.internalProxy = p
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the DNS server.
|
||||
func (s *Server) Stop() error {
|
||||
s.serverLock.Lock()
|
||||
@@ -561,7 +594,7 @@ func (s *Server) proxy() (p *proxy.Proxy) {
|
||||
}
|
||||
|
||||
// Reconfigure applies the new configuration to the DNS server.
|
||||
func (s *Server) Reconfigure(config *ServerConfig) error {
|
||||
func (s *Server) Reconfigure(conf *ServerConfig) error {
|
||||
s.serverLock.Lock()
|
||||
defer s.serverLock.Unlock()
|
||||
|
||||
@@ -575,7 +608,12 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
|
||||
// We wait for some time and hope that this fd will be closed.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
err = s.Prepare(config)
|
||||
// TODO(a.garipov): This whole piece of API is weird and needs to be remade.
|
||||
if conf == nil {
|
||||
conf = &s.conf
|
||||
}
|
||||
|
||||
err = s.Prepare(conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not reconfigure the server: %w", err)
|
||||
}
|
||||
|
||||
@@ -78,9 +78,11 @@ func createTestServer(
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
s.conf = forwardConf
|
||||
if forwardConf.BlockingMode == "" {
|
||||
forwardConf.BlockingMode = BlockingModeDefault
|
||||
}
|
||||
|
||||
err = s.Prepare(nil)
|
||||
err = s.Prepare(&forwardConf)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.serverLock.Lock()
|
||||
@@ -152,7 +154,7 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte)
|
||||
tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem
|
||||
s.conf.TLSConfig = tlsConf
|
||||
|
||||
err := s.Prepare(nil)
|
||||
err := s.Prepare(&s.conf)
|
||||
require.NoErrorf(t, err, "failed to prepare server: %s", err)
|
||||
|
||||
return s, certPem
|
||||
@@ -286,6 +288,9 @@ func TestServer_timeout(t *testing.T) {
|
||||
t.Run("custom", func(t *testing.T) {
|
||||
srvConf := &ServerConfig{
|
||||
UpstreamTimeout: timeout,
|
||||
FilteringConfig: FilteringConfig{
|
||||
BlockingMode: BlockingModeDefault,
|
||||
},
|
||||
}
|
||||
|
||||
s, err := NewServer(DNSCreateParams{})
|
||||
@@ -301,7 +306,8 @@ func TestServer_timeout(t *testing.T) {
|
||||
s, err := NewServer(DNSCreateParams{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Prepare(nil)
|
||||
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
|
||||
err = s.Prepare(&s.conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, DefaultTimeout, s.conf.UpstreamTimeout)
|
||||
@@ -915,6 +921,7 @@ func TestRewrite(t *testing.T) {
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
UpstreamDNS: []string{"8.8.8.8:53"},
|
||||
},
|
||||
}))
|
||||
@@ -1026,9 +1033,10 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
s.conf.UDPListenAddrs = []*net.UDPAddr{{}}
|
||||
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
|
||||
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
||||
s.conf.ProtectionEnabled = true
|
||||
s.conf.FilteringConfig.ProtectionEnabled = true
|
||||
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
|
||||
|
||||
err = s.Prepare(nil)
|
||||
err = s.Prepare(&s.conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Start()
|
||||
@@ -1098,8 +1106,9 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
||||
s.conf.UDPListenAddrs = []*net.UDPAddr{{}}
|
||||
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
|
||||
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
||||
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
|
||||
|
||||
err = s.Prepare(nil)
|
||||
err = s.Prepare(&s.conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Start()
|
||||
@@ -1217,13 +1226,17 @@ func TestServer_Exchange(t *testing.T) {
|
||||
errUpstream := aghtest.NewErrorUpstream()
|
||||
nonPtrUpstream := aghtest.NewBlockUpstream("some-host", true)
|
||||
|
||||
srv := NewCustomServer(&proxy.Proxy{
|
||||
Config: proxy.Config{
|
||||
UpstreamConfig: &proxy.UpstreamConfig{
|
||||
Upstreams: []upstream.Upstream{extUpstream},
|
||||
srv := &Server{
|
||||
recDetector: newRecursionDetector(0, 1),
|
||||
internalProxy: &proxy.Proxy{
|
||||
Config: proxy.Config{
|
||||
UpstreamConfig: &proxy.UpstreamConfig{
|
||||
Upstreams: []upstream.Upstream{extUpstream},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
srv.conf.ResolveClients = true
|
||||
srv.conf.UsePrivateRDNS = true
|
||||
|
||||
|
||||
@@ -12,64 +12,30 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// filterDNSRewriteResponse handles a single DNS rewrite response entry.
|
||||
// It returns the properly constructed answer resource record.
|
||||
func (s *Server) filterDNSRewriteResponse(req *dns.Msg, rr rules.RRType, v rules.RRValue) (ans dns.RR, err error) {
|
||||
// TODO(a.garipov): As more types are added, we will probably want to
|
||||
// use a handler-oriented approach here. So, think of a way to decouple
|
||||
// the answer generation logic from the Server.
|
||||
|
||||
// filterDNSRewriteResponse handles a single DNS rewrite response entry. It
|
||||
// returns the properly constructed answer resource record.
|
||||
func (s *Server) filterDNSRewriteResponse(
|
||||
req *dns.Msg,
|
||||
rr rules.RRType,
|
||||
v rules.RRValue,
|
||||
) (ans dns.RR, err error) {
|
||||
switch rr {
|
||||
case dns.TypeA,
|
||||
case
|
||||
dns.TypeA,
|
||||
dns.TypeAAAA:
|
||||
ip, ok := v.(net.IP)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("value for rr type %d has type %T, not net.IP", rr, v)
|
||||
}
|
||||
|
||||
if rr == dns.TypeA {
|
||||
return s.genAnswerA(req, ip.To4()), nil
|
||||
}
|
||||
|
||||
return s.genAnswerAAAA(req, ip), nil
|
||||
case dns.TypePTR,
|
||||
return s.ansFromDNSRewriteIP(v, rr, req)
|
||||
case
|
||||
dns.TypePTR,
|
||||
dns.TypeTXT:
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("value for rr type %d has type %T, not string", rr, v)
|
||||
}
|
||||
|
||||
if rr == dns.TypeTXT {
|
||||
return s.genAnswerTXT(req, []string{str}), nil
|
||||
}
|
||||
|
||||
return s.genAnswerPTR(req, str), nil
|
||||
return s.ansFromDNSRewriteText(v, rr, req)
|
||||
case dns.TypeMX:
|
||||
mx, ok := v.(*rules.DNSMX)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSMX", rr, v)
|
||||
}
|
||||
|
||||
return s.genAnswerMX(req, mx), nil
|
||||
case dns.TypeHTTPS,
|
||||
return s.ansFromDNSRewriteMX(v, rr, req)
|
||||
case
|
||||
dns.TypeHTTPS,
|
||||
dns.TypeSVCB:
|
||||
svcb, ok := v.(*rules.DNSSVCB)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSSVCB", rr, v)
|
||||
}
|
||||
|
||||
if rr == dns.TypeHTTPS {
|
||||
return s.genAnswerHTTPS(req, svcb), nil
|
||||
}
|
||||
|
||||
return s.genAnswerSVCB(req, svcb), nil
|
||||
return s.ansFromDNSRewriteSVCB(v, rr, req)
|
||||
case dns.TypeSRV:
|
||||
srv, ok := v.(*rules.DNSSRV)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSSRV", rr, v)
|
||||
}
|
||||
|
||||
return s.genAnswerSRV(req, srv), nil
|
||||
return s.ansFromDNSRewriteSRV(v, rr, req)
|
||||
default:
|
||||
log.Debug("don't know how to handle dns rr type %d, skipping", rr)
|
||||
|
||||
@@ -77,9 +43,112 @@ func (s *Server) filterDNSRewriteResponse(req *dns.Msg, rr rules.RRType, v rules
|
||||
}
|
||||
}
|
||||
|
||||
// filterDNSRewrite handles dnsrewrite filters. It constructs a DNS
|
||||
// response and sets it into d.Res.
|
||||
func (s *Server) filterDNSRewrite(req *dns.Msg, res filtering.Result, d *proxy.DNSContext) (err error) {
|
||||
// ansFromDNSRewriteIP creates a new answer resource record from the A/AAAA
|
||||
// dnsrewrite rule data.
|
||||
func (s *Server) ansFromDNSRewriteIP(
|
||||
v rules.RRValue,
|
||||
rr rules.RRType,
|
||||
req *dns.Msg,
|
||||
) (ans dns.RR, err error) {
|
||||
ip, ok := v.(net.IP)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("value for rr type %s has type %T, not net.IP", dns.Type(rr), v)
|
||||
}
|
||||
|
||||
if rr == dns.TypeA {
|
||||
return s.genAnswerA(req, ip.To4()), nil
|
||||
}
|
||||
|
||||
return s.genAnswerAAAA(req, ip), nil
|
||||
}
|
||||
|
||||
// ansFromDNSRewriteText creates a new answer resource record from the TXT/PTR
|
||||
// dnsrewrite rule data.
|
||||
func (s *Server) ansFromDNSRewriteText(
|
||||
v rules.RRValue,
|
||||
rr rules.RRType,
|
||||
req *dns.Msg,
|
||||
) (ans dns.RR, err error) {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("value for rr type %s has type %T, not string", dns.Type(rr), v)
|
||||
}
|
||||
|
||||
if rr == dns.TypeTXT {
|
||||
return s.genAnswerTXT(req, []string{str}), nil
|
||||
}
|
||||
|
||||
return s.genAnswerPTR(req, str), nil
|
||||
}
|
||||
|
||||
// ansFromDNSRewriteMX creates a new answer resource record from the MX
|
||||
// dnsrewrite rule data.
|
||||
func (s *Server) ansFromDNSRewriteMX(
|
||||
v rules.RRValue,
|
||||
rr rules.RRType,
|
||||
req *dns.Msg,
|
||||
) (ans dns.RR, err error) {
|
||||
mx, ok := v.(*rules.DNSMX)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"value for rr type %s has type %T, not *rules.DNSMX",
|
||||
dns.Type(rr),
|
||||
v,
|
||||
)
|
||||
}
|
||||
|
||||
return s.genAnswerMX(req, mx), nil
|
||||
}
|
||||
|
||||
// ansFromDNSRewriteSVCB creates a new answer resource record from the
|
||||
// SVCB/HTTPS dnsrewrite rule data.
|
||||
func (s *Server) ansFromDNSRewriteSVCB(
|
||||
v rules.RRValue,
|
||||
rr rules.RRType,
|
||||
req *dns.Msg,
|
||||
) (ans dns.RR, err error) {
|
||||
svcb, ok := v.(*rules.DNSSVCB)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"value for rr type %s has type %T, not *rules.DNSSVCB",
|
||||
dns.Type(rr),
|
||||
v,
|
||||
)
|
||||
}
|
||||
|
||||
if rr == dns.TypeHTTPS {
|
||||
return s.genAnswerHTTPS(req, svcb), nil
|
||||
}
|
||||
|
||||
return s.genAnswerSVCB(req, svcb), nil
|
||||
}
|
||||
|
||||
// ansFromDNSRewriteSRV creates a new answer resource record from the SRV
|
||||
// dnsrewrite rule data.
|
||||
func (s *Server) ansFromDNSRewriteSRV(
|
||||
v rules.RRValue,
|
||||
rr rules.RRType,
|
||||
req *dns.Msg,
|
||||
) (dns.RR, error) {
|
||||
srv, ok := v.(*rules.DNSSRV)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"value for rr type %s has type %T, not *rules.DNSSRV",
|
||||
dns.Type(rr),
|
||||
v,
|
||||
)
|
||||
}
|
||||
|
||||
return s.genAnswerSRV(req, srv), nil
|
||||
}
|
||||
|
||||
// filterDNSRewrite handles dnsrewrite filters. It constructs a DNS response
|
||||
// and sets it into pctx.Res. All parameters must not be nil.
|
||||
func (s *Server) filterDNSRewrite(
|
||||
req *dns.Msg,
|
||||
res *filtering.Result,
|
||||
pctx *proxy.DNSContext,
|
||||
) (err error) {
|
||||
resp := s.makeResponse(req)
|
||||
dnsrr := res.DNSRewriteResult
|
||||
if dnsrr == nil {
|
||||
@@ -88,7 +157,7 @@ func (s *Server) filterDNSRewrite(req *dns.Msg, res filtering.Result, d *proxy.D
|
||||
|
||||
resp.Rcode = dnsrr.RCode
|
||||
if resp.Rcode != dns.RcodeSuccess {
|
||||
d.Res = resp
|
||||
pctx.Res = resp
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -109,7 +178,7 @@ func (s *Server) filterDNSRewrite(req *dns.Msg, res filtering.Result, d *proxy.D
|
||||
resp.Answer = append(resp.Answer, ans)
|
||||
}
|
||||
|
||||
d.Res = resp
|
||||
pctx.Res = resp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
||||
}},
|
||||
}
|
||||
}
|
||||
makeRes := func(rcode rules.RCode, rr rules.RRType, v rules.RRValue) (res filtering.Result) {
|
||||
makeRes := func(rcode rules.RCode, rr rules.RRType, v rules.RRValue) (res *filtering.Result) {
|
||||
resp := filtering.DNSRewriteResultResponse{
|
||||
rr: []rules.RRValue{v},
|
||||
}
|
||||
return filtering.Result{
|
||||
return &filtering.Result{
|
||||
DNSRewriteResult: &filtering.DNSRewriteResult{
|
||||
RCode: rcode,
|
||||
Response: resp,
|
||||
|
||||
@@ -49,69 +49,83 @@ func (s *Server) beforeRequestHandler(
|
||||
}
|
||||
|
||||
// getClientRequestFilteringSettings looks up client filtering settings using
|
||||
// the client's IP address and ID, if any, from ctx.
|
||||
func (s *Server) getClientRequestFilteringSettings(ctx *dnsContext) *filtering.Settings {
|
||||
// the client's IP address and ID, if any, from dctx.
|
||||
func (s *Server) getClientRequestFilteringSettings(dctx *dnsContext) *filtering.Settings {
|
||||
setts := s.dnsFilter.GetConfig()
|
||||
setts.ProtectionEnabled = ctx.protectionEnabled
|
||||
setts.ProtectionEnabled = dctx.protectionEnabled
|
||||
if s.conf.FilterHandler != nil {
|
||||
ip, _ := netutil.IPAndPortFromAddr(ctx.proxyCtx.Addr)
|
||||
s.conf.FilterHandler(ip, ctx.clientID, &setts)
|
||||
ip, _ := netutil.IPAndPortFromAddr(dctx.proxyCtx.Addr)
|
||||
s.conf.FilterHandler(ip, dctx.clientID, &setts)
|
||||
}
|
||||
|
||||
return &setts
|
||||
}
|
||||
|
||||
// filterDNSRequest applies the dnsFilter and sets d.Res if the request was
|
||||
// filtered.
|
||||
func (s *Server) filterDNSRequest(ctx *dnsContext) (*filtering.Result, error) {
|
||||
d := ctx.proxyCtx
|
||||
req := d.Req
|
||||
// filterDNSRequest applies the dnsFilter and sets dctx.proxyCtx.Res if the
|
||||
// request was filtered.
|
||||
func (s *Server) filterDNSRequest(dctx *dnsContext) (res *filtering.Result, err error) {
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
host := strings.TrimSuffix(q.Name, ".")
|
||||
res, err := s.dnsFilter.CheckHost(host, q.Qtype, ctx.setts)
|
||||
resVal, err := s.dnsFilter.CheckHost(host, q.Qtype, dctx.setts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("checking host %q: %w", host, err)
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Make CheckHost return a pointer.
|
||||
res = &resVal
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("failed to check host %q: %w", host, err)
|
||||
case res.IsFiltered:
|
||||
log.Tracef("host %q is filtered, reason %q, rule: %q", host, res.Reason, res.Rules[0].Text)
|
||||
d.Res = s.genDNSFilterMessage(d, &res)
|
||||
pctx.Res = s.genDNSFilterMessage(pctx, res)
|
||||
case res.Reason.In(filtering.Rewritten, filtering.RewrittenRule) &&
|
||||
res.CanonName != "" &&
|
||||
len(res.IPList) == 0:
|
||||
// Resolve the new canonical name, not the original host name. The
|
||||
// original question is readded in processFilteringAfterResponse.
|
||||
ctx.origQuestion = q
|
||||
dctx.origQuestion = q
|
||||
req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||
case res.Reason == filtering.Rewritten:
|
||||
resp := s.makeResponse(req)
|
||||
|
||||
name := host
|
||||
if len(res.CanonName) != 0 {
|
||||
resp.Answer = append(resp.Answer, s.genAnswerCNAME(req, res.CanonName))
|
||||
name = res.CanonName
|
||||
}
|
||||
|
||||
for _, ip := range res.IPList {
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
a := s.genAnswerA(req, ip.To4())
|
||||
a.Hdr.Name = dns.Fqdn(name)
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
case dns.TypeAAAA:
|
||||
a := s.genAnswerAAAA(req, ip)
|
||||
a.Hdr.Name = dns.Fqdn(name)
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
}
|
||||
}
|
||||
|
||||
d.Res = resp
|
||||
pctx.Res = s.filterRewritten(req, host, res, q.Qtype)
|
||||
case res.Reason.In(filtering.RewrittenRule, filtering.RewrittenAutoHosts):
|
||||
if err = s.filterDNSRewrite(req, res, d); err != nil {
|
||||
if err = s.filterDNSRewrite(req, res, pctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &res, err
|
||||
return res, err
|
||||
}
|
||||
|
||||
// filterRewritten handles DNS rewrite filters. It returns a DNS response with
|
||||
// the data from the filtering result. All parameters must not be nil.
|
||||
func (s *Server) filterRewritten(
|
||||
req *dns.Msg,
|
||||
host string,
|
||||
res *filtering.Result,
|
||||
qt uint16,
|
||||
) (resp *dns.Msg) {
|
||||
resp = s.makeResponse(req)
|
||||
name := host
|
||||
if len(res.CanonName) != 0 {
|
||||
resp.Answer = append(resp.Answer, s.genAnswerCNAME(req, res.CanonName))
|
||||
name = res.CanonName
|
||||
}
|
||||
|
||||
for _, ip := range res.IPList {
|
||||
switch qt {
|
||||
case dns.TypeA:
|
||||
a := s.genAnswerA(req, ip.To4())
|
||||
a.Hdr.Name = dns.Fqdn(name)
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
case dns.TypeAAAA:
|
||||
a := s.genAnswerAAAA(req, ip)
|
||||
a.Hdr.Name = dns.Fqdn(name)
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
}
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// checkHostRules checks the host against filters. It is safe for concurrent
|
||||
@@ -137,16 +151,17 @@ func (s *Server) checkHostRules(host string, rrtype uint16, setts *filtering.Set
|
||||
}
|
||||
|
||||
// filterDNSResponse checks each resource record of the response's answer
|
||||
// section from ctx and returns a non-nil res if at least one of canonnical
|
||||
// section from pctx and returns a non-nil res if at least one of canonnical
|
||||
// names or IP addresses in it matches the filtering rules.
|
||||
func (s *Server) filterDNSResponse(ctx *dnsContext) (res *filtering.Result, err error) {
|
||||
d := ctx.proxyCtx
|
||||
setts := ctx.setts
|
||||
func (s *Server) filterDNSResponse(
|
||||
pctx *proxy.DNSContext,
|
||||
setts *filtering.Settings,
|
||||
) (res *filtering.Result, err error) {
|
||||
if !setts.FilteringEnabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, a := range d.Res.Answer {
|
||||
for _, a := range pctx.Res.Answer {
|
||||
host := ""
|
||||
var rrtype uint16
|
||||
switch a := a.(type) {
|
||||
@@ -171,8 +186,8 @@ func (s *Server) filterDNSResponse(ctx *dnsContext) (res *filtering.Result, err
|
||||
} else if res == nil {
|
||||
continue
|
||||
} else if res.IsFiltered {
|
||||
d.Res = s.genDNSFilterMessage(d, res)
|
||||
log.Debug("DNSFwd: Matched %s by response: %s", d.Req.Question[0].Name, host)
|
||||
pctx.Res = s.genDNSFilterMessage(pctx, res)
|
||||
log.Debug("DNSFwd: Matched %s by response: %s", pctx.Req.Question[0].Name, host)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -45,8 +45,7 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
s.conf = forwardConf
|
||||
err = s.Prepare(nil)
|
||||
err = s.Prepare(&forwardConf)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
|
||||
@@ -20,16 +20,14 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type dnsConfig struct {
|
||||
Upstreams *[]string `json:"upstream_dns"`
|
||||
UpstreamsFile *string `json:"upstream_dns_file"`
|
||||
Bootstraps *[]string `json:"bootstrap_dns"`
|
||||
|
||||
// jsonDNSConfig is the JSON representation of the DNS server configuration.
|
||||
type jsonDNSConfig struct {
|
||||
Upstreams *[]string `json:"upstream_dns"`
|
||||
UpstreamsFile *string `json:"upstream_dns_file"`
|
||||
Bootstraps *[]string `json:"bootstrap_dns"`
|
||||
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||
RateLimit *uint32 `json:"ratelimit"`
|
||||
BlockingMode *BlockingMode `json:"blocking_mode"`
|
||||
BlockingIPv4 net.IP `json:"blocking_ipv4"`
|
||||
BlockingIPv6 net.IP `json:"blocking_ipv6"`
|
||||
EDNSCSEnabled *bool `json:"edns_cs_enabled"`
|
||||
DNSSECEnabled *bool `json:"dnssec_enabled"`
|
||||
DisableIPv6 *bool `json:"disable_ipv6"`
|
||||
@@ -41,9 +39,11 @@ type dnsConfig struct {
|
||||
ResolveClients *bool `json:"resolve_clients"`
|
||||
UsePrivateRDNS *bool `json:"use_private_ptr_resolvers"`
|
||||
LocalPTRUpstreams *[]string `json:"local_ptr_upstreams"`
|
||||
BlockingIPv4 net.IP `json:"blocking_ipv4"`
|
||||
BlockingIPv6 net.IP `json:"blocking_ipv6"`
|
||||
}
|
||||
|
||||
func (s *Server) getDNSConfig() (c *dnsConfig) {
|
||||
func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
@@ -72,7 +72,7 @@ func (s *Server) getDNSConfig() (c *dnsConfig) {
|
||||
upstreamMode = "parallel"
|
||||
}
|
||||
|
||||
return &dnsConfig{
|
||||
return &jsonDNSConfig{
|
||||
Upstreams: &upstreams,
|
||||
UpstreamsFile: &upstreamFile,
|
||||
Bootstraps: &bootstraps,
|
||||
@@ -102,13 +102,13 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
dnsConfig
|
||||
jsonDNSConfig
|
||||
// DefautLocalPTRUpstreams is used to pass the addresses from
|
||||
// systemResolvers to the front-end. It's not a pointer to the slice
|
||||
// since there is no need to omit it while decoding from JSON.
|
||||
DefautLocalPTRUpstreams []string `json:"default_local_ptr_upstreams,omitempty"`
|
||||
}{
|
||||
dnsConfig: *s.getDNSConfig(),
|
||||
jsonDNSConfig: *s.getDNSConfig(),
|
||||
DefautLocalPTRUpstreams: defLocalPTRUps,
|
||||
}
|
||||
|
||||
@@ -121,31 +121,21 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (req *dnsConfig) checkBlockingMode() bool {
|
||||
func (req *jsonDNSConfig) checkBlockingMode() (err error) {
|
||||
if req.BlockingMode == nil {
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
switch bm := *req.BlockingMode; bm {
|
||||
case BlockingModeDefault,
|
||||
BlockingModeREFUSED,
|
||||
BlockingModeNXDOMAIN,
|
||||
BlockingModeNullIP:
|
||||
return true
|
||||
case BlockingModeCustomIP:
|
||||
return req.BlockingIPv4.To4() != nil && req.BlockingIPv6 != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return validateBlockingMode(*req.BlockingMode, req.BlockingIPv4, req.BlockingIPv6)
|
||||
}
|
||||
|
||||
func (req *dnsConfig) checkUpstreamsMode() bool {
|
||||
func (req *jsonDNSConfig) checkUpstreamsMode() bool {
|
||||
valid := []string{"", "fastest_addr", "parallel"}
|
||||
|
||||
return req.UpstreamMode == nil || stringutil.InSlice(valid, *req.UpstreamMode)
|
||||
}
|
||||
|
||||
func (req *dnsConfig) checkBootstrap() (err error) {
|
||||
func (req *jsonDNSConfig) checkBootstrap() (err error) {
|
||||
if req.Bootstraps == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -167,7 +157,7 @@ func (req *dnsConfig) checkBootstrap() (err error) {
|
||||
}
|
||||
|
||||
// validate returns an error if any field of req is invalid.
|
||||
func (req *dnsConfig) validate(privateNets netutil.SubnetSet) (err error) {
|
||||
func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) {
|
||||
if req.Upstreams != nil {
|
||||
err = ValidateUpstreams(*req.Upstreams)
|
||||
if err != nil {
|
||||
@@ -187,9 +177,12 @@ func (req *dnsConfig) validate(privateNets netutil.SubnetSet) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = req.checkBlockingMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case !req.checkBlockingMode():
|
||||
return errors.Error("blocking_mode: incorrect value")
|
||||
case !req.checkUpstreamsMode():
|
||||
return errors.Error("upstream_mode: incorrect value")
|
||||
case !req.checkCacheTTL():
|
||||
@@ -199,7 +192,7 @@ func (req *dnsConfig) validate(privateNets netutil.SubnetSet) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (req *dnsConfig) checkCacheTTL() bool {
|
||||
func (req *jsonDNSConfig) checkCacheTTL() bool {
|
||||
if req.CacheMinTTL == nil && req.CacheMaxTTL == nil {
|
||||
return true
|
||||
}
|
||||
@@ -216,7 +209,7 @@ func (req *dnsConfig) checkCacheTTL() bool {
|
||||
}
|
||||
|
||||
func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req := &dnsConfig{}
|
||||
req := &jsonDNSConfig{}
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "decoding request: %s", err)
|
||||
@@ -242,100 +235,75 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) setConfigRestartable(dc *dnsConfig) (restart bool) {
|
||||
if dc.Upstreams != nil {
|
||||
s.conf.UpstreamDNS = *dc.Upstreams
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.LocalPTRUpstreams != nil {
|
||||
s.conf.LocalPTRResolvers = *dc.LocalPTRUpstreams
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.UpstreamsFile != nil {
|
||||
s.conf.UpstreamDNSFileName = *dc.UpstreamsFile
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.Bootstraps != nil {
|
||||
s.conf.BootstrapDNS = *dc.Bootstraps
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.RateLimit != nil && s.conf.Ratelimit != *dc.RateLimit {
|
||||
s.conf.Ratelimit = *dc.RateLimit
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.EDNSCSEnabled != nil {
|
||||
s.conf.EnableEDNSClientSubnet = *dc.EDNSCSEnabled
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.CacheSize != nil {
|
||||
s.conf.CacheSize = *dc.CacheSize
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.CacheMinTTL != nil {
|
||||
s.conf.CacheMinTTL = *dc.CacheMinTTL
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.CacheMaxTTL != nil {
|
||||
s.conf.CacheMaxTTL = *dc.CacheMaxTTL
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.CacheOptimistic != nil {
|
||||
s.conf.CacheOptimistic = *dc.CacheOptimistic
|
||||
restart = true
|
||||
}
|
||||
|
||||
return restart
|
||||
}
|
||||
|
||||
func (s *Server) setConfig(dc *dnsConfig) (restart bool) {
|
||||
// setConfigRestartable sets the server parameters. shouldRestart is true if
|
||||
// the server should be restarted to apply changes.
|
||||
func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) {
|
||||
s.serverLock.Lock()
|
||||
defer s.serverLock.Unlock()
|
||||
|
||||
if dc.ProtectionEnabled != nil {
|
||||
s.conf.ProtectionEnabled = *dc.ProtectionEnabled
|
||||
}
|
||||
|
||||
if dc.BlockingMode != nil {
|
||||
s.conf.BlockingMode = *dc.BlockingMode
|
||||
if *dc.BlockingMode == "custom_ip" {
|
||||
if *dc.BlockingMode == BlockingModeCustomIP {
|
||||
s.conf.BlockingIPv4 = dc.BlockingIPv4.To4()
|
||||
s.conf.BlockingIPv6 = dc.BlockingIPv6.To16()
|
||||
}
|
||||
}
|
||||
|
||||
if dc.DNSSECEnabled != nil {
|
||||
s.conf.EnableDNSSEC = *dc.DNSSECEnabled
|
||||
}
|
||||
|
||||
if dc.DisableIPv6 != nil {
|
||||
s.conf.AAAADisabled = *dc.DisableIPv6
|
||||
}
|
||||
|
||||
if dc.UpstreamMode != nil {
|
||||
s.conf.AllServers = *dc.UpstreamMode == "parallel"
|
||||
s.conf.FastestAddr = *dc.UpstreamMode == "fastest_addr"
|
||||
}
|
||||
|
||||
if dc.ResolveClients != nil {
|
||||
s.conf.ResolveClients = *dc.ResolveClients
|
||||
}
|
||||
|
||||
if dc.UsePrivateRDNS != nil {
|
||||
s.conf.UsePrivateRDNS = *dc.UsePrivateRDNS
|
||||
}
|
||||
setIfNotNil(&s.conf.ProtectionEnabled, dc.ProtectionEnabled)
|
||||
setIfNotNil(&s.conf.EnableDNSSEC, dc.DNSSECEnabled)
|
||||
setIfNotNil(&s.conf.AAAADisabled, dc.DisableIPv6)
|
||||
setIfNotNil(&s.conf.ResolveClients, dc.ResolveClients)
|
||||
setIfNotNil(&s.conf.UsePrivateRDNS, dc.UsePrivateRDNS)
|
||||
|
||||
return s.setConfigRestartable(dc)
|
||||
}
|
||||
|
||||
// setIfNotNil sets the value pointed at by currentPtr to the value pointed at
|
||||
// by newPtr if newPtr is not nil. currentPtr must not be nil.
|
||||
func setIfNotNil[T any](currentPtr, newPtr *T) (hasSet bool) {
|
||||
if newPtr == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
*currentPtr = *newPtr
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// setConfigRestartable sets the parameters which trigger a restart.
|
||||
// shouldRestart is true if the server should be restarted to apply changes.
|
||||
// s.serverLock is expected to be locked.
|
||||
func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
|
||||
for _, hasSet := range []bool{
|
||||
setIfNotNil(&s.conf.UpstreamDNS, dc.Upstreams),
|
||||
setIfNotNil(&s.conf.LocalPTRResolvers, dc.LocalPTRUpstreams),
|
||||
setIfNotNil(&s.conf.UpstreamDNSFileName, dc.UpstreamsFile),
|
||||
setIfNotNil(&s.conf.BootstrapDNS, dc.Bootstraps),
|
||||
setIfNotNil(&s.conf.EnableEDNSClientSubnet, dc.EDNSCSEnabled),
|
||||
setIfNotNil(&s.conf.CacheSize, dc.CacheSize),
|
||||
setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL),
|
||||
setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL),
|
||||
setIfNotNil(&s.conf.CacheOptimistic, dc.CacheOptimistic),
|
||||
} {
|
||||
shouldRestart = shouldRestart || hasSet
|
||||
if shouldRestart {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dc.RateLimit != nil && s.conf.Ratelimit != *dc.RateLimit {
|
||||
s.conf.Ratelimit = *dc.RateLimit
|
||||
shouldRestart = true
|
||||
}
|
||||
|
||||
return shouldRestart
|
||||
}
|
||||
|
||||
// upstreamJSON is a request body for handleTestUpstreamDNS endpoint.
|
||||
type upstreamJSON struct {
|
||||
Upstreams []string `json:"upstream_dns"`
|
||||
@@ -711,11 +679,16 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// handleDoH is the DNS-over-HTTPs handler.
|
||||
//
|
||||
// Control flow:
|
||||
// web
|
||||
// -> dnsforward.handleDoH -> dnsforward.ServeHTTP
|
||||
// -> proxy.ServeHTTP -> proxy.handleDNSRequest
|
||||
// -> dnsforward.handleDNSRequest
|
||||
//
|
||||
// HTTP server
|
||||
// -> dnsforward.handleDoH
|
||||
// -> dnsforward.ServeHTTP
|
||||
// -> proxy.ServeHTTP
|
||||
// -> proxy.handleDNSRequest
|
||||
// -> dnsforward.handleDNSRequest
|
||||
func (s *Server) handleDoH(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.conf.TLSAllowUnencryptedDoH && r.TLS == nil {
|
||||
aghhttp.Error(r, w, http.StatusNotFound, "Not Found")
|
||||
|
||||
@@ -62,6 +62,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
|
||||
TCPListenAddrs: []*net.TCPAddr{},
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
},
|
||||
ConfigModified: func() {},
|
||||
@@ -135,6 +136,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
||||
TCPListenAddrs: []*net.TCPAddr{},
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
},
|
||||
ConfigModified: func() {},
|
||||
@@ -164,7 +166,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "blocking_mode_bad",
|
||||
wantSet: "blocking_mode: incorrect value",
|
||||
wantSet: "blocking_ipv4 must be set when blocking_mode is custom_ip",
|
||||
}, {
|
||||
name: "ratelimit",
|
||||
wantSet: "",
|
||||
|
||||
@@ -37,66 +37,73 @@ func ipsFromRules(resRules []*filtering.ResultRule) (ips []net.IP) {
|
||||
return ips
|
||||
}
|
||||
|
||||
// genDNSFilterMessage generates a DNS message corresponding to the filtering result
|
||||
func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *filtering.Result) *dns.Msg {
|
||||
m := d.Req
|
||||
|
||||
if m.Question[0].Qtype != dns.TypeA && m.Question[0].Qtype != dns.TypeAAAA {
|
||||
// genDNSFilterMessage generates a filtered response to req for the filtering
|
||||
// result res.
|
||||
func (s *Server) genDNSFilterMessage(
|
||||
dctx *proxy.DNSContext,
|
||||
res *filtering.Result,
|
||||
) (resp *dns.Msg) {
|
||||
req := dctx.Req
|
||||
if qt := req.Question[0].Qtype; qt != dns.TypeA && qt != dns.TypeAAAA {
|
||||
if s.conf.BlockingMode == BlockingModeNullIP {
|
||||
return s.makeResponse(m)
|
||||
return s.makeResponse(req)
|
||||
}
|
||||
return s.genNXDomain(m)
|
||||
|
||||
return s.genNXDomain(req)
|
||||
}
|
||||
|
||||
ips := ipsFromRules(result.Rules)
|
||||
switch result.Reason {
|
||||
switch res.Reason {
|
||||
case filtering.FilteredSafeBrowsing:
|
||||
return s.genBlockedHost(m, s.conf.SafeBrowsingBlockHost, d)
|
||||
return s.genBlockedHost(req, s.conf.SafeBrowsingBlockHost, dctx)
|
||||
case filtering.FilteredParental:
|
||||
return s.genBlockedHost(m, s.conf.ParentalBlockHost, d)
|
||||
return s.genBlockedHost(req, s.conf.ParentalBlockHost, dctx)
|
||||
default:
|
||||
// If the query was filtered by "Safe search", filtering also must return
|
||||
// the IP address that must be used in response.
|
||||
// In this case regardless of the filtering method, we should return it
|
||||
if result.Reason == filtering.FilteredSafeSearch && len(ips) > 0 {
|
||||
return s.genResponseWithIPs(m, ips)
|
||||
// If the query was filtered by Safe Search, filtering also must return
|
||||
// the IP addresses that must be used in response. Return them
|
||||
// regardless of the filtering method.
|
||||
ips := ipsFromRules(res.Rules)
|
||||
if res.Reason == filtering.FilteredSafeSearch && len(ips) > 0 {
|
||||
return s.genResponseWithIPs(req, ips)
|
||||
}
|
||||
|
||||
switch s.conf.BlockingMode {
|
||||
case BlockingModeCustomIP:
|
||||
switch m.Question[0].Qtype {
|
||||
case dns.TypeA:
|
||||
return s.genARecord(m, s.conf.BlockingIPv4)
|
||||
case dns.TypeAAAA:
|
||||
return s.genAAAARecord(m, s.conf.BlockingIPv6)
|
||||
default:
|
||||
// Generally shouldn't happen, since the types
|
||||
// are checked above.
|
||||
log.Error(
|
||||
"dns: invalid msg type %d for blocking mode %s",
|
||||
m.Question[0].Qtype,
|
||||
s.conf.BlockingMode,
|
||||
)
|
||||
return s.genForBlockingMode(req, ips)
|
||||
}
|
||||
}
|
||||
|
||||
return s.makeResponse(m)
|
||||
}
|
||||
case BlockingModeDefault:
|
||||
if len(ips) > 0 {
|
||||
return s.genResponseWithIPs(m, ips)
|
||||
}
|
||||
|
||||
return s.makeResponseNullIP(m)
|
||||
case BlockingModeNullIP:
|
||||
return s.makeResponseNullIP(m)
|
||||
case BlockingModeNXDOMAIN:
|
||||
return s.genNXDomain(m)
|
||||
case BlockingModeREFUSED:
|
||||
return s.makeResponseREFUSED(m)
|
||||
// genForBlockingMode generates a filtered response to req based on the server's
|
||||
// blocking mode.
|
||||
func (s *Server) genForBlockingMode(req *dns.Msg, ips []net.IP) (resp *dns.Msg) {
|
||||
qt := req.Question[0].Qtype
|
||||
switch m := s.conf.BlockingMode; m {
|
||||
case BlockingModeCustomIP:
|
||||
switch qt {
|
||||
case dns.TypeA:
|
||||
return s.genARecord(req, s.conf.BlockingIPv4)
|
||||
case dns.TypeAAAA:
|
||||
return s.genAAAARecord(req, s.conf.BlockingIPv6)
|
||||
default:
|
||||
log.Error("dns: invalid blocking mode %q", s.conf.BlockingMode)
|
||||
// Generally shouldn't happen, since the types are checked in
|
||||
// genDNSFilterMessage.
|
||||
log.Error("dns: invalid msg type %s for blocking mode %s", dns.Type(qt), m)
|
||||
|
||||
return s.makeResponse(m)
|
||||
return s.makeResponse(req)
|
||||
}
|
||||
case BlockingModeDefault:
|
||||
if len(ips) > 0 {
|
||||
return s.genResponseWithIPs(req, ips)
|
||||
}
|
||||
|
||||
return s.makeResponseNullIP(req)
|
||||
case BlockingModeNullIP:
|
||||
return s.makeResponseNullIP(req)
|
||||
case BlockingModeNXDOMAIN:
|
||||
return s.genNXDomain(req)
|
||||
case BlockingModeREFUSED:
|
||||
return s.makeResponseREFUSED(req)
|
||||
default:
|
||||
log.Error("dns: invalid blocking mode %q", s.conf.BlockingMode)
|
||||
|
||||
return s.makeResponse(req)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,10 +157,10 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
|
||||
// Firstly, the parsing of non-contiguous values isn't supported. Secondly, the
|
||||
// parsing of value-lists is not supported either.
|
||||
//
|
||||
// ipv4hint=127.0.0.1 // Supported.
|
||||
// ipv4hint="127.0.0.1" // Unsupported.
|
||||
// ipv4hint=127.0.0.1,127.0.0.2 // Unsupported.
|
||||
// ipv4hint="127.0.0.1,127.0.0.2" // Unsupported.
|
||||
// ipv4hint=127.0.0.1 // Supported.
|
||||
// ipv4hint="127.0.0.1" // Unsupported.
|
||||
// ipv4hint=127.0.0.1,127.0.0.2 // Unsupported.
|
||||
// ipv4hint="127.0.0.1,127.0.0.2" // Unsupported.
|
||||
//
|
||||
// TODO(a.garipov): Support all of these.
|
||||
func (s *Server) genAnswerSVCB(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns.SVCB) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -42,7 +42,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -71,7 +71,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -86,4 +86,4 @@
|
||||
"use_private_ptr_resolvers": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -53,7 +53,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -121,7 +121,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -155,7 +155,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 6,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -189,7 +189,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": true,
|
||||
@@ -223,7 +223,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -257,7 +257,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -291,7 +291,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -325,7 +325,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -361,7 +361,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -397,7 +397,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -432,7 +432,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -466,7 +466,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -502,7 +502,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -541,7 +541,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
@@ -575,7 +575,7 @@
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
|
||||
Reference in New Issue
Block a user