Pull request: 3184 disable private ptr
Merge in DNS/adguard-home from 3184-disable-ptr to master Updates #3184. Squashed commit of the following: commit b78ac2eeb1b408586808ddbd1c87107f373b11b0 Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed May 26 17:20:34 2021 +0300 all: rename dns config field commit 36512134822a5f6b8b296ccbd7e7d5a9b8e87f26 Author: Ildar Kamalov <ik@adguard.com> Date: Wed May 26 15:55:44 2021 +0300 client: handle local ips rdns commit 9a691830d45db93e078332d85bc0efa7dc7b6ac6 Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed May 26 14:43:13 2021 +0300 all: imp naming commit 771b7a3d5d25f91408dd97ba3287efb641028ccf Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed May 26 14:24:38 2021 +0300 all: imp docs, code commit be960893e8bbb7375a944ca0345b50c857a2d7cf Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed May 26 13:23:56 2021 +0300 all: imp docs & log changes commit 4e645a520f6bb584ef951435ee833ad30769af98 Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed May 26 12:49:44 2021 +0300 all: add the field into structs commit 22b5b6163f086560a3189234532ba877be7ba940 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue May 25 15:10:31 2021 +0300 dnsforward: entitle lock, imp code
This commit is contained in:
parent
755a5055c6
commit
557bbcbf37
|
@ -15,6 +15,8 @@ and this project adheres to
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- The ability to completely disable reverse DNS resolving of IPs from
|
||||||
|
locally-served networks ([#3184]).
|
||||||
- New flag `--local-frontend` to serve dinamically changeable frontend files
|
- New flag `--local-frontend` to serve dinamically changeable frontend files
|
||||||
from disk as opposed to the ones that were compiled into the binary.
|
from disk as opposed to the ones that were compiled into the binary.
|
||||||
|
|
||||||
|
@ -35,6 +37,8 @@ released by then.
|
||||||
|
|
||||||
- Go 1.15 support.
|
- Go 1.15 support.
|
||||||
|
|
||||||
|
[#3184]: https://github.com/AdguardTeam/AdGuardHome/issues/3184
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [v0.106.3] - 2021-05-19
|
## [v0.106.3] - 2021-05-19
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
"load_balancing_desc": "Query one upstream server at a time. AdGuard Home will use the weighted random algorithm to pick the server so that the fastest server is used more often.",
|
"load_balancing_desc": "Query one upstream server at a time. AdGuard Home will use the weighted random algorithm to pick the server so that the fastest server is used more often.",
|
||||||
"bootstrap_dns": "Bootstrap DNS servers",
|
"bootstrap_dns": "Bootstrap DNS servers",
|
||||||
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams.",
|
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams.",
|
||||||
"local_ptr_title": "Private DNS servers",
|
"local_ptr_title": "Private reverse DNS servers",
|
||||||
"local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve the hostnames of clients with private IP addresses, for example \"192.168.12.34\", using rDNS. If not set, AdGuard Home uses the default DNS resolvers of your OS.",
|
"local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve the hostnames of clients with private IP addresses, for example \"192.168.12.34\", using rDNS. If not set, AdGuard Home uses the default DNS resolvers of your OS.",
|
||||||
"local_ptr_placeholder": "Enter one server address per line",
|
"local_ptr_placeholder": "Enter one server address per line",
|
||||||
"resolve_clients_title": "Enable reverse resolving of clients' IP addresses",
|
"resolve_clients_title": "Enable reverse resolving of clients' IP addresses",
|
||||||
"resolve_clients_desc": "If enabled, AdGuard Home will attempt to reversely resolve clients' IP addresses into their hostnames by sending PTR queries to corresponding resolvers (private DNS servers for local clients, upstream server for clients with public IP addresses).",
|
"resolve_clients_desc": "If enabled, AdGuard Home will attempt to reversely resolve clients' IP addresses into their hostnames by sending PTR queries to corresponding resolvers (private DNS servers for local clients, upstream server for clients with public IP addresses).",
|
||||||
|
"use_private_ptr_resolvers_title": "Use private rDNS resolvers",
|
||||||
|
"use_private_ptr_resolvers_desc": "Perform reverse DNS lookups for locally-served addresses using these upstream servers. If disabled, AdGuard Home responds with NXDOMAIN to all such PTR requests except for clients known from DHCP, /etc/hosts, and so on.",
|
||||||
"check_dhcp_servers": "Check for DHCP servers",
|
"check_dhcp_servers": "Check for DHCP servers",
|
||||||
"save_config": "Save configuration",
|
"save_config": "Save configuration",
|
||||||
"enabled_dhcp": "DHCP server enabled",
|
"enabled_dhcp": "DHCP server enabled",
|
||||||
|
|
|
@ -178,7 +178,7 @@ const Form = ({
|
||||||
<Examples />
|
<Examples />
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 mb-4">
|
<div className="col-12 mb-2">
|
||||||
<label
|
<label
|
||||||
className="form__label form__label--with-desc"
|
className="form__label form__label--with-desc"
|
||||||
htmlFor="bootstrap_dns"
|
htmlFor="bootstrap_dns"
|
||||||
|
@ -202,7 +202,7 @@ const Form = ({
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 mb-4">
|
<div className="col-12">
|
||||||
<label
|
<label
|
||||||
className="form__label form__label--with-desc"
|
className="form__label form__label--with-desc"
|
||||||
htmlFor="local_ptr"
|
htmlFor="local_ptr"
|
||||||
|
@ -222,6 +222,19 @@ const Form = ({
|
||||||
disabled={processingSetConfig}
|
disabled={processingSetConfig}
|
||||||
normalizeOnBlur={removeEmptyLines}
|
normalizeOnBlur={removeEmptyLines}
|
||||||
/>
|
/>
|
||||||
|
<div className="mt-4">
|
||||||
|
<Field
|
||||||
|
name="use_private_ptr_resolvers"
|
||||||
|
type="checkbox"
|
||||||
|
component={CheckboxField}
|
||||||
|
placeholder={t('use_private_ptr_resolvers_title')}
|
||||||
|
subtitle={t('use_private_ptr_resolvers_desc')}
|
||||||
|
disabled={processingSetConfig}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12">
|
||||||
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 mb-4">
|
<div className="col-12 mb-4">
|
||||||
<Field
|
<Field
|
||||||
|
|
|
@ -14,6 +14,7 @@ const Upstream = () => {
|
||||||
upstream_mode,
|
upstream_mode,
|
||||||
resolve_clients,
|
resolve_clients,
|
||||||
local_ptr_upstreams,
|
local_ptr_upstreams,
|
||||||
|
use_private_ptr_resolvers,
|
||||||
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||||
|
|
||||||
const upstream_dns_file = useSelector((state) => state.dnsConfig.upstream_dns_file);
|
const upstream_dns_file = useSelector((state) => state.dnsConfig.upstream_dns_file);
|
||||||
|
@ -25,6 +26,7 @@ const Upstream = () => {
|
||||||
upstream_mode,
|
upstream_mode,
|
||||||
resolve_clients,
|
resolve_clients,
|
||||||
local_ptr_upstreams,
|
local_ptr_upstreams,
|
||||||
|
use_private_ptr_resolvers,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
const dnsConfig = {
|
const dnsConfig = {
|
||||||
|
@ -32,6 +34,7 @@ const Upstream = () => {
|
||||||
upstream_mode,
|
upstream_mode,
|
||||||
resolve_clients,
|
resolve_clients,
|
||||||
local_ptr_upstreams,
|
local_ptr_upstreams,
|
||||||
|
use_private_ptr_resolvers,
|
||||||
...(upstream_dns_file ? null : { upstream_dns }),
|
...(upstream_dns_file ? null : { upstream_dns }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,6 +56,7 @@ const Upstream = () => {
|
||||||
upstream_mode,
|
upstream_mode,
|
||||||
resolve_clients,
|
resolve_clients,
|
||||||
local_ptr_upstreams,
|
local_ptr_upstreams,
|
||||||
|
use_private_ptr_resolvers,
|
||||||
}}
|
}}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -142,14 +142,19 @@ type accessListJSON struct {
|
||||||
BlockedHosts []string `json:"blocked_hosts"`
|
BlockedHosts []string `json:"blocked_hosts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) accessListJSON() (j accessListJSON) {
|
||||||
s.RLock()
|
s.serverLock.RLock()
|
||||||
j := accessListJSON{
|
defer s.serverLock.RUnlock()
|
||||||
AllowedClients: s.conf.AllowedClients,
|
|
||||||
DisallowedClients: s.conf.DisallowedClients,
|
return accessListJSON{
|
||||||
BlockedHosts: s.conf.BlockedHosts,
|
AllowedClients: aghstrings.CloneSlice(s.conf.AllowedClients),
|
||||||
|
DisallowedClients: aghstrings.CloneSlice(s.conf.DisallowedClients),
|
||||||
|
BlockedHosts: aghstrings.CloneSlice(s.conf.BlockedHosts),
|
||||||
}
|
}
|
||||||
s.RUnlock()
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
j := s.accessListJSON()
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
err := json.NewEncoder(w).Encode(j)
|
err := json.NewEncoder(w).Encode(j)
|
||||||
|
@ -200,14 +205,16 @@ func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Lock()
|
defer log.Debug("Access: updated lists: %d, %d, %d",
|
||||||
|
len(j.AllowedClients), len(j.DisallowedClients), len(j.BlockedHosts))
|
||||||
|
|
||||||
|
defer s.conf.ConfigModified()
|
||||||
|
|
||||||
|
s.serverLock.Lock()
|
||||||
|
defer s.serverLock.Unlock()
|
||||||
|
|
||||||
s.conf.AllowedClients = j.AllowedClients
|
s.conf.AllowedClients = j.AllowedClients
|
||||||
s.conf.DisallowedClients = j.DisallowedClients
|
s.conf.DisallowedClients = j.DisallowedClients
|
||||||
s.conf.BlockedHosts = j.BlockedHosts
|
s.conf.BlockedHosts = j.BlockedHosts
|
||||||
s.access = a
|
s.access = a
|
||||||
s.Unlock()
|
|
||||||
s.conf.ConfigModified()
|
|
||||||
|
|
||||||
log.Debug("Access: updated lists: %d, %d, %d",
|
|
||||||
len(j.AllowedClients), len(j.DisallowedClients), len(j.BlockedHosts))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,6 +153,10 @@ type ServerConfig struct {
|
||||||
// ResolveClients signals if the RDNS should resolve clients' addresses.
|
// ResolveClients signals if the RDNS should resolve clients' addresses.
|
||||||
ResolveClients bool
|
ResolveClients bool
|
||||||
|
|
||||||
|
// UsePrivateRDNS defines if the PTR requests for unknown addresses from
|
||||||
|
// locally-served networks should be resolved via private PTR resolvers.
|
||||||
|
UsePrivateRDNS bool
|
||||||
|
|
||||||
// LocalPTRResolvers is a slice of addresses to be used as upstreams for
|
// LocalPTRResolvers is a slice of addresses to be used as upstreams for
|
||||||
// resolving PTR queries for local addresses.
|
// resolving PTR queries for local addresses.
|
||||||
LocalPTRResolvers []string
|
LocalPTRResolvers []string
|
||||||
|
|
|
@ -414,20 +414,25 @@ func (s *Server) processLocalPTR(ctx *dnsContext) (rc resultCode) {
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.serverLock.RLock()
|
||||||
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
if !s.subnetDetector.IsLocallyServedNetwork(ip) {
|
if !s.subnetDetector.IsLocallyServedNetwork(ip) {
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.localResolvers.Resolve(d)
|
if s.conf.UsePrivateRDNS {
|
||||||
if err != nil {
|
if err := s.localResolvers.Resolve(d); err != nil {
|
||||||
ctx.err = err
|
ctx.err = err
|
||||||
|
|
||||||
return resultCodeError
|
return resultCodeError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Res == nil {
|
if d.Res == nil {
|
||||||
d.Res = s.genNXDomain(d.Req)
|
d.Res = s.genNXDomain(d.Req)
|
||||||
|
|
||||||
|
// Do not even put into query log.
|
||||||
return resultCodeFinish
|
return resultCodeFinish
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,24 +448,20 @@ func processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode) {
|
||||||
return resultCodeSuccess // response is already set - nothing to do
|
return resultCodeSuccess // response is already set - nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
s.RLock()
|
s.serverLock.RLock()
|
||||||
// Synchronize access to s.dnsFilter so it won't be suddenly uninitialized while in use.
|
defer s.serverLock.RUnlock()
|
||||||
// This could happen after proxy server has been stopped, but its workers are not yet exited.
|
|
||||||
//
|
ctx.protectionEnabled = s.conf.ProtectionEnabled && s.dnsFilter != nil
|
||||||
// A better approach is for proxy.Stop() to wait until all its workers exit,
|
if !ctx.protectionEnabled {
|
||||||
// but this would require the Upstream interface to have Close() function
|
return resultCodeSuccess
|
||||||
// (to prevent from hanging while waiting for unresponsive DNS server to respond).
|
}
|
||||||
|
|
||||||
|
if ctx.setts == nil {
|
||||||
|
ctx.setts = s.getClientRequestFilteringSettings(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
ctx.protectionEnabled = s.conf.ProtectionEnabled && s.dnsFilter != nil
|
ctx.result, err = s.filterDNSRequest(ctx)
|
||||||
if ctx.protectionEnabled {
|
|
||||||
if ctx.setts == nil {
|
|
||||||
ctx.setts = s.getClientRequestFilteringSettings(ctx)
|
|
||||||
}
|
|
||||||
ctx.result, err = s.filterDNSRequest(ctx)
|
|
||||||
}
|
|
||||||
s.RUnlock()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.err = err
|
ctx.err = err
|
||||||
|
|
||||||
|
|
|
@ -260,7 +260,7 @@ func TestServer_ProcessInternalHosts(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLocalRestriction(t *testing.T) {
|
func TestServer_ProcessRestrictLocal(t *testing.T) {
|
||||||
ups := &aghtest.TestUpstream{
|
ups := &aghtest.TestUpstream{
|
||||||
Reverse: map[string][]string{
|
Reverse: map[string][]string{
|
||||||
"251.252.253.254.in-addr.arpa.": {"host1.example.net."},
|
"251.252.253.254.in-addr.arpa.": {"host1.example.net."},
|
||||||
|
@ -318,14 +318,64 @@ func TestLocalRestriction(t *testing.T) {
|
||||||
IP: tc.cliIP,
|
IP: tc.cliIP,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err = s.handleDNSRequest(nil, pctx)
|
err = s.handleDNSRequest(nil, pctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, pctx.Res)
|
require.NotNil(t, pctx.Res)
|
||||||
require.Len(t, pctx.Res.Answer, tc.wantLen)
|
require.Len(t, pctx.Res.Answer, tc.wantLen)
|
||||||
|
|
||||||
if tc.wantLen > 0 {
|
if tc.wantLen > 0 {
|
||||||
assert.Equal(t, tc.want, pctx.Res.Answer[0].Header().Name)
|
assert.Equal(t, tc.want, pctx.Res.Answer[0].Header().Name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
|
||||||
|
const locDomain = "some.local."
|
||||||
|
const reqAddr = "1.1.168.192.in-addr.arpa."
|
||||||
|
|
||||||
|
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||||
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
}, &aghtest.TestUpstream{
|
||||||
|
Reverse: map[string][]string{
|
||||||
|
reqAddr: {locDomain},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
var proxyCtx *proxy.DNSContext
|
||||||
|
var dnsCtx *dnsContext
|
||||||
|
setup := func(use bool) {
|
||||||
|
proxyCtx = &proxy.DNSContext{
|
||||||
|
Addr: &net.TCPAddr{
|
||||||
|
IP: net.IP{127, 0, 0, 1},
|
||||||
|
},
|
||||||
|
Req: createTestMessageWithType(reqAddr, dns.TypePTR),
|
||||||
|
}
|
||||||
|
dnsCtx = &dnsContext{
|
||||||
|
proxyCtx: proxyCtx,
|
||||||
|
unreversedReqIP: net.IP{192, 168, 1, 1},
|
||||||
|
}
|
||||||
|
s.conf.UsePrivateRDNS = use
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("enabled", func(t *testing.T) {
|
||||||
|
setup(true)
|
||||||
|
|
||||||
|
rc := s.processLocalPTR(dnsCtx)
|
||||||
|
require.Equal(t, resultCodeSuccess, rc)
|
||||||
|
require.NotEmpty(t, proxyCtx.Res.Answer)
|
||||||
|
|
||||||
|
assert.Equal(t, locDomain, proxyCtx.Res.Answer[0].Header().Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disabled", func(t *testing.T) {
|
||||||
|
setup(false)
|
||||||
|
|
||||||
|
rc := s.processLocalPTR(dnsCtx)
|
||||||
|
require.Equal(t, resultCodeFinish, rc)
|
||||||
|
require.Empty(t, proxyCtx.Res.Answer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -89,8 +89,9 @@ type Server struct {
|
||||||
|
|
||||||
isRunning bool
|
isRunning bool
|
||||||
|
|
||||||
sync.RWMutex
|
|
||||||
conf ServerConfig
|
conf ServerConfig
|
||||||
|
// serverLock protects Server.
|
||||||
|
serverLock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultLocalDomainSuffix is the default suffix used to detect internal hosts
|
// defaultLocalDomainSuffix is the default suffix used to detect internal hosts
|
||||||
|
@ -167,25 +168,31 @@ func NewCustomServer(internalProxy *proxy.Proxy) *Server {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close - close object
|
// 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
|
||||||
|
// its workers finished. But it would require the upstream.Upstream to have the
|
||||||
|
// Close method to prevent from hanging while waiting for unresponsive server to
|
||||||
|
// respond.
|
||||||
func (s *Server) Close() {
|
func (s *Server) Close() {
|
||||||
s.Lock()
|
s.serverLock.Lock()
|
||||||
|
defer s.serverLock.Unlock()
|
||||||
|
|
||||||
s.dnsFilter = nil
|
s.dnsFilter = nil
|
||||||
s.stats = nil
|
s.stats = nil
|
||||||
s.queryLog = nil
|
s.queryLog = nil
|
||||||
s.dnsProxy = nil
|
s.dnsProxy = nil
|
||||||
|
|
||||||
err := s.ipset.Close()
|
if err := s.ipset.Close(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error("closing ipset: %s", err)
|
log.Error("closing ipset: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteDiskConfig - write configuration
|
// WriteDiskConfig - write configuration
|
||||||
func (s *Server) WriteDiskConfig(c *FilteringConfig) {
|
func (s *Server) WriteDiskConfig(c *FilteringConfig) {
|
||||||
s.RLock()
|
s.serverLock.RLock()
|
||||||
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
sc := s.conf.FilteringConfig
|
sc := s.conf.FilteringConfig
|
||||||
*c = sc
|
*c = sc
|
||||||
c.RatelimitWhitelist = aghstrings.CloneSlice(sc.RatelimitWhitelist)
|
c.RatelimitWhitelist = aghstrings.CloneSlice(sc.RatelimitWhitelist)
|
||||||
|
@ -194,15 +201,16 @@ func (s *Server) WriteDiskConfig(c *FilteringConfig) {
|
||||||
c.DisallowedClients = aghstrings.CloneSlice(sc.DisallowedClients)
|
c.DisallowedClients = aghstrings.CloneSlice(sc.DisallowedClients)
|
||||||
c.BlockedHosts = aghstrings.CloneSlice(sc.BlockedHosts)
|
c.BlockedHosts = aghstrings.CloneSlice(sc.BlockedHosts)
|
||||||
c.UpstreamDNS = aghstrings.CloneSlice(sc.UpstreamDNS)
|
c.UpstreamDNS = aghstrings.CloneSlice(sc.UpstreamDNS)
|
||||||
s.RUnlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RDNSSettings returns the copy of actual RDNS configuration.
|
// RDNSSettings returns the copy of actual RDNS configuration.
|
||||||
func (s *Server) RDNSSettings() (localPTRResolvers []string, resolveClients bool) {
|
func (s *Server) RDNSSettings() (localPTRResolvers []string, resolveClients, resolvePTR bool) {
|
||||||
s.RLock()
|
s.serverLock.RLock()
|
||||||
defer s.RUnlock()
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
return aghstrings.CloneSlice(s.conf.LocalPTRResolvers), s.conf.ResolveClients
|
return aghstrings.CloneSlice(s.conf.LocalPTRResolvers),
|
||||||
|
s.conf.ResolveClients,
|
||||||
|
s.conf.UsePrivateRDNS
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve - get IP addresses by host name from an upstream server.
|
// Resolve - get IP addresses by host name from an upstream server.
|
||||||
|
@ -210,8 +218,9 @@ func (s *Server) RDNSSettings() (localPTRResolvers []string, resolveClients bool
|
||||||
// Query log and Stats are not updated.
|
// Query log and Stats are not updated.
|
||||||
// This method may be called before Start().
|
// This method may be called before Start().
|
||||||
func (s *Server) Resolve(host string) ([]net.IPAddr, error) {
|
func (s *Server) Resolve(host string) ([]net.IPAddr, error) {
|
||||||
s.RLock()
|
s.serverLock.RLock()
|
||||||
defer s.RUnlock()
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
return s.internalProxy.LookupIPAddr(host)
|
return s.internalProxy.LookupIPAddr(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +229,9 @@ type RDNSExchanger interface {
|
||||||
// Exchange tries to resolve the ip in a suitable way, e.g. either as
|
// Exchange tries to resolve the ip in a suitable way, e.g. either as
|
||||||
// local or as external.
|
// local or as external.
|
||||||
Exchange(ip net.IP) (host string, err error)
|
Exchange(ip net.IP) (host string, err error)
|
||||||
|
// ResolvesPrivatePTR returns true if the RDNSExchanger is able to
|
||||||
|
// resolve PTR requests for locally-served addresses.
|
||||||
|
ResolvesPrivatePTR() (ok bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -234,13 +246,20 @@ const (
|
||||||
|
|
||||||
// Exchange implements the RDNSExchanger interface for *Server.
|
// Exchange implements the RDNSExchanger interface for *Server.
|
||||||
func (s *Server) Exchange(ip net.IP) (host string, err error) {
|
func (s *Server) Exchange(ip net.IP) (host string, err error) {
|
||||||
s.RLock()
|
s.serverLock.RLock()
|
||||||
defer s.RUnlock()
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
if !s.conf.ResolveClients {
|
if !s.conf.ResolveClients {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resolver *proxy.Proxy = s.localResolvers
|
||||||
|
if !s.subnetDetector.IsLocallyServedNetwork(ip) {
|
||||||
|
resolver = s.internalProxy
|
||||||
|
} else if !s.conf.UsePrivateRDNS {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
arpa := dns.Fqdn(aghnet.ReverseAddr(ip))
|
arpa := dns.Fqdn(aghnet.ReverseAddr(ip))
|
||||||
req := &dns.Msg{
|
req := &dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
|
@ -259,13 +278,8 @@ func (s *Server) Exchange(ip net.IP) (host string, err error) {
|
||||||
Req: req,
|
Req: req,
|
||||||
StartTime: time.Now(),
|
StartTime: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp *dns.Msg
|
var resp *dns.Msg
|
||||||
if s.subnetDetector.IsLocallyServedNetwork(ip) {
|
err = resolver.Resolve(ctx)
|
||||||
err = s.localResolvers.Resolve(ctx)
|
|
||||||
} else {
|
|
||||||
err = s.internalProxy.Resolve(ctx)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -284,10 +298,19 @@ func (s *Server) Exchange(ip net.IP) (host string, err error) {
|
||||||
return strings.TrimSuffix(ptr.Ptr, "."), nil
|
return strings.TrimSuffix(ptr.Ptr, "."), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolvesPrivatePTR implements the RDNSExchanger interface for *Server.
|
||||||
|
func (s *Server) ResolvesPrivatePTR() (ok bool) {
|
||||||
|
s.serverLock.RLock()
|
||||||
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
|
return s.conf.UsePrivateRDNS
|
||||||
|
}
|
||||||
|
|
||||||
// Start starts the DNS server.
|
// Start starts the DNS server.
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
s.Lock()
|
s.serverLock.Lock()
|
||||||
defer s.Unlock()
|
defer s.serverLock.Unlock()
|
||||||
|
|
||||||
return s.startLocked()
|
return s.startLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +329,7 @@ func (s *Server) startLocked() error {
|
||||||
const defaultLocalTimeout = 1 * time.Second
|
const defaultLocalTimeout = 1 * time.Second
|
||||||
|
|
||||||
// collectDNSIPAddrs returns IP addresses the server is listening on without
|
// collectDNSIPAddrs returns IP addresses the server is listening on without
|
||||||
// port numbers as a map. For internal use only.
|
// port numbersю For internal use only.
|
||||||
func (s *Server) collectDNSIPAddrs() (addrs []string, err error) {
|
func (s *Server) collectDNSIPAddrs() (addrs []string, err error) {
|
||||||
addrs = make([]string, len(s.conf.TCPListenAddrs)+len(s.conf.UDPListenAddrs))
|
addrs = make([]string, len(s.conf.TCPListenAddrs)+len(s.conf.UDPListenAddrs))
|
||||||
var i int
|
var i int
|
||||||
|
@ -472,8 +495,9 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
||||||
|
|
||||||
// Stop stops the DNS server.
|
// Stop stops the DNS server.
|
||||||
func (s *Server) Stop() error {
|
func (s *Server) Stop() error {
|
||||||
s.Lock()
|
s.serverLock.Lock()
|
||||||
defer s.Unlock()
|
defer s.serverLock.Unlock()
|
||||||
|
|
||||||
return s.stopLocked()
|
return s.stopLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,17 +514,18 @@ func (s *Server) stopLocked() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRunning returns true if the DNS server is running
|
// IsRunning returns true if the DNS server is running.
|
||||||
func (s *Server) IsRunning() bool {
|
func (s *Server) IsRunning() bool {
|
||||||
s.RLock()
|
s.serverLock.RLock()
|
||||||
defer s.RUnlock()
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
return s.isRunning
|
return s.isRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconfigure applies the new configuration to the DNS server
|
// Reconfigure applies the new configuration to the DNS server.
|
||||||
func (s *Server) Reconfigure(config *ServerConfig) error {
|
func (s *Server) Reconfigure(config *ServerConfig) error {
|
||||||
s.Lock()
|
s.serverLock.Lock()
|
||||||
defer s.Unlock()
|
defer s.serverLock.Unlock()
|
||||||
|
|
||||||
log.Print("Start reconfiguring the server")
|
log.Print("Start reconfiguring the server")
|
||||||
err := s.stopLocked()
|
err := s.stopLocked()
|
||||||
|
@ -525,12 +550,18 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP is a HTTP handler method we use to provide DNS-over-HTTPS
|
// ServeHTTP is a HTTP handler method we use to provide DNS-over-HTTPS.
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
s.RLock()
|
var p *proxy.Proxy
|
||||||
p := s.dnsProxy
|
|
||||||
s.RUnlock()
|
func() {
|
||||||
if p != nil { // an attempt to protect against race in case we're here after Close() was called
|
s.serverLock.RLock()
|
||||||
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
|
p = s.dnsProxy
|
||||||
|
}()
|
||||||
|
|
||||||
|
if p != nil {
|
||||||
p.ServeHTTP(w, r)
|
p.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,11 +86,12 @@ func createTestServer(
|
||||||
err = s.Prepare(nil)
|
err = s.Prepare(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
s.Lock()
|
s.serverLock.Lock()
|
||||||
defer s.Unlock()
|
defer s.serverLock.Unlock()
|
||||||
|
|
||||||
if localUps != nil {
|
if localUps != nil {
|
||||||
s.localResolvers.Config.UpstreamConfig.Upstreams = []upstream.Upstream{localUps}
|
s.localResolvers.Config.UpstreamConfig.Upstreams = []upstream.Upstream{localUps}
|
||||||
|
s.conf.UsePrivateRDNS = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
@ -1207,17 +1208,18 @@ func TestServer_Exchange(t *testing.T) {
|
||||||
Block: true,
|
Block: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
dns := NewCustomServer(&proxy.Proxy{
|
srv := NewCustomServer(&proxy.Proxy{
|
||||||
Config: proxy.Config{
|
Config: proxy.Config{
|
||||||
UpstreamConfig: &proxy.UpstreamConfig{
|
UpstreamConfig: &proxy.UpstreamConfig{
|
||||||
Upstreams: []upstream.Upstream{extUpstream},
|
Upstreams: []upstream.Upstream{extUpstream},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
dns.conf.ResolveClients = true
|
srv.conf.ResolveClients = true
|
||||||
|
srv.conf.UsePrivateRDNS = true
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
dns.subnetDetector, err = aghnet.NewSubnetDetector()
|
srv.subnetDetector, err = aghnet.NewSubnetDetector()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
localIP := net.IP{192, 168, 1, 1}
|
localIP := net.IP{192, 168, 1, 1}
|
||||||
|
@ -1265,12 +1267,12 @@ func TestServer_Exchange(t *testing.T) {
|
||||||
Upstreams: []upstream.Upstream{tc.locUpstream},
|
Upstreams: []upstream.Upstream{tc.locUpstream},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
dns.localResolvers = &proxy.Proxy{
|
srv.localResolvers = &proxy.Proxy{
|
||||||
Config: pcfg,
|
Config: pcfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
host, eerr := dns.Exchange(tc.req)
|
host, eerr := srv.Exchange(tc.req)
|
||||||
|
|
||||||
require.ErrorIs(t, eerr, tc.wantErr)
|
require.ErrorIs(t, eerr, tc.wantErr)
|
||||||
assert.Equal(t, tc.want, host)
|
assert.Equal(t, tc.want, host)
|
||||||
|
@ -1278,12 +1280,11 @@ func TestServer_Exchange(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("resolving_disabled", func(t *testing.T) {
|
t.Run("resolving_disabled", func(t *testing.T) {
|
||||||
dns.conf.ResolveClients = false
|
srv.conf.UsePrivateRDNS = false
|
||||||
for _, tc := range testCases {
|
|
||||||
host, eerr := dns.Exchange(tc.req)
|
|
||||||
|
|
||||||
require.NoError(t, eerr)
|
host, eerr := srv.Exchange(localIP)
|
||||||
assert.Empty(t, host)
|
|
||||||
}
|
require.NoError(t, eerr)
|
||||||
|
assert.Empty(t, host)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,8 +117,31 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*filtering.Result, error) {
|
||||||
return &res, err
|
return &res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If response contains CNAME, A or AAAA records, we apply filtering to each canonical host name or IP address.
|
// checkHostRules checks the host against filters. It is safe for concurrent
|
||||||
// If this is a match, we set a new response in d.Res and return.
|
// use.
|
||||||
|
func (s *Server) checkHostRules(host string, qtype uint16, setts *filtering.Settings) (
|
||||||
|
r *filtering.Result,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
s.serverLock.RLock()
|
||||||
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
|
if s.dnsFilter == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var res filtering.Result
|
||||||
|
res, err = s.dnsFilter.CheckHostRules(host, qtype, setts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If response contains CNAME, A or AAAA records, we apply filtering to each
|
||||||
|
// canonical host name or IP address. If this is a match, we set a new response
|
||||||
|
// in d.Res and return.
|
||||||
func (s *Server) filterDNSResponse(ctx *dnsContext) (*filtering.Result, error) {
|
func (s *Server) filterDNSResponse(ctx *dnsContext) (*filtering.Result, error) {
|
||||||
d := ctx.proxyCtx
|
d := ctx.proxyCtx
|
||||||
for _, a := range d.Res.Answer {
|
for _, a := range d.Res.Answer {
|
||||||
|
@ -141,22 +164,16 @@ func (s *Server) filterDNSResponse(ctx *dnsContext) (*filtering.Result, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s.RLock()
|
res, err := s.checkHostRules(host, d.Req.Question[0].Qtype, ctx.setts)
|
||||||
// Synchronize access to s.dnsFilter so it won't be suddenly uninitialized while in use.
|
|
||||||
// This could happen after proxy server has been stopped, but its workers are not yet exited.
|
|
||||||
if !s.conf.ProtectionEnabled || s.dnsFilter == nil {
|
|
||||||
s.RUnlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
res, err := s.dnsFilter.CheckHostRules(host, d.Req.Question[0].Qtype, ctx.setts)
|
|
||||||
s.RUnlock()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if res == nil {
|
||||||
|
continue
|
||||||
} else if res.IsFiltered {
|
} else if res.IsFiltered {
|
||||||
d.Res = s.genDNSFilterMessage(d, &res)
|
d.Res = s.genDNSFilterMessage(d, res)
|
||||||
log.Debug("DNSFwd: Matched %s by response: %s", d.Req.Question[0].Name, host)
|
log.Debug("DNSFwd: Matched %s by response: %s", d.Req.Question[0].Name, host)
|
||||||
return &res, nil
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,12 +41,13 @@ type dnsConfig struct {
|
||||||
CacheMinTTL *uint32 `json:"cache_ttl_min"`
|
CacheMinTTL *uint32 `json:"cache_ttl_min"`
|
||||||
CacheMaxTTL *uint32 `json:"cache_ttl_max"`
|
CacheMaxTTL *uint32 `json:"cache_ttl_max"`
|
||||||
ResolveClients *bool `json:"resolve_clients"`
|
ResolveClients *bool `json:"resolve_clients"`
|
||||||
|
UsePrivateRDNS *bool `json:"use_private_ptr_resolvers"`
|
||||||
LocalPTRUpstreams *[]string `json:"local_ptr_upstreams"`
|
LocalPTRUpstreams *[]string `json:"local_ptr_upstreams"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getDNSConfig() dnsConfig {
|
func (s *Server) getDNSConfig() dnsConfig {
|
||||||
s.RLock()
|
s.serverLock.RLock()
|
||||||
defer s.RUnlock()
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
upstreams := aghstrings.CloneSliceOrEmpty(s.conf.UpstreamDNS)
|
upstreams := aghstrings.CloneSliceOrEmpty(s.conf.UpstreamDNS)
|
||||||
upstreamFile := s.conf.UpstreamDNSFileName
|
upstreamFile := s.conf.UpstreamDNSFileName
|
||||||
|
@ -63,6 +64,7 @@ func (s *Server) getDNSConfig() dnsConfig {
|
||||||
cacheMinTTL := s.conf.CacheMinTTL
|
cacheMinTTL := s.conf.CacheMinTTL
|
||||||
cacheMaxTTL := s.conf.CacheMaxTTL
|
cacheMaxTTL := s.conf.CacheMaxTTL
|
||||||
resolveClients := s.conf.ResolveClients
|
resolveClients := s.conf.ResolveClients
|
||||||
|
usePrivateRDNS := s.conf.UsePrivateRDNS
|
||||||
localPTRUpstreams := aghstrings.CloneSliceOrEmpty(s.conf.LocalPTRResolvers)
|
localPTRUpstreams := aghstrings.CloneSliceOrEmpty(s.conf.LocalPTRResolvers)
|
||||||
var upstreamMode string
|
var upstreamMode string
|
||||||
if s.conf.FastestAddr {
|
if s.conf.FastestAddr {
|
||||||
|
@ -88,6 +90,7 @@ func (s *Server) getDNSConfig() dnsConfig {
|
||||||
CacheMaxTTL: &cacheMaxTTL,
|
CacheMaxTTL: &cacheMaxTTL,
|
||||||
UpstreamMode: &upstreamMode,
|
UpstreamMode: &upstreamMode,
|
||||||
ResolveClients: &resolveClients,
|
ResolveClients: &resolveClients,
|
||||||
|
UsePrivateRDNS: &usePrivateRDNS,
|
||||||
LocalPTRUpstreams: &localPTRUpstreams,
|
LocalPTRUpstreams: &localPTRUpstreams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,8 +283,8 @@ func (s *Server) setConfigRestartable(dc dnsConfig) (restart bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setConfig(dc dnsConfig) (restart bool) {
|
func (s *Server) setConfig(dc dnsConfig) (restart bool) {
|
||||||
s.Lock()
|
s.serverLock.Lock()
|
||||||
defer s.Unlock()
|
defer s.serverLock.Unlock()
|
||||||
|
|
||||||
if dc.ProtectionEnabled != nil {
|
if dc.ProtectionEnabled != nil {
|
||||||
s.conf.ProtectionEnabled = *dc.ProtectionEnabled
|
s.conf.ProtectionEnabled = *dc.ProtectionEnabled
|
||||||
|
@ -312,6 +315,10 @@ func (s *Server) setConfig(dc dnsConfig) (restart bool) {
|
||||||
s.conf.ResolveClients = *dc.ResolveClients
|
s.conf.ResolveClients = *dc.ResolveClients
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dc.UsePrivateRDNS != nil {
|
||||||
|
s.conf.UsePrivateRDNS = *dc.UsePrivateRDNS
|
||||||
|
}
|
||||||
|
|
||||||
return s.setConfigRestartable(dc)
|
return s.setConfigRestartable(dc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,9 @@ func processQueryLogsAndStats(ctx *dnsContext) (rc resultCode) {
|
||||||
shouldLog = false
|
shouldLog = false
|
||||||
}
|
}
|
||||||
|
|
||||||
s.RLock()
|
s.serverLock.RLock()
|
||||||
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
// Synchronize access to s.queryLog and s.stats so they won't be suddenly uninitialized while in use.
|
// Synchronize access to s.queryLog and s.stats so they won't be suddenly uninitialized while in use.
|
||||||
// This can happen after proxy server has been stopped, but its workers haven't yet exited.
|
// This can happen after proxy server has been stopped, but its workers haven't yet exited.
|
||||||
if shouldLog && s.queryLog != nil {
|
if shouldLog && s.queryLog != nil {
|
||||||
|
@ -61,7 +63,6 @@ func processQueryLogsAndStats(ctx *dnsContext) (rc resultCode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.updateStats(ctx, elapsed, *ctx.result)
|
s.updateStats(ctx, elapsed, *ctx.result)
|
||||||
s.RUnlock()
|
|
||||||
|
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
},
|
},
|
||||||
"fastest_addr": {
|
"fastest_addr": {
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
},
|
},
|
||||||
"parallel": {
|
"parallel": {
|
||||||
|
@ -78,6 +80,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -31,6 +31,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -62,6 +63,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -94,6 +96,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -126,6 +129,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -158,6 +162,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -190,6 +195,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -222,6 +228,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -254,6 +261,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -286,6 +294,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -318,6 +327,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -352,6 +362,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -386,6 +397,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -419,6 +431,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -451,6 +464,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -485,6 +499,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": [
|
"local_ptr_upstreams": [
|
||||||
"123.123.123.123"
|
"123.123.123.123"
|
||||||
]
|
]
|
||||||
|
@ -519,6 +534,7 @@
|
||||||
"cache_ttl_min": 0,
|
"cache_ttl_min": 0,
|
||||||
"cache_ttl_max": 0,
|
"cache_ttl_max": 0,
|
||||||
"resolve_clients": false,
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
"local_ptr_upstreams": []
|
"local_ptr_upstreams": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,8 @@ type Client struct {
|
||||||
type clientSource uint
|
type clientSource uint
|
||||||
|
|
||||||
// Client sources. The order determines the priority.
|
// Client sources. The order determines the priority.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Is ARP a higher priority source than DHCP?
|
||||||
const (
|
const (
|
||||||
ClientSourceWHOIS clientSource = iota
|
ClientSourceWHOIS clientSource = iota
|
||||||
ClientSourceRDNS
|
ClientSourceRDNS
|
||||||
|
|
|
@ -108,6 +108,10 @@ type dnsConfig struct {
|
||||||
// ResolveClients enables and disables resolving clients with RDNS.
|
// ResolveClients enables and disables resolving clients with RDNS.
|
||||||
ResolveClients bool `yaml:"resolve_clients"`
|
ResolveClients bool `yaml:"resolve_clients"`
|
||||||
|
|
||||||
|
// UsePrivateRDNS defines if the PTR requests for unknown addresses from
|
||||||
|
// locally-served networks should be resolved via private PTR resolvers.
|
||||||
|
UsePrivateRDNS bool `yaml:"use_private_ptr_resolvers"`
|
||||||
|
|
||||||
// LocalPTRResolvers is the slice of addresses to be used as upstreams
|
// LocalPTRResolvers is the slice of addresses to be used as upstreams
|
||||||
// for PTR queries for locally-served networks.
|
// for PTR queries for locally-served networks.
|
||||||
LocalPTRResolvers []string `yaml:"local_ptr_upstreams"`
|
LocalPTRResolvers []string `yaml:"local_ptr_upstreams"`
|
||||||
|
@ -166,6 +170,7 @@ var config = configuration{
|
||||||
FiltersUpdateIntervalHours: 24,
|
FiltersUpdateIntervalHours: 24,
|
||||||
LocalDomainName: "lan",
|
LocalDomainName: "lan",
|
||||||
ResolveClients: true,
|
ResolveClients: true,
|
||||||
|
UsePrivateRDNS: true,
|
||||||
},
|
},
|
||||||
TLS: tlsConfigSettings{
|
TLS: tlsConfigSettings{
|
||||||
PortHTTPS: 443,
|
PortHTTPS: 443,
|
||||||
|
@ -314,9 +319,11 @@ func (c *configuration) write() error {
|
||||||
if s := Context.dnsServer; s != nil {
|
if s := Context.dnsServer; s != nil {
|
||||||
c := dnsforward.FilteringConfig{}
|
c := dnsforward.FilteringConfig{}
|
||||||
s.WriteDiskConfig(&c)
|
s.WriteDiskConfig(&c)
|
||||||
config.DNS.FilteringConfig = c
|
dns := &config.DNS
|
||||||
|
dns.FilteringConfig = c
|
||||||
config.DNS.LocalPTRResolvers, config.DNS.ResolveClients = s.RDNSSettings()
|
dns.LocalPTRResolvers,
|
||||||
|
dns.ResolveClients,
|
||||||
|
dns.UsePrivateRDNS = s.RDNSSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
if Context.dhcpServer != nil {
|
if Context.dhcpServer != nil {
|
||||||
|
|
|
@ -94,7 +94,7 @@ func initDNSServer() error {
|
||||||
return fmt.Errorf("dnsServer.Prepare: %w", err)
|
return fmt.Errorf("dnsServer.Prepare: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.rdns = NewRDNS(Context.dnsServer, &Context.clients)
|
Context.rdns = NewRDNS(Context.dnsServer, &Context.clients, config.DNS.UsePrivateRDNS)
|
||||||
Context.whois = initWhois(&Context.clients)
|
Context.whois = initWhois(&Context.clients)
|
||||||
|
|
||||||
Context.filters.Init()
|
Context.filters.Init()
|
||||||
|
@ -200,6 +200,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||||
newConf.GetCustomUpstreamByClient = Context.clients.FindUpstreams
|
newConf.GetCustomUpstreamByClient = Context.clients.FindUpstreams
|
||||||
|
|
||||||
newConf.ResolveClients = dnsConf.ResolveClients
|
newConf.ResolveClients = dnsConf.ResolveClients
|
||||||
|
newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS
|
||||||
newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
|
newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
|
||||||
|
|
||||||
return newConf, nil
|
return newConf, nil
|
||||||
|
|
|
@ -3,6 +3,7 @@ package home
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
|
@ -15,6 +16,10 @@ type RDNS struct {
|
||||||
exchanger dnsforward.RDNSExchanger
|
exchanger dnsforward.RDNSExchanger
|
||||||
clients *clientsContainer
|
clients *clientsContainer
|
||||||
|
|
||||||
|
// usePrivate is used to store the state of current private RDNS
|
||||||
|
// resolving settings and to react to it's changes.
|
||||||
|
usePrivate uint32
|
||||||
|
|
||||||
// ipCh used to pass client's IP to rDNS workerLoop.
|
// ipCh used to pass client's IP to rDNS workerLoop.
|
||||||
ipCh chan net.IP
|
ipCh chan net.IP
|
||||||
|
|
||||||
|
@ -37,6 +42,7 @@ const (
|
||||||
func NewRDNS(
|
func NewRDNS(
|
||||||
exchanger dnsforward.RDNSExchanger,
|
exchanger dnsforward.RDNSExchanger,
|
||||||
clients *clientsContainer,
|
clients *clientsContainer,
|
||||||
|
usePrivate bool,
|
||||||
) (rDNS *RDNS) {
|
) (rDNS *RDNS) {
|
||||||
rDNS = &RDNS{
|
rDNS = &RDNS{
|
||||||
exchanger: exchanger,
|
exchanger: exchanger,
|
||||||
|
@ -47,19 +53,39 @@ func NewRDNS(
|
||||||
}),
|
}),
|
||||||
ipCh: make(chan net.IP, defaultRDNSIPChSize),
|
ipCh: make(chan net.IP, defaultRDNSIPChSize),
|
||||||
}
|
}
|
||||||
|
if usePrivate {
|
||||||
|
rDNS.usePrivate = 1
|
||||||
|
}
|
||||||
|
|
||||||
go rDNS.workerLoop()
|
go rDNS.workerLoop()
|
||||||
|
|
||||||
return rDNS
|
return rDNS
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin adds the ip to the resolving queue if it is not cached or already
|
// ensurePrivateCache ensures that the state of the RDNS cache is consistent
|
||||||
// resolved.
|
// with the current private client RDNS resolving settings.
|
||||||
func (r *RDNS) Begin(ip net.IP) {
|
//
|
||||||
|
// TODO(e.burkov): Clearing cache each time this value changed is not a perfect
|
||||||
|
// approach since only unresolved locally-served addresses should be removed.
|
||||||
|
// Implement when improving the cache.
|
||||||
|
func (r *RDNS) ensurePrivateCache() {
|
||||||
|
var usePrivate uint32
|
||||||
|
if r.exchanger.ResolvesPrivatePTR() {
|
||||||
|
usePrivate = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if atomic.CompareAndSwapUint32(&r.usePrivate, 1-usePrivate, usePrivate) {
|
||||||
|
r.ipCache.Clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isCached returns true if ip is already cached and not expired yet. It also
|
||||||
|
// caches it otherwise.
|
||||||
|
func (r *RDNS) isCached(ip net.IP) (ok bool) {
|
||||||
now := uint64(time.Now().Unix())
|
now := uint64(time.Now().Unix())
|
||||||
if expire := r.ipCache.Get(ip); len(expire) != 0 {
|
if expire := r.ipCache.Get(ip); len(expire) != 0 {
|
||||||
if binary.BigEndian.Uint64(expire) > now {
|
if binary.BigEndian.Uint64(expire) > now {
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +94,18 @@ func (r *RDNS) Begin(ip net.IP) {
|
||||||
binary.BigEndian.PutUint64(ttl, now+defaultRDNSCacheTTL)
|
binary.BigEndian.PutUint64(ttl, now+defaultRDNSCacheTTL)
|
||||||
r.ipCache.Set(ip, ttl)
|
r.ipCache.Set(ip, ttl)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin adds the ip to the resolving queue if it is not cached or already
|
||||||
|
// resolved.
|
||||||
|
func (r *RDNS) Begin(ip net.IP) {
|
||||||
|
r.ensurePrivateCache()
|
||||||
|
|
||||||
|
if r.isCached(ip) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
id := ip.String()
|
id := ip.String()
|
||||||
if r.clients.Exists(id, ClientSourceRDNS) {
|
if r.clients.Exists(id, ClientSourceRDNS) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRDNS_Begin(t *testing.T) {
|
func TestRDNS_Begin(t *testing.T) {
|
||||||
|
@ -78,7 +79,8 @@ func TestRDNS_Begin(t *testing.T) {
|
||||||
binary.BigEndian.PutUint64(ttl, uint64(time.Now().Add(100*time.Hour).Unix()))
|
binary.BigEndian.PutUint64(ttl, uint64(time.Now().Add(100*time.Hour).Unix()))
|
||||||
|
|
||||||
rdns := &RDNS{
|
rdns := &RDNS{
|
||||||
ipCache: ipCache,
|
ipCache: ipCache,
|
||||||
|
exchanger: &rDNSExchanger{},
|
||||||
clients: &clientsContainer{
|
clients: &clientsContainer{
|
||||||
list: map[string]*Client{},
|
list: map[string]*Client{},
|
||||||
idIndex: tc.cliIDIndex,
|
idIndex: tc.cliIDIndex,
|
||||||
|
@ -105,7 +107,8 @@ func TestRDNS_Begin(t *testing.T) {
|
||||||
|
|
||||||
// rDNSExchanger is a mock dnsforward.RDNSExchanger implementation for tests.
|
// rDNSExchanger is a mock dnsforward.RDNSExchanger implementation for tests.
|
||||||
type rDNSExchanger struct {
|
type rDNSExchanger struct {
|
||||||
aghtest.Exchanger
|
ex aghtest.Exchanger
|
||||||
|
usePrivate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange implements dnsforward.RDNSExchanger interface for *RDNSExchanger.
|
// Exchange implements dnsforward.RDNSExchanger interface for *RDNSExchanger.
|
||||||
|
@ -117,7 +120,7 @@ func (e *rDNSExchanger) Exchange(ip net.IP) (host string, err error) {
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := e.Exchanger.Exchange(req)
|
resp, err := e.ex.Exchange(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -129,6 +132,35 @@ func (e *rDNSExchanger) Exchange(ip net.IP) (host string, err error) {
|
||||||
return resp.Answer[0].Header().Name, nil
|
return resp.Answer[0].Header().Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exchange implements dnsforward.RDNSExchanger interface for *RDNSExchanger.
|
||||||
|
func (e *rDNSExchanger) ResolvesPrivatePTR() (ok bool) {
|
||||||
|
return e.usePrivate
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRDNS_ensurePrivateCache(t *testing.T) {
|
||||||
|
data := []byte{1, 2, 3, 4}
|
||||||
|
|
||||||
|
ipCache := cache.New(cache.Config{
|
||||||
|
EnableLRU: true,
|
||||||
|
MaxCount: defaultRDNSCacheSize,
|
||||||
|
})
|
||||||
|
|
||||||
|
ex := &rDNSExchanger{}
|
||||||
|
|
||||||
|
rdns := &RDNS{
|
||||||
|
ipCache: ipCache,
|
||||||
|
exchanger: ex,
|
||||||
|
}
|
||||||
|
|
||||||
|
rdns.ipCache.Set(data, data)
|
||||||
|
require.NotZero(t, rdns.ipCache.Stats().Count)
|
||||||
|
|
||||||
|
ex.usePrivate = !ex.usePrivate
|
||||||
|
|
||||||
|
rdns.ensurePrivateCache()
|
||||||
|
require.Zero(t, rdns.ipCache.Stats().Count)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRDNS_WorkerLoop(t *testing.T) {
|
func TestRDNS_WorkerLoop(t *testing.T) {
|
||||||
aghtest.ReplaceLogLevel(t, log.DEBUG)
|
aghtest.ReplaceLogLevel(t, log.DEBUG)
|
||||||
w := &bytes.Buffer{}
|
w := &bytes.Buffer{}
|
||||||
|
@ -178,7 +210,7 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||||
ch := make(chan net.IP)
|
ch := make(chan net.IP)
|
||||||
rdns := &RDNS{
|
rdns := &RDNS{
|
||||||
exchanger: &rDNSExchanger{
|
exchanger: &rDNSExchanger{
|
||||||
Exchanger: aghtest.Exchanger{
|
ex: aghtest.Exchanger{
|
||||||
Ups: tc.ups,
|
Ups: tc.ups,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,11 +2,18 @@
|
||||||
|
|
||||||
<!-- TODO(a.garipov): Reformat in accordance with the KeepAChangelog spec. -->
|
<!-- TODO(a.garipov): Reformat in accordance with the KeepAChangelog spec. -->
|
||||||
|
|
||||||
|
## v0.107: API changes
|
||||||
|
|
||||||
|
### The field `"use_private_ptr_resolvers"` in DNS configuration
|
||||||
|
|
||||||
|
* The new optional field `"use_private_ptr_resolvers"` of `"DNSConfig"`
|
||||||
|
specifies if the DNS server should use `"local_ptr_upstreams"` at all.
|
||||||
|
|
||||||
## v0.106: API changes
|
## v0.106: API changes
|
||||||
|
|
||||||
### The field `"supported_tags"` in `GET /control/clients`
|
### The field `"supported_tags"` in `GET /control/clients`
|
||||||
|
|
||||||
* Prefiously undocumented field `"supported_tags"` in the response is now
|
* Previously undocumented field `"supported_tags"` in the response is now
|
||||||
documented.
|
documented.
|
||||||
|
|
||||||
### The field `"whois_info"` in `GET /control/clients`
|
### The field `"whois_info"` in `GET /control/clients`
|
||||||
|
|
|
@ -1300,6 +1300,8 @@
|
||||||
- ''
|
- ''
|
||||||
- 'parallel'
|
- 'parallel'
|
||||||
- 'fastest_addr'
|
- 'fastest_addr'
|
||||||
|
'use_private_ptr_resolvers':
|
||||||
|
'type': 'boolean'
|
||||||
'resolve_clients':
|
'resolve_clients':
|
||||||
'type': 'boolean'
|
'type': 'boolean'
|
||||||
'local_ptr_upstreams':
|
'local_ptr_upstreams':
|
||||||
|
|
Loading…
Reference in New Issue