Pull request: 2704 local addresses vol.3
Merge in DNS/adguard-home from 2704-local-addresses-vol.3 to master Updates #2704. Updates #2829. Updates #2928. Squashed commit of the following: commit 8c42355c0093a3ac6951f79a5211e7891800f93a Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Apr 7 18:07:41 2021 +0300 dnsforward: rm errors pkg commit 7594a21a620239951039454dd5686a872e6f41a8 Merge: 830b0834908452f8
Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Apr 7 18:00:03 2021 +0300 Merge branch 'master' into 2704-local-addresses-vol.3 commit 830b0834090510096061fed20b600195ab3773b8 Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Apr 7 17:47:51 2021 +0300 dnsforward: reduce local upstream timeout commit 493e81d9e8bacdc690f88af29a38d211b9733c7e Author: Ildar Kamalov <ik@adguard.com> Date: Tue Apr 6 19:11:00 2021 +0300 client: private_upstream test commit a0194ac28f15114578359b8c2460cd9af621e912 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Apr 6 18:36:23 2021 +0300 all: expand api, fix conflicts commit 0f4e06836fed958391aa597c8b02453564980ca3 Merge: 89cf93ad8746005d
Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Apr 6 18:35:04 2021 +0300 Merge branch 'master' into 2704-local-addresses-vol.3 commit 89cf93ad4f26c2bf4f1b18ecaa4d3a1e169f9b06 Author: Ildar Kamalov <ik@adguard.com> Date: Tue Apr 6 18:02:40 2021 +0300 client: add local ptr upstreams to upstream test commit e6dd869dddd4888474d625cbb005bad6390e4760 Author: Ildar Kamalov <ik@adguard.com> Date: Tue Apr 6 15:24:22 2021 +0300 client: add private DNS form commit b858057b9a957a416117f22b8bd0025f90e8c758 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Apr 6 13:05:28 2021 +0300 aghstrings: mk cloning correct commit 8009ba60a6a7d6ceb7b6483a29f4e68d533af243 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Apr 6 12:37:46 2021 +0300 aghstrings: fix lil bug commit 0dd19f2e7cc7c0de21517c37abd8336a907e1c0d Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 20:45:01 2021 +0300 all: log changes commit eb5558d96fffa6e7bca7e14d3740d26e47382e23 Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 20:18:53 2021 +0300 dnsforward: keep the style commit d6d5fcbde40a633129c0e04887b81cf0b1ce6875 Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 20:02:52 2021 +0300 dnsforward: disable redundant filtering for local ptr commit 4f864c32027d10db9bcb4a264d2338df8c20afac Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 17:53:17 2021 +0300 dnsforward: imp tests commit 7848e6f2341868f8ba0bb839956a0b7444cf02ca Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 14:52:12 2021 +0300 all: imp code commit 19ac30653800eebf8aaee499f65560ae2d458a5a Author: Eugene Burkov <e.burkov@adguard.com> Date: Sun Apr 4 16:28:05 2021 +0300 all: mv more logic to aghstrings commit fac892ec5f0d2e30d6d64def0609267bbae4a202 Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Apr 2 20:23:23 2021 +0300 dnsforward: use filepath commit 05a3aeef1181b914788d14c7519287d467ab301f Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Apr 2 20:17:54 2021 +0300 aghstrings: introduce the pkg commit f24e1b63d6e1bf266a4ed063f46f86d7abf65663 Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Apr 2 20:01:23 2021 +0300 all: imp code commit 0217a0ebb341f99a90c9b68013bebf6ff73d08ae Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Apr 2 18:04:13 2021 +0300 openapi: log changes ... and 3 more commits
This commit is contained in:
parent
908452f883
commit
80ed8be145
|
@ -15,6 +15,9 @@ and this project adheres to
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- The ability to set up custom upstreams to resolve PTR queries for local
|
||||||
|
addresses and to disable the automatic resolving of clients' addresses
|
||||||
|
([#2704]).
|
||||||
- Logging of the client's IP address after failed login attempts ([#2824]).
|
- Logging of the client's IP address after failed login attempts ([#2824]).
|
||||||
- Search by clients' names in the query log ([#1273]).
|
- Search by clients' names in the query log ([#1273]).
|
||||||
- Verbose version output with `-v --version` ([#2416]).
|
- Verbose version output with `-v --version` ([#2416]).
|
||||||
|
|
|
@ -8,6 +8,11 @@
|
||||||
"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_desc": "The DNS server or servers that AdGuard Home will use for queries for locally served resources. For instance, this server will be used for resolving clients' hostnames for the clients with private IP addresses. If not set, AdGuard Home will automatically use your default DNS resolver.",
|
||||||
|
"local_ptr_placeholder": "Enter one server address per line",
|
||||||
|
"resolve_clients_title": "Enable clients' hostnames resolution",
|
||||||
|
"resolve_clients_desc": "If enabled, AdGuard Home will attempt to automatically resolve clients' hostnames from their IP addresses by sending a PTR query to a corresponding resolver (private DNS server for local clients, upstream server for clients with public IP).",
|
||||||
"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",
|
||||||
|
|
|
@ -33,6 +33,10 @@ export const setDnsConfig = (config) => async (dispatch) => {
|
||||||
data.bootstrap_dns = splitByNewLine(config.bootstrap_dns);
|
data.bootstrap_dns = splitByNewLine(config.bootstrap_dns);
|
||||||
hasDnsSettings = true;
|
hasDnsSettings = true;
|
||||||
}
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(data, 'local_ptr_upstreams')) {
|
||||||
|
data.local_ptr_upstreams = splitByNewLine(config.local_ptr_upstreams);
|
||||||
|
hasDnsSettings = true;
|
||||||
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(data, 'upstream_dns')) {
|
if (Object.prototype.hasOwnProperty.call(data, 'upstream_dns')) {
|
||||||
data.upstream_dns = splitByNewLine(config.upstream_dns);
|
data.upstream_dns = splitByNewLine(config.upstream_dns);
|
||||||
hasDnsSettings = true;
|
hasDnsSettings = true;
|
||||||
|
|
|
@ -296,7 +296,7 @@ export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE');
|
||||||
export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS');
|
export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS');
|
||||||
|
|
||||||
export const testUpstream = (
|
export const testUpstream = (
|
||||||
{ bootstrap_dns, upstream_dns }, upstream_dns_file,
|
{ bootstrap_dns, upstream_dns, local_ptr_upstreams }, upstream_dns_file,
|
||||||
) => async (dispatch) => {
|
) => async (dispatch) => {
|
||||||
dispatch(testUpstreamRequest());
|
dispatch(testUpstreamRequest());
|
||||||
try {
|
try {
|
||||||
|
@ -304,6 +304,7 @@ export const testUpstream = (
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
bootstrap_dns: splitByNewLine(bootstrap_dns),
|
bootstrap_dns: splitByNewLine(bootstrap_dns),
|
||||||
|
private_upstream: splitByNewLine(local_ptr_upstreams),
|
||||||
...(upstream_dns_file ? null : {
|
...(upstream_dns_file ? null : {
|
||||||
upstream_dns: removeComments(upstream_dns),
|
upstream_dns: removeComments(upstream_dns),
|
||||||
}),
|
}),
|
||||||
|
@ -332,8 +333,17 @@ export const testUpstream = (
|
||||||
|
|
||||||
export const testUpstreamWithFormValues = () => async (dispatch, getState) => {
|
export const testUpstreamWithFormValues = () => async (dispatch, getState) => {
|
||||||
const { upstream_dns_file } = getState().dnsConfig;
|
const { upstream_dns_file } = getState().dnsConfig;
|
||||||
const { bootstrap_dns, upstream_dns } = getState().form[FORM_NAME.UPSTREAM].values;
|
const {
|
||||||
return dispatch(testUpstream({ bootstrap_dns, upstream_dns }, upstream_dns_file));
|
bootstrap_dns,
|
||||||
|
upstream_dns,
|
||||||
|
local_ptr_upstreams,
|
||||||
|
} = getState().form[FORM_NAME.UPSTREAM].values;
|
||||||
|
|
||||||
|
return dispatch(testUpstream({
|
||||||
|
bootstrap_dns,
|
||||||
|
upstream_dns,
|
||||||
|
local_ptr_upstreams,
|
||||||
|
}, upstream_dns_file));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const changeLanguageRequest = createAction('CHANGE_LANGUAGE_REQUEST');
|
export const changeLanguageRequest = createAction('CHANGE_LANGUAGE_REQUEST');
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Field, reduxForm } from 'redux-form';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Examples from './Examples';
|
import Examples from './Examples';
|
||||||
import { renderRadioField, renderTextareaField } from '../../../../helpers/form';
|
import { renderRadioField, renderTextareaField, CheckboxField } from '../../../../helpers/form';
|
||||||
import {
|
import {
|
||||||
DNS_REQUEST_OPTIONS,
|
DNS_REQUEST_OPTIONS,
|
||||||
FORM_NAME,
|
FORM_NAME,
|
||||||
|
@ -28,11 +28,12 @@ const renderField = ({
|
||||||
const processingTestUpstream = useSelector((state) => state.settings.processingTestUpstream);
|
const processingTestUpstream = useSelector((state) => state.settings.processingTestUpstream);
|
||||||
const processingSetConfig = useSelector((state) => state.dnsConfig.processingSetConfig);
|
const processingSetConfig = useSelector((state) => state.dnsConfig.processingSetConfig);
|
||||||
|
|
||||||
return <div
|
return (
|
||||||
|
<div
|
||||||
key={placeholder}
|
key={placeholder}
|
||||||
className={classnames('col-12 mb-4', containerClass)}
|
className={classnames('col-12 mb-4', containerClass)}
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
id={name}
|
id={name}
|
||||||
value={value}
|
value={value}
|
||||||
name={name}
|
name={name}
|
||||||
|
@ -44,8 +45,9 @@ const renderField = ({
|
||||||
disabled={processingSetConfig || processingTestUpstream}
|
disabled={processingSetConfig || processingTestUpstream}
|
||||||
normalizeOnBlur={normalizeOnBlur}
|
normalizeOnBlur={normalizeOnBlur}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderField.propTypes = {
|
renderField.propTypes = {
|
||||||
|
@ -160,10 +162,10 @@ const Form = ({
|
||||||
{' '}
|
{' '}
|
||||||
<Trans components={[
|
<Trans components={[
|
||||||
<a
|
<a
|
||||||
href="https://kb.adguard.com/general/dns-providers"
|
href="https://kb.adguard.com/general/dns-providers"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
key="0"
|
key="0"
|
||||||
>
|
>
|
||||||
DNS providers
|
DNS providers
|
||||||
</a>,
|
</a>,
|
||||||
|
@ -197,6 +199,40 @@ const Form = ({
|
||||||
normalizeOnBlur={removeEmptyLines}
|
normalizeOnBlur={removeEmptyLines}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-12">
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<div className="col-12 mb-4">
|
||||||
|
<label
|
||||||
|
className="form__label form__label--with-desc"
|
||||||
|
htmlFor="local_ptr"
|
||||||
|
>
|
||||||
|
<Trans>local_ptr_title</Trans>
|
||||||
|
</label>
|
||||||
|
<div className="form__desc form__desc--top">
|
||||||
|
<Trans>local_ptr_desc</Trans>
|
||||||
|
</div>
|
||||||
|
<Field
|
||||||
|
id="local_ptr_upstreams"
|
||||||
|
name="local_ptr_upstreams"
|
||||||
|
component={renderTextareaField}
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control--textarea form-control--textarea-small font-monospace"
|
||||||
|
placeholder={t('local_ptr_placeholder')}
|
||||||
|
disabled={processingSetConfig}
|
||||||
|
normalizeOnBlur={removeEmptyLines}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 mb-4">
|
||||||
|
<Field
|
||||||
|
name="resolve_clients"
|
||||||
|
type="checkbox"
|
||||||
|
component={CheckboxField}
|
||||||
|
placeholder={t('resolve_clients_title')}
|
||||||
|
subtitle={t('resolve_clients_desc')}
|
||||||
|
disabled={processingSetConfig}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-actions">
|
<div className="card-actions">
|
||||||
<div className="btn-list">
|
<div className="btn-list">
|
||||||
|
|
|
@ -12,6 +12,8 @@ const Upstream = () => {
|
||||||
upstream_dns,
|
upstream_dns,
|
||||||
bootstrap_dns,
|
bootstrap_dns,
|
||||||
upstream_mode,
|
upstream_mode,
|
||||||
|
resolve_clients,
|
||||||
|
local_ptr_upstreams,
|
||||||
} = 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);
|
||||||
|
@ -21,11 +23,15 @@ const Upstream = () => {
|
||||||
bootstrap_dns,
|
bootstrap_dns,
|
||||||
upstream_dns,
|
upstream_dns,
|
||||||
upstream_mode,
|
upstream_mode,
|
||||||
|
resolve_clients,
|
||||||
|
local_ptr_upstreams,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
const dnsConfig = {
|
const dnsConfig = {
|
||||||
bootstrap_dns,
|
bootstrap_dns,
|
||||||
upstream_mode,
|
upstream_mode,
|
||||||
|
resolve_clients,
|
||||||
|
local_ptr_upstreams,
|
||||||
...(upstream_dns_file ? null : { upstream_dns }),
|
...(upstream_dns_file ? null : { upstream_dns }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,6 +51,8 @@ const Upstream = () => {
|
||||||
upstream_dns: upstreamDns,
|
upstream_dns: upstreamDns,
|
||||||
bootstrap_dns,
|
bootstrap_dns,
|
||||||
upstream_mode,
|
upstream_mode,
|
||||||
|
resolve_clients,
|
||||||
|
local_ptr_upstreams,
|
||||||
}}
|
}}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,6 +16,7 @@ const dnsConfig = handleActions(
|
||||||
blocking_ipv6,
|
blocking_ipv6,
|
||||||
upstream_dns,
|
upstream_dns,
|
||||||
bootstrap_dns,
|
bootstrap_dns,
|
||||||
|
local_ptr_upstreams,
|
||||||
...values
|
...values
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ const dnsConfig = handleActions(
|
||||||
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
|
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
|
||||||
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
|
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
|
||||||
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
|
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
|
||||||
|
local_ptr_upstreams: (local_ptr_upstreams && local_ptr_upstreams.join('\n')) || '',
|
||||||
processingGetConfig: false,
|
processingGetConfig: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -46,7 +46,8 @@ func (e *manyError) Error() (msg string) {
|
||||||
b := &strings.Builder{}
|
b := &strings.Builder{}
|
||||||
|
|
||||||
// Ignore errors, since strings.(*Buffer).Write never returns
|
// Ignore errors, since strings.(*Buffer).Write never returns
|
||||||
// errors.
|
// errors. We don't use aghstrings.WriteToBuilder here since
|
||||||
|
// this package should be importable for any other.
|
||||||
_, _ = fmt.Fprintf(b, "%s: %s (hidden: %s", e.message, e.underlying[0], e.underlying[1])
|
_, _ = fmt.Fprintf(b, "%s: %s (hidden: %s", e.message, e.underlying[0], e.underlying[1])
|
||||||
for _, u := range e.underlying[2:] {
|
for _, u := range e.underlying[2:] {
|
||||||
// See comment above.
|
// See comment above.
|
||||||
|
|
|
@ -31,7 +31,11 @@ type multiAddrExchanger struct {
|
||||||
|
|
||||||
// NewMultiAddrExchanger creates an Exchanger instance from passed addresses.
|
// NewMultiAddrExchanger creates an Exchanger instance from passed addresses.
|
||||||
// It returns an error if any of addrs failed to become an upstream.
|
// It returns an error if any of addrs failed to become an upstream.
|
||||||
func NewMultiAddrExchanger(addrs []string, timeout time.Duration) (e Exchanger, err error) {
|
func NewMultiAddrExchanger(
|
||||||
|
addrs []string,
|
||||||
|
bootstraps []string,
|
||||||
|
timeout time.Duration,
|
||||||
|
) (e Exchanger, err error) {
|
||||||
defer agherr.Annotate("exchanger: %w", &err)
|
defer agherr.Annotate("exchanger: %w", &err)
|
||||||
|
|
||||||
if len(addrs) == 0 {
|
if len(addrs) == 0 {
|
||||||
|
@ -41,7 +45,10 @@ func NewMultiAddrExchanger(addrs []string, timeout time.Duration) (e Exchanger,
|
||||||
var ups []upstream.Upstream = make([]upstream.Upstream, 0, len(addrs))
|
var ups []upstream.Upstream = make([]upstream.Upstream, 0, len(addrs))
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
var u upstream.Upstream
|
var u upstream.Upstream
|
||||||
u, err = upstream.AddressToUpstream(addr, upstream.Options{Timeout: timeout})
|
u, err = upstream.AddressToUpstream(addr, upstream.Options{
|
||||||
|
Bootstrap: bootstraps,
|
||||||
|
Timeout: timeout,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,19 +15,19 @@ func TestNewMultiAddrExchanger(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
t.Run("empty", func(t *testing.T) {
|
t.Run("empty", func(t *testing.T) {
|
||||||
e, err = NewMultiAddrExchanger([]string{}, 0)
|
e, err = NewMultiAddrExchanger([]string{}, nil, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, e)
|
assert.NotNil(t, e)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("successful", func(t *testing.T) {
|
t.Run("successful", func(t *testing.T) {
|
||||||
e, err = NewMultiAddrExchanger([]string{"www.example.com"}, 0)
|
e, err = NewMultiAddrExchanger([]string{"www.example.com"}, nil, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, e)
|
assert.NotNil(t, e)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("unsuccessful", func(t *testing.T) {
|
t.Run("unsuccessful", func(t *testing.T) {
|
||||||
e, err = NewMultiAddrExchanger([]string{"invalid-proto://www.example.com"}, 0)
|
e, err = NewMultiAddrExchanger([]string{"invalid-proto://www.example.com"}, nil, 0)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -355,30 +356,30 @@ const (
|
||||||
// (PTR) record lookups. This is the modified version of ReverseAddr from
|
// (PTR) record lookups. This is the modified version of ReverseAddr from
|
||||||
// github.com/miekg/dns package with no error among returned values.
|
// github.com/miekg/dns package with no error among returned values.
|
||||||
func ReverseAddr(ip net.IP) (arpa string) {
|
func ReverseAddr(ip net.IP) (arpa string) {
|
||||||
|
const dot = "."
|
||||||
|
|
||||||
var strLen int
|
var strLen int
|
||||||
var suffix string
|
var suffix string
|
||||||
// Don't handle errors in implementations since strings.WriteString
|
|
||||||
// never returns non-nil errors.
|
|
||||||
var writeByte func(val byte)
|
var writeByte func(val byte)
|
||||||
b := &strings.Builder{}
|
b := &strings.Builder{}
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
strLen, suffix = arpaV4MaxLen, arpaV4Suffix[1:]
|
strLen, suffix = arpaV4MaxLen, arpaV4Suffix[1:]
|
||||||
ip = ip4
|
ip = ip4
|
||||||
writeByte = func(val byte) {
|
writeByte = func(val byte) {
|
||||||
_, _ = b.WriteString(strconv.Itoa(int(val)))
|
aghstrings.WriteToBuilder(b, strconv.Itoa(int(val)), dot)
|
||||||
_, _ = b.WriteRune('.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ip6 := ip.To16(); ip6 != nil {
|
} else if ip6 := ip.To16(); ip6 != nil {
|
||||||
strLen, suffix = arpaV6MaxLen, arpaV6Suffix[1:]
|
strLen, suffix = arpaV6MaxLen, arpaV6Suffix[1:]
|
||||||
ip = ip6
|
ip = ip6
|
||||||
writeByte = func(val byte) {
|
writeByte = func(val byte) {
|
||||||
lByte, rByte := val&0xF, val>>4
|
aghstrings.WriteToBuilder(
|
||||||
|
b,
|
||||||
_, _ = b.WriteString(strconv.FormatUint(uint64(lByte), 16))
|
strconv.FormatUint(uint64(val&0xF), 16),
|
||||||
_, _ = b.WriteRune('.')
|
dot,
|
||||||
_, _ = b.WriteString(strconv.FormatUint(uint64(rByte), 16))
|
strconv.FormatUint(uint64(val>>4), 16),
|
||||||
_, _ = b.WriteRune('.')
|
dot,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -389,7 +390,38 @@ func ReverseAddr(ip net.IP) (arpa string) {
|
||||||
for i := len(ip) - 1; i >= 0; i-- {
|
for i := len(ip) - 1; i >= 0; i-- {
|
||||||
writeByte(ip[i])
|
writeByte(ip[i])
|
||||||
}
|
}
|
||||||
_, _ = b.WriteString(suffix)
|
aghstrings.WriteToBuilder(b, suffix)
|
||||||
|
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectAllIfacesAddrs returns the slice of all network interfaces IP
|
||||||
|
// addresses without port number.
|
||||||
|
func CollectAllIfacesAddrs() (addrs []string, err error) {
|
||||||
|
var ifaces []net.Interface
|
||||||
|
ifaces, err = net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting network interfaces: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
var ifaceAddrs []net.Addr
|
||||||
|
ifaceAddrs, err = iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting addresses for %q: %w", iface.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range ifaceAddrs {
|
||||||
|
cidr := addr.String()
|
||||||
|
var ip net.IP
|
||||||
|
ip, _, err = net.ParseCIDR(cidr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing cidr: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs = append(addrs, ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Package aghstrings contains utilities dealing with strings.
|
||||||
|
package aghstrings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CloneSliceOrEmpty returns the copy of a or empty strings slice if a is nil.
|
||||||
|
func CloneSliceOrEmpty(a []string) (b []string) {
|
||||||
|
return append([]string{}, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneSlice returns the exact copy of a.
|
||||||
|
func CloneSlice(a []string) (b []string) {
|
||||||
|
if a == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return CloneSliceOrEmpty(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InSlice checks if string is in the slice of strings.
|
||||||
|
func InSlice(strs []string, str string) (ok bool) {
|
||||||
|
for _, s := range strs {
|
||||||
|
if s == str {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitNext splits string by a byte and returns the first chunk skipping empty
|
||||||
|
// ones. Whitespaces are trimmed.
|
||||||
|
func SplitNext(s *string, sep rune) (chunk string) {
|
||||||
|
if s == nil {
|
||||||
|
return chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
i := strings.IndexByte(*s, byte(sep))
|
||||||
|
if i == -1 {
|
||||||
|
chunk = *s
|
||||||
|
*s = ""
|
||||||
|
|
||||||
|
return strings.TrimSpace(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk = (*s)[:i]
|
||||||
|
*s = (*s)[i+1:]
|
||||||
|
var j int
|
||||||
|
var r rune
|
||||||
|
for j, r = range *s {
|
||||||
|
if r != sep {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = (*s)[j:]
|
||||||
|
|
||||||
|
return strings.TrimSpace(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToBuilder is a convenient wrapper for strings.(*Builder).WriteString
|
||||||
|
// that deals with multiple strings and ignores errors that are guaranteed to be
|
||||||
|
// nil.
|
||||||
|
func WriteToBuilder(b *strings.Builder, strs ...string) {
|
||||||
|
// TODO(e.burkov): Recover from panic?
|
||||||
|
for _, s := range strs {
|
||||||
|
_, _ = b.WriteString(s)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
package aghstrings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCloneSlice_family(t *testing.T) {
|
||||||
|
a := []string{"1", "2", "3"}
|
||||||
|
|
||||||
|
t.Run("cloneslice_simple", func(t *testing.T) {
|
||||||
|
assert.Equal(t, a, CloneSlice(a))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cloneslice_nil", func(t *testing.T) {
|
||||||
|
assert.Nil(t, CloneSlice(nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cloneslice_empty", func(t *testing.T) {
|
||||||
|
assert.Equal(t, []string{}, CloneSlice([]string{}))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("clonesliceorempty_nil", func(t *testing.T) {
|
||||||
|
assert.Equal(t, []string{}, CloneSliceOrEmpty(nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("clonesliceorempty_empty", func(t *testing.T) {
|
||||||
|
assert.Equal(t, []string{}, CloneSliceOrEmpty([]string{}))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("clonesliceorempty_sameness", func(t *testing.T) {
|
||||||
|
assert.Equal(t, CloneSlice(a), CloneSliceOrEmpty(a))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInSlice(t *testing.T) {
|
||||||
|
simpleStrs := []string{"1", "2", "3"}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
str string
|
||||||
|
strs []string
|
||||||
|
want bool
|
||||||
|
}{{
|
||||||
|
name: "yes",
|
||||||
|
str: "2",
|
||||||
|
strs: simpleStrs,
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "no",
|
||||||
|
str: "4",
|
||||||
|
strs: simpleStrs,
|
||||||
|
want: false,
|
||||||
|
}, {
|
||||||
|
name: "nil",
|
||||||
|
str: "any",
|
||||||
|
strs: nil,
|
||||||
|
want: false,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.want, InSlice(tc.strs, tc.str))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitNext(t *testing.T) {
|
||||||
|
t.Run("ordinary", func(t *testing.T) {
|
||||||
|
s := " a,b , c "
|
||||||
|
require.Equal(t, "a", SplitNext(&s, ','))
|
||||||
|
require.Equal(t, "b", SplitNext(&s, ','))
|
||||||
|
require.Equal(t, "c", SplitNext(&s, ','))
|
||||||
|
|
||||||
|
assert.Empty(t, s)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil_source", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "", SplitNext(nil, 's'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteToBuilder(t *testing.T) {
|
||||||
|
b := &strings.Builder{}
|
||||||
|
|
||||||
|
t.Run("single", func(t *testing.T) {
|
||||||
|
assert.NotPanics(t, func() { WriteToBuilder(b, t.Name()) })
|
||||||
|
assert.Equal(t, t.Name(), b.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
t.Run("several", func(t *testing.T) {
|
||||||
|
const (
|
||||||
|
_1 = "one"
|
||||||
|
_2 = "two"
|
||||||
|
_123 = _1 + _2
|
||||||
|
)
|
||||||
|
assert.NotPanics(t, func() { WriteToBuilder(b, _1, _2) })
|
||||||
|
assert.Equal(t, _123, b.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
t.Run("nothing", func(t *testing.T) {
|
||||||
|
assert.NotPanics(t, func() { WriteToBuilder(b) })
|
||||||
|
assert.Equal(t, "", b.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil_builder", func(t *testing.T) {
|
||||||
|
assert.Panics(t, func() { WriteToBuilder(nil, "a") })
|
||||||
|
})
|
||||||
|
}
|
|
@ -11,10 +11,10 @@ type Exchanger struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange implements aghnet.Exchanger interface for *Exchanger.
|
// Exchange implements aghnet.Exchanger interface for *Exchanger.
|
||||||
func (lr *Exchanger) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
|
func (e *Exchanger) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||||
if lr.Ups == nil {
|
if e.Ups == nil {
|
||||||
lr.Ups = &TestErrUpstream{}
|
e.Ups = &TestErrUpstream{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lr.Ups.Exchange(req)
|
return e.Ups.Exchange(req)
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ type Config struct {
|
||||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
|
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
|
||||||
|
|
||||||
// CustomResolver is the resolver used by DNSFilter.
|
// CustomResolver is the resolver used by DNSFilter.
|
||||||
CustomResolver Resolver
|
CustomResolver Resolver `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupStats store stats collected during safebrowsing or parental checks
|
// LookupStats store stats collected during safebrowsing or parental checks
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/cache"
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
@ -181,26 +182,21 @@ func hostnameToHashes(host string) map[[32]byte]string {
|
||||||
// convert hash array to string
|
// convert hash array to string
|
||||||
func (c *sbCtx) getQuestion() string {
|
func (c *sbCtx) getQuestion() string {
|
||||||
b := &strings.Builder{}
|
b := &strings.Builder{}
|
||||||
encoder := hex.NewEncoder(b)
|
|
||||||
|
|
||||||
for hash := range c.hashToHost {
|
for hash := range c.hashToHost {
|
||||||
// Ignore errors, since strings.(*Buffer).Write never returns
|
|
||||||
// errors.
|
|
||||||
//
|
|
||||||
// TODO(e.burkov, a.garipov): Find out and document why exactly
|
// TODO(e.burkov, a.garipov): Find out and document why exactly
|
||||||
// this slice.
|
// this slice.
|
||||||
_, _ = encoder.Write(hash[0:2])
|
aghstrings.WriteToBuilder(b, hex.EncodeToString(hash[0:2]), ".")
|
||||||
_, _ = b.WriteRune('.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.svc == "SafeBrowsing" {
|
if c.svc == "SafeBrowsing" {
|
||||||
// See comment above.
|
aghstrings.WriteToBuilder(b, sbTXTSuffix)
|
||||||
_, _ = b.WriteString(sbTXTSuffix)
|
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// See comment above.
|
aghstrings.WriteToBuilder(b, pcTXTSuffix)
|
||||||
_, _ = b.WriteString(pcTXTSuffix)
|
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/urlfilter"
|
"github.com/AdguardTeam/urlfilter"
|
||||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||||
|
@ -36,16 +37,15 @@ func (a *accessCtx) Init(allowedClients, disallowedClients, blockedHosts []strin
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := strings.Builder{}
|
b := &strings.Builder{}
|
||||||
for _, s := range blockedHosts {
|
for _, s := range blockedHosts {
|
||||||
buf.WriteString(s)
|
aghstrings.WriteToBuilder(b, s, "\n")
|
||||||
buf.WriteString("\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listArray := []filterlist.RuleList{}
|
listArray := []filterlist.RuleList{}
|
||||||
list := &filterlist.StringRuleList{
|
list := &filterlist.StringRuleList{
|
||||||
ID: int(0),
|
ID: int(0),
|
||||||
RulesText: buf.String(),
|
RulesText: b.String(),
|
||||||
IgnoreCosmetic: true,
|
IgnoreCosmetic: true,
|
||||||
}
|
}
|
||||||
listArray = append(listArray, list)
|
listArray = append(listArray, list)
|
||||||
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
@ -149,6 +149,13 @@ type ServerConfig struct {
|
||||||
|
|
||||||
// Register an HTTP handler
|
// Register an HTTP handler
|
||||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request))
|
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request))
|
||||||
|
|
||||||
|
// ResolveClients signals if the RDNS should resolve clients' addresses.
|
||||||
|
ResolveClients bool
|
||||||
|
|
||||||
|
// LocalPTRResolvers is a slice of addresses to be used as upstreams for
|
||||||
|
// resolving PTR queries for local addresses.
|
||||||
|
LocalPTRResolvers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// if any of ServerConfig values are zero, then default values from below are used
|
// if any of ServerConfig values are zero, then default values from below are used
|
||||||
|
@ -274,7 +281,7 @@ func (s *Server) prepareUpstreamSettings() error {
|
||||||
}
|
}
|
||||||
d := string(data)
|
d := string(data)
|
||||||
for len(d) != 0 {
|
for len(d) != 0 {
|
||||||
s := util.SplitNext(&d, '\n')
|
s := aghstrings.SplitNext(&d, '\n')
|
||||||
upstreams = append(upstreams, s)
|
upstreams = append(upstreams, s)
|
||||||
}
|
}
|
||||||
log.Debug("dns: using %d upstream servers from file %s", len(upstreams), s.conf.UpstreamDNSFileName)
|
log.Debug("dns: using %d upstream servers from file %s", len(upstreams), s.conf.UpstreamDNSFileName)
|
||||||
|
|
|
@ -293,6 +293,14 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
||||||
// Do not perform unreversing ever again.
|
// Do not perform unreversing ever again.
|
||||||
ctx.unreversedReqIP = ip
|
ctx.unreversedReqIP = ip
|
||||||
|
|
||||||
|
// Disable redundant filtering.
|
||||||
|
filterSetts := s.getClientRequestFilteringSettings(ctx)
|
||||||
|
filterSetts.ParentalEnabled = false
|
||||||
|
filterSetts.SafeBrowsingEnabled = false
|
||||||
|
filterSetts.SafeSearchEnabled = false
|
||||||
|
filterSetts.ServicesRules = nil
|
||||||
|
ctx.setts = filterSetts
|
||||||
|
|
||||||
// Nothing to restrict.
|
// Nothing to restrict.
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
@ -405,15 +413,19 @@ func processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode) {
|
||||||
var err error
|
var err error
|
||||||
ctx.protectionEnabled = s.conf.ProtectionEnabled && s.dnsFilter != nil
|
ctx.protectionEnabled = s.conf.ProtectionEnabled && s.dnsFilter != nil
|
||||||
if ctx.protectionEnabled {
|
if ctx.protectionEnabled {
|
||||||
ctx.setts = s.getClientRequestFilteringSettings(ctx)
|
if ctx.setts == nil {
|
||||||
|
ctx.setts = s.getClientRequestFilteringSettings(ctx)
|
||||||
|
}
|
||||||
ctx.result, err = s.filterDNSRequest(ctx)
|
ctx.result, err = s.filterDNSRequest(ctx)
|
||||||
}
|
}
|
||||||
s.RUnlock()
|
s.RUnlock()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.err = err
|
ctx.err = err
|
||||||
|
|
||||||
return resultCodeError
|
return resultCodeError
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
|
@ -92,7 +95,6 @@ type DNSCreateParams struct {
|
||||||
QueryLog querylog.QueryLog
|
QueryLog querylog.QueryLog
|
||||||
DHCPServer dhcpd.ServerInterface
|
DHCPServer dhcpd.ServerInterface
|
||||||
SubnetDetector *aghnet.SubnetDetector
|
SubnetDetector *aghnet.SubnetDetector
|
||||||
LocalResolvers aghnet.Exchanger
|
|
||||||
AutohostTLD string
|
AutohostTLD string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +129,6 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
||||||
stats: p.Stats,
|
stats: p.Stats,
|
||||||
queryLog: p.QueryLog,
|
queryLog: p.QueryLog,
|
||||||
subnetDetector: p.SubnetDetector,
|
subnetDetector: p.SubnetDetector,
|
||||||
localResolvers: p.LocalResolvers,
|
|
||||||
autohostSuffix: autohostSuffix,
|
autohostSuffix: autohostSuffix,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,15 +177,23 @@ func (s *Server) WriteDiskConfig(c *FilteringConfig) {
|
||||||
s.RLock()
|
s.RLock()
|
||||||
sc := s.conf.FilteringConfig
|
sc := s.conf.FilteringConfig
|
||||||
*c = sc
|
*c = sc
|
||||||
c.RatelimitWhitelist = stringArrayDup(sc.RatelimitWhitelist)
|
c.RatelimitWhitelist = aghstrings.CloneSlice(sc.RatelimitWhitelist)
|
||||||
c.BootstrapDNS = stringArrayDup(sc.BootstrapDNS)
|
c.BootstrapDNS = aghstrings.CloneSlice(sc.BootstrapDNS)
|
||||||
c.AllowedClients = stringArrayDup(sc.AllowedClients)
|
c.AllowedClients = aghstrings.CloneSlice(sc.AllowedClients)
|
||||||
c.DisallowedClients = stringArrayDup(sc.DisallowedClients)
|
c.DisallowedClients = aghstrings.CloneSlice(sc.DisallowedClients)
|
||||||
c.BlockedHosts = stringArrayDup(sc.BlockedHosts)
|
c.BlockedHosts = aghstrings.CloneSlice(sc.BlockedHosts)
|
||||||
c.UpstreamDNS = stringArrayDup(sc.UpstreamDNS)
|
c.UpstreamDNS = aghstrings.CloneSlice(sc.UpstreamDNS)
|
||||||
s.RUnlock()
|
s.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RDNSSettings returns the copy of actual RDNS configuration.
|
||||||
|
func (s *Server) RDNSSettings() (localPTRResolvers []string, resolveClients bool) {
|
||||||
|
s.RLock()
|
||||||
|
defer s.RUnlock()
|
||||||
|
|
||||||
|
return aghstrings.CloneSlice(s.conf.LocalPTRResolvers), s.conf.ResolveClients
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve - get IP addresses by host name from an upstream server.
|
// Resolve - get IP addresses by host name from an upstream server.
|
||||||
// No request/response filtering is performed.
|
// No request/response filtering is performed.
|
||||||
// Query log and Stats are not updated.
|
// Query log and Stats are not updated.
|
||||||
|
@ -195,24 +204,73 @@ func (s *Server) Resolve(host string) ([]net.IPAddr, error) {
|
||||||
return s.internalProxy.LookupIPAddr(host)
|
return s.internalProxy.LookupIPAddr(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange - send DNS request to an upstream server and receive response
|
// RDNSExchanger is a resolver for clients' addresses.
|
||||||
// No request/response filtering is performed.
|
type RDNSExchanger interface {
|
||||||
// Query log and Stats are not updated.
|
// Exchange tries to resolve the ip in a suitable way, e.g. either as
|
||||||
// This method may be called before Start().
|
// local or as external.
|
||||||
func (s *Server) Exchange(req *dns.Msg) (*dns.Msg, error) {
|
Exchange(ip net.IP) (host string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// rDNSEmptyAnswerErr is returned by Exchange method when the answer
|
||||||
|
// section of respond is empty.
|
||||||
|
rDNSEmptyAnswerErr agherr.Error = "the answer section is empty"
|
||||||
|
|
||||||
|
// rDNSNotPTRErr is returned by Exchange method when the response is not
|
||||||
|
// of PTR type.
|
||||||
|
rDNSNotPTRErr agherr.Error = "the response is not a ptr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exchange implements the RDNSExchanger interface for *Server.
|
||||||
|
func (s *Server) Exchange(ip net.IP) (host string, err error) {
|
||||||
s.RLock()
|
s.RLock()
|
||||||
defer s.RUnlock()
|
defer s.RUnlock()
|
||||||
|
|
||||||
ctx := &proxy.DNSContext{
|
if !s.conf.ResolveClients {
|
||||||
Proto: "udp",
|
return "", nil
|
||||||
Req: req,
|
}
|
||||||
StartTime: time.Now(),
|
|
||||||
|
arpa := dns.Fqdn(aghnet.ReverseAddr(ip))
|
||||||
|
req := &dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: dns.Id(),
|
||||||
|
RecursionDesired: true,
|
||||||
|
},
|
||||||
|
Compress: true,
|
||||||
|
Question: []dns.Question{{
|
||||||
|
Name: arpa,
|
||||||
|
Qtype: dns.TypePTR,
|
||||||
|
Qclass: dns.ClassINET,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp *dns.Msg
|
||||||
|
if s.subnetDetector.IsLocallyServedNetwork(ip) {
|
||||||
|
resp, err = s.localResolvers.Exchange(req)
|
||||||
|
} else {
|
||||||
|
ctx := &proxy.DNSContext{
|
||||||
|
Proto: "udp",
|
||||||
|
Req: req,
|
||||||
|
StartTime: time.Now(),
|
||||||
|
}
|
||||||
|
err = s.internalProxy.Resolve(ctx)
|
||||||
|
|
||||||
|
resp = ctx.Res
|
||||||
}
|
}
|
||||||
err := s.internalProxy.Resolve(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
return ctx.Res, nil
|
|
||||||
|
if len(resp.Answer) == 0 {
|
||||||
|
return "", fmt.Errorf("lookup for %q: %w", arpa, rDNSEmptyAnswerErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr, ok := resp.Answer[0].(*dns.PTR)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("type checking: %w", rDNSNotPTRErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSuffix(ptr.Ptr, "."), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the DNS server.
|
// Start starts the DNS server.
|
||||||
|
@ -231,6 +289,110 @@ func (s *Server) startLocked() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultLocalTimeout is the default timeout for resolving addresses from
|
||||||
|
// locally-served networks. It is assumed that local resolvers should work much
|
||||||
|
// faster than ordinary upstreams.
|
||||||
|
const defaultLocalTimeout = 1 * time.Second
|
||||||
|
|
||||||
|
// collectDNSIPAddrs returns the slice of IP addresses without port number which
|
||||||
|
// we are listening on. For internal use only.
|
||||||
|
func (s *Server) collectDNSIPAddrs() (addrs []string, err error) {
|
||||||
|
addrs = make([]string, len(s.conf.TCPListenAddrs)+len(s.conf.UDPListenAddrs))
|
||||||
|
var i int
|
||||||
|
var ip net.IP
|
||||||
|
for _, addr := range s.conf.TCPListenAddrs {
|
||||||
|
if addr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip = addr.IP; ip.IsUnspecified() {
|
||||||
|
return aghnet.CollectAllIfacesAddrs()
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs[i] = ip.String()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for _, addr := range s.conf.UDPListenAddrs {
|
||||||
|
if addr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip = addr.IP; ip.IsUnspecified() {
|
||||||
|
return aghnet.CollectAllIfacesAddrs()
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs[i] = ip.String()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs[:i], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringSetSubtract subtracts b from a interpreted as sets.
|
||||||
|
func stringSetSubtract(a, b []string) (c []string) {
|
||||||
|
// unit is an object to be used as value in set.
|
||||||
|
type unit = struct{}
|
||||||
|
|
||||||
|
cSet := make(map[string]unit)
|
||||||
|
for _, k := range a {
|
||||||
|
cSet[k] = unit{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range b {
|
||||||
|
delete(cSet, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
c = make([]string, len(cSet))
|
||||||
|
i := 0
|
||||||
|
for k := range cSet {
|
||||||
|
c[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupResolvers initializes the resolvers for local addresses. For internal
|
||||||
|
// use only.
|
||||||
|
func (s *Server) setupResolvers(localAddrs []string) (err error) {
|
||||||
|
bootstraps := s.conf.BootstrapDNS
|
||||||
|
if len(localAddrs) == 0 {
|
||||||
|
var sysRes aghnet.SystemResolvers
|
||||||
|
// TODO(e.burkov): Enable the refresher after the actual
|
||||||
|
// implementation passes the public testing.
|
||||||
|
sysRes, err = aghnet.NewSystemResolvers(0, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localAddrs = sysRes.Get()
|
||||||
|
bootstraps = nil
|
||||||
|
}
|
||||||
|
log.Debug("upstreams to resolve PTR for local addresses: %v", localAddrs)
|
||||||
|
|
||||||
|
var ourAddrs []string
|
||||||
|
ourAddrs, err = s.collectDNSIPAddrs()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(e.burkov): The approach of subtracting sets of strings
|
||||||
|
// is not really applicable here since in case of listening on
|
||||||
|
// all network interfaces we should check the whole interface's
|
||||||
|
// network to cut off all the loopback addresses as well.
|
||||||
|
localAddrs = stringSetSubtract(localAddrs, ourAddrs)
|
||||||
|
|
||||||
|
if s.localResolvers, err = aghnet.NewMultiAddrExchanger(
|
||||||
|
localAddrs,
|
||||||
|
bootstraps,
|
||||||
|
defaultLocalTimeout,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare the object
|
// Prepare the object
|
||||||
func (s *Server) Prepare(config *ServerConfig) error {
|
func (s *Server) Prepare(config *ServerConfig) error {
|
||||||
// Initialize the server configuration
|
// Initialize the server configuration
|
||||||
|
@ -305,6 +467,12 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
||||||
// Create the main DNS proxy instance
|
// Create the main DNS proxy instance
|
||||||
// --
|
// --
|
||||||
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
|
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
|
||||||
|
|
||||||
|
err = s.setupResolvers(s.conf.LocalPTRResolvers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting up resolvers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
|
@ -73,7 +74,6 @@ func createTestServer(t *testing.T, filterConf *dnsfilter.Config, forwardConf Se
|
||||||
s, err = NewServer(DNSCreateParams{
|
s, err = NewServer(DNSCreateParams{
|
||||||
DNSFilter: f,
|
DNSFilter: f,
|
||||||
SubnetDetector: snd,
|
SubnetDetector: snd,
|
||||||
LocalResolvers: &aghtest.Exchanger{},
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -82,6 +82,11 @@ func createTestServer(t *testing.T, filterConf *dnsfilter.Config, forwardConf Se
|
||||||
err = s.Prepare(nil)
|
err = s.Prepare(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
s.localResolvers = &aghtest.Exchanger{}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -728,7 +733,6 @@ func TestBlockedCustomIP(t *testing.T) {
|
||||||
s, err = NewServer(DNSCreateParams{
|
s, err = NewServer(DNSCreateParams{
|
||||||
DNSFilter: dnsfilter.New(&dnsfilter.Config{}, filters),
|
DNSFilter: dnsfilter.New(&dnsfilter.Config{}, filters),
|
||||||
SubnetDetector: snd,
|
SubnetDetector: snd,
|
||||||
LocalResolvers: &aghtest.Exchanger{},
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -866,7 +870,6 @@ func TestRewrite(t *testing.T) {
|
||||||
s, err = NewServer(DNSCreateParams{
|
s, err = NewServer(DNSCreateParams{
|
||||||
DNSFilter: f,
|
DNSFilter: f,
|
||||||
SubnetDetector: snd,
|
SubnetDetector: snd,
|
||||||
LocalResolvers: &aghtest.Exchanger{},
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -1029,7 +1032,6 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||||
DNSFilter: dnsfilter.New(&dnsfilter.Config{}, nil),
|
DNSFilter: dnsfilter.New(&dnsfilter.Config{}, nil),
|
||||||
DHCPServer: &testDHCP{},
|
DHCPServer: &testDHCP{},
|
||||||
SubnetDetector: snd,
|
SubnetDetector: snd,
|
||||||
LocalResolvers: &aghtest.Exchanger{},
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -1094,7 +1096,6 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
||||||
s, err = NewServer(DNSCreateParams{
|
s, err = NewServer(DNSCreateParams{
|
||||||
DNSFilter: dnsfilter.New(&c, nil),
|
DNSFilter: dnsfilter.New(&c, nil),
|
||||||
SubnetDetector: snd,
|
SubnetDetector: snd,
|
||||||
LocalResolvers: &aghtest.Exchanger{},
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -1164,3 +1165,100 @@ func TestNewServer(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_Exchange(t *testing.T) {
|
||||||
|
extUpstream := &aghtest.TestUpstream{
|
||||||
|
Reverse: map[string][]string{
|
||||||
|
"1.1.1.1.in-addr.arpa.": {"one.one.one.one"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
locUpstream := &aghtest.TestUpstream{
|
||||||
|
Reverse: map[string][]string{
|
||||||
|
"1.1.168.192.in-addr.arpa.": {"local.domain"},
|
||||||
|
"2.1.168.192.in-addr.arpa.": {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
upstreamErr := agherr.Error("upstream error")
|
||||||
|
errUpstream := &aghtest.TestErrUpstream{
|
||||||
|
Err: upstreamErr,
|
||||||
|
}
|
||||||
|
nonPtrUpstream := &aghtest.TestBlockUpstream{
|
||||||
|
Hostname: "some-host",
|
||||||
|
Block: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
dns := NewCustomServer(&proxy.Proxy{
|
||||||
|
Config: proxy.Config{
|
||||||
|
UpstreamConfig: &proxy.UpstreamConfig{
|
||||||
|
Upstreams: []upstream.Upstream{extUpstream},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
dns.conf.ResolveClients = true
|
||||||
|
|
||||||
|
var err error
|
||||||
|
dns.subnetDetector, err = aghnet.NewSubnetDetector()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
localIP := net.IP{192, 168, 1, 1}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
want string
|
||||||
|
wantErr error
|
||||||
|
locUpstream upstream.Upstream
|
||||||
|
req net.IP
|
||||||
|
}{{
|
||||||
|
name: "external_good",
|
||||||
|
want: "one.one.one.one",
|
||||||
|
wantErr: nil,
|
||||||
|
locUpstream: nil,
|
||||||
|
req: net.IP{1, 1, 1, 1},
|
||||||
|
}, {
|
||||||
|
name: "local_good",
|
||||||
|
want: "local.domain",
|
||||||
|
wantErr: nil,
|
||||||
|
locUpstream: locUpstream,
|
||||||
|
req: localIP,
|
||||||
|
}, {
|
||||||
|
name: "upstream_error",
|
||||||
|
want: "",
|
||||||
|
wantErr: upstreamErr,
|
||||||
|
locUpstream: errUpstream,
|
||||||
|
req: localIP,
|
||||||
|
}, {
|
||||||
|
name: "empty_answer_error",
|
||||||
|
want: "",
|
||||||
|
wantErr: rDNSEmptyAnswerErr,
|
||||||
|
locUpstream: locUpstream,
|
||||||
|
req: net.IP{192, 168, 1, 2},
|
||||||
|
}, {
|
||||||
|
name: "not_ptr_error",
|
||||||
|
want: "",
|
||||||
|
wantErr: rDNSNotPTRErr,
|
||||||
|
locUpstream: nonPtrUpstream,
|
||||||
|
req: localIP,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
dns.localResolvers = &aghtest.Exchanger{
|
||||||
|
Ups: tc.locUpstream,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
host, eerr := dns.Exchange(tc.req)
|
||||||
|
|
||||||
|
require.ErrorIs(t, eerr, tc.wantErr)
|
||||||
|
assert.Equal(t, tc.want, host)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("resolving_disabled", func(t *testing.T) {
|
||||||
|
dns.conf.ResolveClients = false
|
||||||
|
for _, tc := range testCases {
|
||||||
|
host, eerr := dns.Exchange(tc.req)
|
||||||
|
|
||||||
|
require.NoError(t, eerr)
|
||||||
|
assert.Empty(t, host)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -42,15 +42,15 @@ func (s *Server) getClientRequestFilteringSettings(ctx *dnsContext) *dnsfilter.F
|
||||||
return &setts
|
return &setts
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterDNSRequest applies the dnsFilter and sets d.Res if the request
|
// filterDNSRequest applies the dnsFilter and sets d.Res if the request was
|
||||||
// was filtered.
|
// filtered.
|
||||||
func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
d := ctx.proxyCtx
|
d := ctx.proxyCtx
|
||||||
// TODO(e.burkov): Consistently use req instead of d.Req since it is
|
// TODO(e.burkov): Consistently use req instead of d.Req since it is
|
||||||
// declared.
|
// declared.
|
||||||
req := d.Req
|
req := d.Req
|
||||||
host := strings.TrimSuffix(req.Question[0].Name, ".")
|
host := strings.TrimSuffix(req.Question[0].Name, ".")
|
||||||
res, err := s.dnsFilter.CheckHost(host, d.Req.Question[0].Qtype, ctx.setts)
|
res, err := s.dnsFilter.CheckHost(host, req.Question[0].Qtype, ctx.setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Return immediately if there's an error
|
// Return immediately if there's an error
|
||||||
return nil, fmt.Errorf("dnsfilter failed to check host %q: %w", host, err)
|
return nil, fmt.Errorf("dnsfilter failed to check host %q: %w", host, err)
|
||||||
|
@ -63,8 +63,8 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
// Resolve the new canonical name, not the original host
|
// Resolve the new canonical name, not the original host
|
||||||
// name. The original question is readded in
|
// name. The original question is readded in
|
||||||
// processFilteringAfterResponse.
|
// processFilteringAfterResponse.
|
||||||
ctx.origQuestion = d.Req.Question[0]
|
ctx.origQuestion = req.Question[0]
|
||||||
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
|
req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||||
} else if res.Reason == dnsfilter.RewrittenAutoHosts && len(res.ReverseHosts) != 0 {
|
} else if res.Reason == dnsfilter.RewrittenAutoHosts && len(res.ReverseHosts) != 0 {
|
||||||
resp := s.makeResponse(req)
|
resp := s.makeResponse(req)
|
||||||
for _, h := range res.ReverseHosts {
|
for _, h := range res.ReverseHosts {
|
||||||
|
@ -84,7 +84,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Res = resp
|
d.Res = resp
|
||||||
} else if res.Reason == dnsfilter.Rewritten || res.Reason == dnsfilter.RewrittenAutoHosts {
|
} else if res.Reason.In(dnsfilter.Rewritten, dnsfilter.RewrittenAutoHosts) {
|
||||||
resp := s.makeResponse(req)
|
resp := s.makeResponse(req)
|
||||||
|
|
||||||
name := host
|
name := host
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
@ -27,59 +28,67 @@ type dnsConfig struct {
|
||||||
UpstreamsFile *string `json:"upstream_dns_file"`
|
UpstreamsFile *string `json:"upstream_dns_file"`
|
||||||
Bootstraps *[]string `json:"bootstrap_dns"`
|
Bootstraps *[]string `json:"bootstrap_dns"`
|
||||||
|
|
||||||
ProtectionEnabled *bool `json:"protection_enabled"`
|
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||||
RateLimit *uint32 `json:"ratelimit"`
|
RateLimit *uint32 `json:"ratelimit"`
|
||||||
BlockingMode *string `json:"blocking_mode"`
|
BlockingMode *string `json:"blocking_mode"`
|
||||||
BlockingIPv4 net.IP `json:"blocking_ipv4"`
|
BlockingIPv4 net.IP `json:"blocking_ipv4"`
|
||||||
BlockingIPv6 net.IP `json:"blocking_ipv6"`
|
BlockingIPv6 net.IP `json:"blocking_ipv6"`
|
||||||
EDNSCSEnabled *bool `json:"edns_cs_enabled"`
|
EDNSCSEnabled *bool `json:"edns_cs_enabled"`
|
||||||
DNSSECEnabled *bool `json:"dnssec_enabled"`
|
DNSSECEnabled *bool `json:"dnssec_enabled"`
|
||||||
DisableIPv6 *bool `json:"disable_ipv6"`
|
DisableIPv6 *bool `json:"disable_ipv6"`
|
||||||
UpstreamMode *string `json:"upstream_mode"`
|
UpstreamMode *string `json:"upstream_mode"`
|
||||||
CacheSize *uint32 `json:"cache_size"`
|
CacheSize *uint32 `json:"cache_size"`
|
||||||
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"`
|
||||||
|
LocalPTRUpstreams *[]string `json:"local_ptr_upstreams"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getDNSConfig() dnsConfig {
|
func (s *Server) getDNSConfig() dnsConfig {
|
||||||
s.RLock()
|
s.RLock()
|
||||||
upstreams := stringArrayDup(s.conf.UpstreamDNS)
|
defer s.RUnlock()
|
||||||
|
|
||||||
|
upstreams := aghstrings.CloneSliceOrEmpty(s.conf.UpstreamDNS)
|
||||||
upstreamFile := s.conf.UpstreamDNSFileName
|
upstreamFile := s.conf.UpstreamDNSFileName
|
||||||
bootstraps := stringArrayDup(s.conf.BootstrapDNS)
|
bootstraps := aghstrings.CloneSliceOrEmpty(s.conf.BootstrapDNS)
|
||||||
protectionEnabled := s.conf.ProtectionEnabled
|
protectionEnabled := s.conf.ProtectionEnabled
|
||||||
blockingMode := s.conf.BlockingMode
|
blockingMode := s.conf.BlockingMode
|
||||||
BlockingIPv4 := s.conf.BlockingIPv4
|
blockingIPv4 := s.conf.BlockingIPv4
|
||||||
BlockingIPv6 := s.conf.BlockingIPv6
|
blockingIPv6 := s.conf.BlockingIPv6
|
||||||
Ratelimit := s.conf.Ratelimit
|
ratelimit := s.conf.Ratelimit
|
||||||
EnableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet
|
enableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet
|
||||||
EnableDNSSEC := s.conf.EnableDNSSEC
|
enableDNSSEC := s.conf.EnableDNSSEC
|
||||||
AAAADisabled := s.conf.AAAADisabled
|
aaaaDisabled := s.conf.AAAADisabled
|
||||||
CacheSize := s.conf.CacheSize
|
cacheSize := s.conf.CacheSize
|
||||||
CacheMinTTL := s.conf.CacheMinTTL
|
cacheMinTTL := s.conf.CacheMinTTL
|
||||||
CacheMaxTTL := s.conf.CacheMaxTTL
|
cacheMaxTTL := s.conf.CacheMaxTTL
|
||||||
|
resolveClients := s.conf.ResolveClients
|
||||||
|
localPTRUpstreams := aghstrings.CloneSliceOrEmpty(s.conf.LocalPTRResolvers)
|
||||||
var upstreamMode string
|
var upstreamMode string
|
||||||
if s.conf.FastestAddr {
|
if s.conf.FastestAddr {
|
||||||
upstreamMode = "fastest_addr"
|
upstreamMode = "fastest_addr"
|
||||||
} else if s.conf.AllServers {
|
} else if s.conf.AllServers {
|
||||||
upstreamMode = "parallel"
|
upstreamMode = "parallel"
|
||||||
}
|
}
|
||||||
s.RUnlock()
|
|
||||||
return dnsConfig{
|
return dnsConfig{
|
||||||
Upstreams: &upstreams,
|
Upstreams: &upstreams,
|
||||||
UpstreamsFile: &upstreamFile,
|
UpstreamsFile: &upstreamFile,
|
||||||
Bootstraps: &bootstraps,
|
Bootstraps: &bootstraps,
|
||||||
ProtectionEnabled: &protectionEnabled,
|
ProtectionEnabled: &protectionEnabled,
|
||||||
BlockingMode: &blockingMode,
|
BlockingMode: &blockingMode,
|
||||||
BlockingIPv4: BlockingIPv4,
|
BlockingIPv4: blockingIPv4,
|
||||||
BlockingIPv6: BlockingIPv6,
|
BlockingIPv6: blockingIPv6,
|
||||||
RateLimit: &Ratelimit,
|
RateLimit: &ratelimit,
|
||||||
EDNSCSEnabled: &EnableEDNSClientSubnet,
|
EDNSCSEnabled: &enableEDNSClientSubnet,
|
||||||
DNSSECEnabled: &EnableDNSSEC,
|
DNSSECEnabled: &enableDNSSEC,
|
||||||
DisableIPv6: &AAAADisabled,
|
DisableIPv6: &aaaaDisabled,
|
||||||
CacheSize: &CacheSize,
|
CacheSize: &cacheSize,
|
||||||
CacheMinTTL: &CacheMinTTL,
|
CacheMinTTL: &cacheMinTTL,
|
||||||
CacheMaxTTL: &CacheMaxTTL,
|
CacheMaxTTL: &cacheMaxTTL,
|
||||||
UpstreamMode: &upstreamMode,
|
UpstreamMode: &upstreamMode,
|
||||||
|
ResolveClients: &resolveClients,
|
||||||
|
LocalPTRUpstreams: &localPTRUpstreams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,6 +236,11 @@ func (s *Server) setConfigRestartable(dc dnsConfig) (restart bool) {
|
||||||
restart = true
|
restart = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dc.LocalPTRUpstreams != nil {
|
||||||
|
s.conf.LocalPTRResolvers = *dc.LocalPTRUpstreams
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
|
||||||
if dc.UpstreamsFile != nil {
|
if dc.UpstreamsFile != nil {
|
||||||
s.conf.UpstreamDNSFileName = *dc.UpstreamsFile
|
s.conf.UpstreamDNSFileName = *dc.UpstreamsFile
|
||||||
restart = true
|
restart = true
|
||||||
|
@ -294,15 +308,24 @@ func (s *Server) setConfig(dc dnsConfig) (restart bool) {
|
||||||
s.conf.FastestAddr = *dc.UpstreamMode == "fastest_addr"
|
s.conf.FastestAddr = *dc.UpstreamMode == "fastest_addr"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dc.ResolveClients != nil {
|
||||||
|
s.conf.ResolveClients = *dc.ResolveClients
|
||||||
|
}
|
||||||
|
|
||||||
return s.setConfigRestartable(dc)
|
return s.setConfigRestartable(dc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// upstreamJSON is a request body for handleTestUpstreamDNS endpoint.
|
||||||
type upstreamJSON struct {
|
type upstreamJSON struct {
|
||||||
Upstreams []string `json:"upstream_dns"` // Upstreams
|
Upstreams []string `json:"upstream_dns"`
|
||||||
BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS
|
BootstrapDNS []string `json:"bootstrap_dns"`
|
||||||
|
PrivateUpstreams []string `json:"private_upstream"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateUpstreams validates each upstream and returns an error if any upstream is invalid or if there are no default upstreams specified
|
// ValidateUpstreams validates each upstream and returns an error if any
|
||||||
|
// upstream is invalid or if there are no default upstreams specified.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Move into aghnet or even into dnsproxy.
|
||||||
func ValidateUpstreams(upstreams []string) (err error) {
|
func ValidateUpstreams(upstreams []string) (err error) {
|
||||||
// No need to validate comments
|
// No need to validate comments
|
||||||
upstreams = filterOutComments(upstreams)
|
upstreams = filterOutComments(upstreams)
|
||||||
|
@ -428,52 +451,76 @@ func checkPlainDNS(upstream string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
// excFunc is a signature of function to check if upstream exchanges correctly.
|
||||||
req := upstreamJSON{}
|
type excFunc func(u upstream.Upstream) (err error)
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
|
||||||
if err != nil {
|
// checkDNSUpstreamExc checks if the DNS upstream exchanges correctly.
|
||||||
httpError(r, w, http.StatusBadRequest, "Failed to read request body: %s", err)
|
func checkDNSUpstreamExc(u upstream.Upstream) (err error) {
|
||||||
return
|
req := &dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: dns.Id(),
|
||||||
|
RecursionDesired: true,
|
||||||
|
},
|
||||||
|
Question: []dns.Question{{
|
||||||
|
Name: "google-public-dns-a.google.com.",
|
||||||
|
Qtype: dns.TypeA,
|
||||||
|
Qclass: dns.ClassINET,
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
result := map[string]string{}
|
var reply *dns.Msg
|
||||||
|
reply, err = u.Exchange(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't communicate with upstream: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
for _, host := range req.Upstreams {
|
if len(reply.Answer) != 1 {
|
||||||
err = checkDNS(host, req.BootstrapDNS)
|
return fmt.Errorf("wrong response")
|
||||||
if err != nil {
|
}
|
||||||
log.Info("%v", err)
|
|
||||||
result[host] = err.Error()
|
if t, ok := reply.Answer[0].(*dns.A); ok {
|
||||||
} else {
|
if !net.IPv4(8, 8, 8, 8).Equal(t.A) {
|
||||||
result[host] = "OK"
|
return fmt.Errorf("wrong response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonVal, err := json.Marshal(result)
|
return nil
|
||||||
if err != nil {
|
|
||||||
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, err = w.Write(jsonVal)
|
|
||||||
if err != nil {
|
|
||||||
httpError(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDNS(input string, bootstrap []string) error {
|
// checkPrivateUpstreamExc checks if the upstream for resolving private
|
||||||
|
// addresses exchanges correctly.
|
||||||
|
func checkPrivateUpstreamExc(u upstream.Upstream) (err error) {
|
||||||
|
req := &dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: dns.Id(),
|
||||||
|
RecursionDesired: true,
|
||||||
|
},
|
||||||
|
Question: []dns.Question{{
|
||||||
|
Name: "1.0.0.127.in-addr.arpa.",
|
||||||
|
Qtype: dns.TypePTR,
|
||||||
|
Qclass: dns.ClassINET,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = u.Exchange(req); err != nil {
|
||||||
|
return fmt.Errorf("couldn't communicate with upstream: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDNS(input string, bootstrap []string, ef excFunc) (err error) {
|
||||||
if !isUpstream(input) {
|
if !isUpstream(input) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// separate upstream from domains list
|
// Separate upstream from domains list.
|
||||||
input, useDefault, err := separateUpstream(input)
|
var useDefault bool
|
||||||
if err != nil {
|
if input, useDefault, err = separateUpstream(input); err != nil {
|
||||||
return fmt.Errorf("wrong upstream format: %w", err)
|
return fmt.Errorf("wrong upstream format: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to check this DNS server
|
// No need to check this DNS server.
|
||||||
if !useDefault {
|
if !useDefault {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -486,35 +533,80 @@ func checkDNS(input string, bootstrap []string) error {
|
||||||
bootstrap = defaultBootstrap
|
bootstrap = defaultBootstrap
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("checking if dns %s works...", input)
|
log.Debug("checking if dns server %q works...", input)
|
||||||
u, err := upstream.AddressToUpstream(input, upstream.Options{Bootstrap: bootstrap, Timeout: DefaultTimeout})
|
var u upstream.Upstream
|
||||||
|
u, err = upstream.AddressToUpstream(input, upstream.Options{
|
||||||
|
Bootstrap: bootstrap,
|
||||||
|
Timeout: DefaultTimeout,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to choose upstream for %s: %w", input, err)
|
return fmt.Errorf("failed to choose upstream for %q: %w", input, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req := dns.Msg{}
|
if err = ef(u); err != nil {
|
||||||
req.Id = dns.Id()
|
return fmt.Errorf("upstream %q fails to exchange: %w", input, err)
|
||||||
req.RecursionDesired = true
|
|
||||||
req.Question = []dns.Question{
|
|
||||||
{Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
|
||||||
}
|
|
||||||
reply, err := u.Exchange(&req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't communicate with dns server %s: %w", input, err)
|
|
||||||
}
|
|
||||||
if len(reply.Answer) != 1 {
|
|
||||||
return fmt.Errorf("dns server %s returned wrong answer", input)
|
|
||||||
}
|
|
||||||
if t, ok := reply.Answer[0].(*dns.A); ok {
|
|
||||||
if !net.IPv4(8, 8, 8, 8).Equal(t.A) {
|
|
||||||
return fmt.Errorf("dns server %s returned wrong answer: %v", input, t.A)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("dns %s works OK", input)
|
log.Debug("dns %s works OK", input)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||||
|
req := &upstreamJSON{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(req)
|
||||||
|
if err != nil {
|
||||||
|
httpError(r, w, http.StatusBadRequest, "Failed to read request body: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]string{}
|
||||||
|
bootstraps := req.BootstrapDNS
|
||||||
|
|
||||||
|
for _, host := range req.Upstreams {
|
||||||
|
err = checkDNS(host, bootstraps, checkDNSUpstreamExc)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("%v", err)
|
||||||
|
result[host] = err.Error()
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result[host] = "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range req.PrivateUpstreams {
|
||||||
|
err = checkDNS(host, bootstraps, checkPrivateUpstreamExc)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("%v", err)
|
||||||
|
// TODO(e.burkov): If passed upstream have already
|
||||||
|
// written an error above, we rewriting the error for
|
||||||
|
// it. These cases should be handled properly instead.
|
||||||
|
result[host] = err.Error()
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result[host] = "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonVal, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, err = w.Write(jsonVal)
|
||||||
|
if err != nil {
|
||||||
|
httpError(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Control flow:
|
// Control flow:
|
||||||
// web
|
// web
|
||||||
// -> dnsforward.handleDOH -> dnsforward.ServeHTTP
|
// -> dnsforward.handleDOH -> dnsforward.ServeHTTP
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package dnsforward
|
package dnsforward
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
|
@ -13,6 +16,22 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func loadTestData(t *testing.T, casesFileName string, cases interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var f *os.File
|
||||||
|
f, err := os.Open(filepath.Join("testdata", casesFileName))
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, f.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
err = json.NewDecoder(f).Decode(cases)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonExt = ".json"
|
||||||
|
|
||||||
func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||||
filterConf := &dnsfilter.Config{
|
filterConf := &dnsfilter.Config{
|
||||||
SafeBrowsingEnabled: true,
|
SafeBrowsingEnabled: true,
|
||||||
|
@ -42,36 +61,38 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
|
||||||
conf func() ServerConfig
|
conf func() ServerConfig
|
||||||
want string
|
name string
|
||||||
}{{
|
}{{
|
||||||
name: "all_right",
|
|
||||||
conf: func() ServerConfig {
|
conf: func() ServerConfig {
|
||||||
return defaultConf
|
return defaultConf
|
||||||
},
|
},
|
||||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
name: "all_right",
|
||||||
}, {
|
}, {
|
||||||
name: "fastest_addr",
|
|
||||||
conf: func() ServerConfig {
|
conf: func() ServerConfig {
|
||||||
conf := defaultConf
|
conf := defaultConf
|
||||||
conf.FastestAddr = true
|
conf.FastestAddr = true
|
||||||
|
|
||||||
return conf
|
return conf
|
||||||
},
|
},
|
||||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
name: "fastest_addr",
|
||||||
}, {
|
}, {
|
||||||
name: "parallel",
|
|
||||||
conf: func() ServerConfig {
|
conf: func() ServerConfig {
|
||||||
conf := defaultConf
|
conf := defaultConf
|
||||||
conf.AllServers = true
|
conf.AllServers = true
|
||||||
|
|
||||||
return conf
|
return conf
|
||||||
},
|
},
|
||||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
name: "parallel",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
var data map[string]json.RawMessage
|
||||||
|
loadTestData(t, t.Name()+jsonExt, &data)
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
caseWant, ok := data[tc.name]
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Cleanup(w.Body.Reset)
|
t.Cleanup(w.Body.Reset)
|
||||||
|
|
||||||
|
@ -79,7 +100,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||||
s.handleGetConfig(w, nil)
|
s.handleGetConfig(w, nil)
|
||||||
|
|
||||||
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
||||||
assert.Equal(t, tc.want, w.Body.String())
|
assert.JSONEq(t, string(caseWant), w.Body.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,97 +129,81 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
|
||||||
|
|
||||||
err := s.Start()
|
err := s.Start()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer func() {
|
t.Cleanup(func() {
|
||||||
assert.Nil(t, s.Stop())
|
assert.Nil(t, s.Stop())
|
||||||
}()
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n"
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
req string
|
|
||||||
wantSet string
|
wantSet string
|
||||||
wantGet string
|
|
||||||
}{{
|
}{{
|
||||||
name: "upstream_dns",
|
name: "upstream_dns",
|
||||||
req: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"]}",
|
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "bootstraps",
|
name: "bootstraps",
|
||||||
req: "{\"bootstrap_dns\":[\"9.9.9.10\"]}",
|
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "blocking_mode_good",
|
name: "blocking_mode_good",
|
||||||
req: "{\"blocking_mode\":\"refused\"}",
|
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "blocking_mode_bad",
|
name: "blocking_mode_bad",
|
||||||
req: "{\"blocking_mode\":\"custom_ip\"}",
|
|
||||||
wantSet: "blocking_mode: incorrect value\n",
|
wantSet: "blocking_mode: incorrect value\n",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "ratelimit",
|
name: "ratelimit",
|
||||||
req: "{\"ratelimit\":6}",
|
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "edns_cs_enabled",
|
name: "edns_cs_enabled",
|
||||||
req: "{\"edns_cs_enabled\":true}",
|
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "dnssec_enabled",
|
name: "dnssec_enabled",
|
||||||
req: "{\"dnssec_enabled\":true}",
|
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "cache_size",
|
name: "cache_size",
|
||||||
req: "{\"cache_size\":1024}",
|
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "upstream_mode_parallel",
|
name: "upstream_mode_parallel",
|
||||||
req: "{\"upstream_mode\":\"parallel\"}",
|
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "upstream_mode_fastest_addr",
|
name: "upstream_mode_fastest_addr",
|
||||||
req: "{\"upstream_mode\":\"fastest_addr\"}",
|
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
|
||||||
}, {
|
}, {
|
||||||
name: "upstream_dns_bad",
|
name: "upstream_dns_bad",
|
||||||
req: "{\"upstream_dns\":[\"\"]}",
|
|
||||||
wantSet: "wrong upstreams specification: missing port in address\n",
|
wantSet: "wrong upstreams specification: missing port in address\n",
|
||||||
wantGet: defaultConfJSON,
|
|
||||||
}, {
|
}, {
|
||||||
name: "bootstraps_bad",
|
name: "bootstraps_bad",
|
||||||
req: "{\"bootstrap_dns\":[\"a\"]}",
|
|
||||||
wantSet: "a can not be used as bootstrap dns cause: invalid bootstrap server address: Resolver a is not eligible to be a bootstrap DNS server\n",
|
wantSet: "a can not be used as bootstrap dns cause: invalid bootstrap server address: Resolver a is not eligible to be a bootstrap DNS server\n",
|
||||||
wantGet: defaultConfJSON,
|
|
||||||
}, {
|
}, {
|
||||||
name: "cache_bad_ttl",
|
name: "cache_bad_ttl",
|
||||||
req: "{\"cache_ttl_min\":1024,\"cache_ttl_max\":512}",
|
|
||||||
wantSet: "cache_ttl_min must be less or equal than cache_ttl_max\n",
|
wantSet: "cache_ttl_min must be less or equal than cache_ttl_max\n",
|
||||||
wantGet: defaultConfJSON,
|
|
||||||
}, {
|
}, {
|
||||||
name: "upstream_mode_bad",
|
name: "upstream_mode_bad",
|
||||||
req: "{\"upstream_mode\":\"somethingelse\"}",
|
|
||||||
wantSet: "upstream_mode: incorrect value\n",
|
wantSet: "upstream_mode: incorrect value\n",
|
||||||
wantGet: defaultConfJSON,
|
}, {
|
||||||
|
name: "local_ptr_upstreams_good",
|
||||||
|
wantSet: "",
|
||||||
|
}, {
|
||||||
|
name: "local_ptr_upstreams_null",
|
||||||
|
wantSet: "",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
var data map[string]struct {
|
||||||
|
Req json.RawMessage `json:"req"`
|
||||||
|
Want json.RawMessage `json:"want"`
|
||||||
|
}
|
||||||
|
loadTestData(t, t.Name()+jsonExt, &data)
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
caseData, ok := data[tc.name]
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
s.conf = defaultConf
|
s.conf = defaultConf
|
||||||
})
|
})
|
||||||
|
|
||||||
rBody := ioutil.NopCloser(strings.NewReader(tc.req))
|
rBody := ioutil.NopCloser(bytes.NewReader(caseData.Req))
|
||||||
var r *http.Request
|
var r *http.Request
|
||||||
r, err = http.NewRequest(http.MethodPost, "http://example.com", rBody)
|
r, err = http.NewRequest(http.MethodPost, "http://example.com", rBody)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -208,7 +213,7 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
|
||||||
w.Body.Reset()
|
w.Body.Reset()
|
||||||
|
|
||||||
s.handleGetConfig(w, nil)
|
s.handleGetConfig(w, nil)
|
||||||
assert.Equal(t, tc.wantGet, w.Body.String())
|
assert.JSONEq(t, string(caseData.Want), w.Body.String())
|
||||||
w.Body.Reset()
|
w.Body.Reset()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
{
|
||||||
|
"all_right": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
},
|
||||||
|
"fastest_addr": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "fastest_addr",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
},
|
||||||
|
"parallel": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "parallel",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,525 @@
|
||||||
|
{
|
||||||
|
"upstream_dns": {
|
||||||
|
"req": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:77",
|
||||||
|
"8.8.4.4:77"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:77",
|
||||||
|
"8.8.4.4:77"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bootstraps": {
|
||||||
|
"req": {
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"blocking_mode_good": {
|
||||||
|
"req": {
|
||||||
|
"blocking_mode": "refused"
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "refused",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"blocking_mode_bad": {
|
||||||
|
"req": {
|
||||||
|
"blocking_mode": "custom_ip"
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ratelimit": {
|
||||||
|
"req": {
|
||||||
|
"ratelimit": 6
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 6,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"edns_cs_enabled": {
|
||||||
|
"req": {
|
||||||
|
"edns_cs_enabled": true
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": true,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dnssec_enabled": {
|
||||||
|
"req": {
|
||||||
|
"dnssec_enabled": true
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": true,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cache_size": {
|
||||||
|
"req": {
|
||||||
|
"cache_size": 1024
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 1024,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upstream_mode_parallel": {
|
||||||
|
"req": {
|
||||||
|
"upstream_mode": "parallel"
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "parallel",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upstream_mode_fastest_addr": {
|
||||||
|
"req": {
|
||||||
|
"upstream_mode": "fastest_addr"
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "fastest_addr",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upstream_dns_bad": {
|
||||||
|
"req": {
|
||||||
|
"upstream_dns": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bootstraps_bad": {
|
||||||
|
"req": {
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"a"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cache_bad_ttl": {
|
||||||
|
"req": {
|
||||||
|
"cache_ttl_min": 1024,
|
||||||
|
"cache_ttl_max": 512
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upstream_mode_bad": {
|
||||||
|
"req": {
|
||||||
|
"upstream_mode": "somethingelse"
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"local_ptr_upstreams_good": {
|
||||||
|
"req": {
|
||||||
|
"local_ptr_upstreams": [
|
||||||
|
"123.123.123.123"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": [
|
||||||
|
"123.123.123.123"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"local_ptr_upstreams_null": {
|
||||||
|
"req": {
|
||||||
|
"local_ptr_upstreams": null
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"8.8.4.4:53"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"local_ptr_upstreams": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,12 +30,6 @@ func IPStringFromAddr(addr net.Addr) (ipStr string) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringArrayDup(a []string) []string {
|
|
||||||
a2 := make([]string, len(a))
|
|
||||||
copy(a2, a)
|
|
||||||
return a2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find value in a sorted array
|
// Find value in a sorted array
|
||||||
func findSorted(ar []string, val string) int {
|
func findSorted(ar []string, val string) int {
|
||||||
i := sort.SearchStrings(ar, val)
|
i := sort.SearchStrings(ar, val)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
|
@ -216,10 +217,10 @@ func (clients *clientsContainer) WriteDiskConfig(objects *[]clientObject) {
|
||||||
UseGlobalBlockedServices: !cli.UseOwnBlockedServices,
|
UseGlobalBlockedServices: !cli.UseOwnBlockedServices,
|
||||||
}
|
}
|
||||||
|
|
||||||
cy.Tags = copyStrings(cli.Tags)
|
cy.Tags = aghstrings.CloneSlice(cli.Tags)
|
||||||
cy.IDs = copyStrings(cli.IDs)
|
cy.IDs = aghstrings.CloneSlice(cli.IDs)
|
||||||
cy.BlockedServices = copyStrings(cli.BlockedServices)
|
cy.BlockedServices = aghstrings.CloneSlice(cli.BlockedServices)
|
||||||
cy.Upstreams = copyStrings(cli.Upstreams)
|
cy.Upstreams = aghstrings.CloneSlice(cli.Upstreams)
|
||||||
|
|
||||||
*objects = append(*objects, cy)
|
*objects = append(*objects, cy)
|
||||||
}
|
}
|
||||||
|
@ -266,10 +267,6 @@ func (clients *clientsContainer) Exists(id string, source clientSource) (ok bool
|
||||||
return source <= rc.Source
|
return source <= rc.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyStrings(a []string) (b []string) {
|
|
||||||
return append(b, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toQueryLogWhois(wi *RuntimeClientWhoisInfo) (cw *querylog.ClientWhois) {
|
func toQueryLogWhois(wi *RuntimeClientWhoisInfo) (cw *querylog.ClientWhois) {
|
||||||
if wi == nil {
|
if wi == nil {
|
||||||
return &querylog.ClientWhois{}
|
return &querylog.ClientWhois{}
|
||||||
|
@ -326,10 +323,10 @@ func (clients *clientsContainer) Find(id string) (c *Client, ok bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IDs = copyStrings(c.IDs)
|
c.IDs = aghstrings.CloneSlice(c.IDs)
|
||||||
c.Tags = copyStrings(c.Tags)
|
c.Tags = aghstrings.CloneSlice(c.Tags)
|
||||||
c.BlockedServices = copyStrings(c.BlockedServices)
|
c.BlockedServices = aghstrings.CloneSlice(c.BlockedServices)
|
||||||
c.Upstreams = copyStrings(c.Upstreams)
|
c.Upstreams = aghstrings.CloneSlice(c.Upstreams)
|
||||||
return c, true
|
return c, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,13 @@ type dnsConfig struct {
|
||||||
// For example, a machine called "myhost" can be addressed as
|
// For example, a machine called "myhost" can be addressed as
|
||||||
// "myhost.lan" when AutohostTLD is "lan".
|
// "myhost.lan" when AutohostTLD is "lan".
|
||||||
AutohostTLD string `yaml:"autohost_tld"`
|
AutohostTLD string `yaml:"autohost_tld"`
|
||||||
|
|
||||||
|
// ResolveClients enables and disables resolving clients with RDNS.
|
||||||
|
ResolveClients bool `yaml:"resolve_clients"`
|
||||||
|
|
||||||
|
// LocalPTRResolvers is the slice of addresses to be used as upstreams
|
||||||
|
// for PTR queries for locally-served networks.
|
||||||
|
LocalPTRResolvers []string `yaml:"local_ptr_upstreams"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tlsConfigSettings struct {
|
type tlsConfigSettings struct {
|
||||||
|
@ -150,6 +157,7 @@ var config = configuration{
|
||||||
FilteringEnabled: true, // whether or not use filter lists
|
FilteringEnabled: true, // whether or not use filter lists
|
||||||
FiltersUpdateIntervalHours: 24,
|
FiltersUpdateIntervalHours: 24,
|
||||||
AutohostTLD: "lan",
|
AutohostTLD: "lan",
|
||||||
|
ResolveClients: true,
|
||||||
},
|
},
|
||||||
TLS: tlsConfigSettings{
|
TLS: tlsConfigSettings{
|
||||||
PortHTTPS: 443,
|
PortHTTPS: 443,
|
||||||
|
@ -296,10 +304,12 @@ func (c *configuration) write() error {
|
||||||
config.DNS.DnsfilterConf = c
|
config.DNS.DnsfilterConf = c
|
||||||
}
|
}
|
||||||
|
|
||||||
if Context.dnsServer != nil {
|
if s := Context.dnsServer; s != nil {
|
||||||
c := dnsforward.FilteringConfig{}
|
c := dnsforward.FilteringConfig{}
|
||||||
Context.dnsServer.WriteDiskConfig(&c)
|
s.WriteDiskConfig(&c)
|
||||||
config.DNS.FilteringConfig = c
|
config.DNS.FilteringConfig = c
|
||||||
|
|
||||||
|
config.DNS.LocalPTRResolvers, config.DNS.ResolveClients = s.RDNSSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
if Context.dhcpServer != nil {
|
if Context.dhcpServer != nil {
|
||||||
|
|
|
@ -67,7 +67,6 @@ func initDNSServer() error {
|
||||||
Stats: Context.stats,
|
Stats: Context.stats,
|
||||||
QueryLog: Context.queryLog,
|
QueryLog: Context.queryLog,
|
||||||
SubnetDetector: Context.subnetDetector,
|
SubnetDetector: Context.subnetDetector,
|
||||||
LocalResolvers: Context.localResolvers,
|
|
||||||
AutohostTLD: config.DNS.AutohostTLD,
|
AutohostTLD: config.DNS.AutohostTLD,
|
||||||
}
|
}
|
||||||
if Context.dhcpServer != nil {
|
if Context.dhcpServer != nil {
|
||||||
|
@ -95,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.subnetDetector, Context.localResolvers)
|
Context.rdns = NewRDNS(Context.dnsServer, &Context.clients)
|
||||||
Context.whois = initWhois(&Context.clients)
|
Context.whois = initWhois(&Context.clients)
|
||||||
|
|
||||||
Context.filters.Init()
|
Context.filters.Init()
|
||||||
|
@ -113,7 +112,7 @@ func onDNSRequest(d *proxy.DNSContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ip.IsLoopback() {
|
if config.DNS.ResolveClients && !ip.IsLoopback() {
|
||||||
Context.rdns.Begin(ip)
|
Context.rdns.Begin(ip)
|
||||||
}
|
}
|
||||||
if !Context.subnetDetector.IsSpecialNetwork(ip) {
|
if !Context.subnetDetector.IsSpecialNetwork(ip) {
|
||||||
|
@ -200,6 +199,9 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||||
newConf.FilterHandler = applyAdditionalFiltering
|
newConf.FilterHandler = applyAdditionalFiltering
|
||||||
newConf.GetCustomUpstreamByClient = Context.clients.FindUpstreams
|
newConf.GetCustomUpstreamByClient = Context.clients.FindUpstreams
|
||||||
|
|
||||||
|
newConf.ResolveClients = dnsConf.ResolveClients
|
||||||
|
newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
|
||||||
|
|
||||||
return newConf, nil
|
return newConf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +339,7 @@ func startDNSServer() error {
|
||||||
|
|
||||||
const topClientsNumber = 100 // the number of clients to get
|
const topClientsNumber = 100 // the number of clients to get
|
||||||
for _, ip := range Context.stats.GetTopClientsIP(topClientsNumber) {
|
for _, ip := range Context.stats.GetTopClientsIP(topClientsNumber) {
|
||||||
if !Context.subnetDetector.IsLocallyServedNetwork(ip) {
|
if config.DNS.ResolveClients && !ip.IsLoopback() {
|
||||||
Context.rdns.Begin(ip)
|
Context.rdns.Begin(ip)
|
||||||
}
|
}
|
||||||
if !Context.subnetDetector.IsSpecialNetwork(ip) {
|
if !Context.subnetDetector.IsSpecialNetwork(ip) {
|
||||||
|
|
|
@ -61,9 +61,7 @@ type homeContext struct {
|
||||||
autoHosts util.AutoHosts // IP-hostname pairs taken from system configuration (e.g. /etc/hosts) files
|
autoHosts util.AutoHosts // IP-hostname pairs taken from system configuration (e.g. /etc/hosts) files
|
||||||
updater *updater.Updater
|
updater *updater.Updater
|
||||||
|
|
||||||
subnetDetector *aghnet.SubnetDetector
|
subnetDetector *aghnet.SubnetDetector
|
||||||
systemResolvers aghnet.SystemResolvers
|
|
||||||
localResolvers aghnet.Exchanger
|
|
||||||
|
|
||||||
// mux is our custom http.ServeMux.
|
// mux is our custom http.ServeMux.
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
|
@ -222,110 +220,6 @@ func setupConfig(args options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultLocalTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
// stringsSetSubtract subtracts b from a interpreted as sets.
|
|
||||||
//
|
|
||||||
// TODO(e.burkov): Move into our internal package for working with strings.
|
|
||||||
func stringsSetSubtract(a, b []string) (c []string) {
|
|
||||||
// unit is an object to be used as value in set.
|
|
||||||
type unit = struct{}
|
|
||||||
|
|
||||||
cSet := make(map[string]unit)
|
|
||||||
for _, k := range a {
|
|
||||||
cSet[k] = unit{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, k := range b {
|
|
||||||
delete(cSet, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
c = make([]string, len(cSet))
|
|
||||||
i := 0
|
|
||||||
for k := range cSet {
|
|
||||||
c[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// collectAllIfacesAddrs returns the slice of all network interfaces IP
|
|
||||||
// addresses without port number.
|
|
||||||
func collectAllIfacesAddrs() (addrs []string, err error) {
|
|
||||||
var ifaces []net.Interface
|
|
||||||
ifaces, err = net.Interfaces()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting network interfaces: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, iface := range ifaces {
|
|
||||||
var ifaceAddrs []net.Addr
|
|
||||||
ifaceAddrs, err = iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting addresses for %q: %w", iface.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range ifaceAddrs {
|
|
||||||
cidr := addr.String()
|
|
||||||
var ip net.IP
|
|
||||||
ip, _, err = net.ParseCIDR(cidr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing %q as cidr: %w", cidr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs = append(addrs, ip.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return addrs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// collectDNSIPAddrs returns the slice of IP addresses without port number which
|
|
||||||
// we are listening on.
|
|
||||||
func collectDNSIPaddrs() (addrs []string, err error) {
|
|
||||||
addrs = make([]string, len(config.DNS.BindHosts))
|
|
||||||
|
|
||||||
for i, bh := range config.DNS.BindHosts {
|
|
||||||
if bh.IsUnspecified() {
|
|
||||||
return collectAllIfacesAddrs()
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs[i] = bh.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return addrs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupResolvers() {
|
|
||||||
// TODO(e.burkov): Enhance when the config will contain local resolvers
|
|
||||||
// addresses.
|
|
||||||
|
|
||||||
sysRes, err := aghnet.NewSystemResolvers(0, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
Context.systemResolvers = sysRes
|
|
||||||
|
|
||||||
var ourAddrs []string
|
|
||||||
ourAddrs, err = collectDNSIPaddrs()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(e.burkov): The approach of subtracting sets of strings is not
|
|
||||||
// really applicable here since in case of listening on all network
|
|
||||||
// interfaces we should check the whole interface's network to cut off
|
|
||||||
// all the loopback addresses as well.
|
|
||||||
addrs := stringsSetSubtract(sysRes.Get(), ourAddrs)
|
|
||||||
|
|
||||||
Context.localResolvers, err = aghnet.NewMultiAddrExchanger(addrs, defaultLocalTimeout)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run performs configurating and starts AdGuard Home.
|
// run performs configurating and starts AdGuard Home.
|
||||||
func run(args options) {
|
func run(args options) {
|
||||||
// configure config filename
|
// configure config filename
|
||||||
|
@ -416,8 +310,6 @@ func run(args options) {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupResolvers()
|
|
||||||
|
|
||||||
if !Context.firstRun {
|
if !Context.firstRun {
|
||||||
err = initDNSServer()
|
err = initDNSServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,25 +2,19 @@ package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/golibs/cache"
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RDNS resolves clients' addresses to enrich their metadata.
|
// RDNS resolves clients' addresses to enrich their metadata.
|
||||||
type RDNS struct {
|
type RDNS struct {
|
||||||
dnsServer *dnsforward.Server
|
exchanger dnsforward.RDNSExchanger
|
||||||
clients *clientsContainer
|
clients *clientsContainer
|
||||||
subnetDetector *aghnet.SubnetDetector
|
|
||||||
localResolvers aghnet.Exchanger
|
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -42,16 +36,12 @@ const (
|
||||||
|
|
||||||
// NewRDNS creates and returns initialized RDNS.
|
// NewRDNS creates and returns initialized RDNS.
|
||||||
func NewRDNS(
|
func NewRDNS(
|
||||||
dnsServer *dnsforward.Server,
|
exchanger dnsforward.RDNSExchanger,
|
||||||
clients *clientsContainer,
|
clients *clientsContainer,
|
||||||
snd *aghnet.SubnetDetector,
|
|
||||||
lr aghnet.Exchanger,
|
|
||||||
) (rDNS *RDNS) {
|
) (rDNS *RDNS) {
|
||||||
rDNS = &RDNS{
|
rDNS = &RDNS{
|
||||||
dnsServer: dnsServer,
|
exchanger: exchanger,
|
||||||
clients: clients,
|
clients: clients,
|
||||||
subnetDetector: snd,
|
|
||||||
localResolvers: lr,
|
|
||||||
ipCache: cache.New(cache.Config{
|
ipCache: cache.New(cache.Config{
|
||||||
EnableLRU: true,
|
EnableLRU: true,
|
||||||
MaxCount: defaultRDNSCacheSize,
|
MaxCount: defaultRDNSCacheSize,
|
||||||
|
@ -92,73 +82,23 @@ func (r *RDNS) Begin(ip net.IP) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
// rDNSEmptyAnswerErr is returned by RDNS resolve method when the answer
|
|
||||||
// section of respond is empty.
|
|
||||||
rDNSEmptyAnswerErr agherr.Error = "the answer section is empty"
|
|
||||||
|
|
||||||
// rDNSNotPTRErr is returned by RDNS resolve method when the response is
|
|
||||||
// not of PTR type.
|
|
||||||
rDNSNotPTRErr agherr.Error = "the response is not a ptr"
|
|
||||||
)
|
|
||||||
|
|
||||||
// resolve tries to resolve the ip in a suitable way.
|
|
||||||
func (r *RDNS) resolve(ip net.IP) (host string, err error) {
|
|
||||||
log.Tracef("rdns: resolving host for %q", ip)
|
|
||||||
|
|
||||||
arpa := dns.Fqdn(aghnet.ReverseAddr(ip))
|
|
||||||
msg := &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{
|
|
||||||
Id: dns.Id(),
|
|
||||||
RecursionDesired: true,
|
|
||||||
},
|
|
||||||
Compress: true,
|
|
||||||
Question: []dns.Question{{
|
|
||||||
Name: arpa,
|
|
||||||
Qtype: dns.TypePTR,
|
|
||||||
Qclass: dns.ClassINET,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp *dns.Msg
|
|
||||||
if r.subnetDetector.IsLocallyServedNetwork(ip) {
|
|
||||||
resp, err = r.localResolvers.Exchange(msg)
|
|
||||||
} else {
|
|
||||||
resp, err = r.dnsServer.Exchange(msg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("performing lookup for %q: %w", arpa, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resp.Answer) == 0 {
|
|
||||||
return "", fmt.Errorf("lookup for %q: %w", arpa, rDNSEmptyAnswerErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr, ok := resp.Answer[0].(*dns.PTR)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("type checking: %w", rDNSNotPTRErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Tracef("rdns: ptr response for %q: %s", ip, ptr.String())
|
|
||||||
|
|
||||||
return strings.TrimSuffix(ptr.Ptr, "."), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// workerLoop handles incoming IP addresses from ipChan and adds it into
|
// workerLoop handles incoming IP addresses from ipChan and adds it into
|
||||||
// clients.
|
// clients.
|
||||||
func (r *RDNS) workerLoop() {
|
func (r *RDNS) workerLoop() {
|
||||||
defer agherr.LogPanic("rdns")
|
defer agherr.LogPanic("rdns")
|
||||||
|
|
||||||
for ip := range r.ipCh {
|
for ip := range r.ipCh {
|
||||||
host, err := r.resolve(ip)
|
host, err := r.exchanger.Exchange(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("rdns: resolving %q: %s", ip, err)
|
log.Error("rdns: resolving %q: %s", ip, err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't handle any errors since AddHost doesn't return non-nil
|
if host != "" {
|
||||||
// errors for now.
|
// Don't handle any errors since AddHost doesn't return non-nil
|
||||||
_, _ = r.clients.AddHost(ip.String(), host, ClientSourceRDNS)
|
// errors for now.
|
||||||
|
_, _ = r.clients.AddHost(ip.String(), host, ClientSourceRDNS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/cache"
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"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) {
|
||||||
|
@ -105,90 +102,30 @@ func TestRDNS_Begin(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRDNS_Resolve(t *testing.T) {
|
// rDNSExchanger is a mock dnsforward.RDNSExchanger implementation for tests.
|
||||||
extUpstream := &aghtest.TestUpstream{
|
type rDNSExchanger struct {
|
||||||
Reverse: map[string][]string{
|
aghtest.Exchanger
|
||||||
"1.1.1.1.in-addr.arpa.": {"one.one.one.one"},
|
}
|
||||||
},
|
|
||||||
}
|
// Exchange implements dnsforward.RDNSExchanger interface for *RDNSExchanger.
|
||||||
locUpstream := &aghtest.TestUpstream{
|
func (e *rDNSExchanger) Exchange(ip net.IP) (host string, err error) {
|
||||||
Reverse: map[string][]string{
|
req := &dns.Msg{
|
||||||
"1.1.168.192.in-addr.arpa.": {"local.domain"},
|
Question: []dns.Question{{
|
||||||
"2.1.168.192.in-addr.arpa.": {},
|
Name: ip.String(),
|
||||||
},
|
Qtype: dns.TypePTR,
|
||||||
}
|
}},
|
||||||
upstreamErr := errors.New("upstream error")
|
|
||||||
errUpstream := &aghtest.TestErrUpstream{
|
|
||||||
Err: upstreamErr,
|
|
||||||
}
|
|
||||||
nonPtrUpstream := &aghtest.TestBlockUpstream{
|
|
||||||
Hostname: "some-host",
|
|
||||||
Block: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dns := dnsforward.NewCustomServer(&proxy.Proxy{
|
resp, err := e.Exchanger.Exchange(req)
|
||||||
Config: proxy.Config{
|
if err != nil {
|
||||||
UpstreamConfig: &proxy.UpstreamConfig{
|
return "", err
|
||||||
Upstreams: []upstream.Upstream{extUpstream},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
cc := &clientsContainer{}
|
|
||||||
|
|
||||||
snd, err := aghnet.NewSubnetDetector()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
localIP := net.IP{192, 168, 1, 1}
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
want string
|
|
||||||
wantErr error
|
|
||||||
locUpstream upstream.Upstream
|
|
||||||
req net.IP
|
|
||||||
}{{
|
|
||||||
name: "external_good",
|
|
||||||
want: "one.one.one.one",
|
|
||||||
wantErr: nil,
|
|
||||||
locUpstream: nil,
|
|
||||||
req: net.IP{1, 1, 1, 1},
|
|
||||||
}, {
|
|
||||||
name: "local_good",
|
|
||||||
want: "local.domain",
|
|
||||||
wantErr: nil,
|
|
||||||
locUpstream: locUpstream,
|
|
||||||
req: localIP,
|
|
||||||
}, {
|
|
||||||
name: "upstream_error",
|
|
||||||
want: "",
|
|
||||||
wantErr: upstreamErr,
|
|
||||||
locUpstream: errUpstream,
|
|
||||||
req: localIP,
|
|
||||||
}, {
|
|
||||||
name: "empty_answer_error",
|
|
||||||
want: "",
|
|
||||||
wantErr: rDNSEmptyAnswerErr,
|
|
||||||
locUpstream: locUpstream,
|
|
||||||
req: net.IP{192, 168, 1, 2},
|
|
||||||
}, {
|
|
||||||
name: "not_ptr_error",
|
|
||||||
want: "",
|
|
||||||
wantErr: rDNSNotPTRErr,
|
|
||||||
locUpstream: nonPtrUpstream,
|
|
||||||
req: localIP,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
rdns := NewRDNS(dns, cc, snd, &aghtest.Exchanger{
|
|
||||||
Ups: tc.locUpstream,
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
r, rerr := rdns.resolve(tc.req)
|
|
||||||
require.ErrorIs(t, rerr, tc.wantErr)
|
|
||||||
assert.Equal(t, tc.want, r)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(resp.Answer) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Answer[0].Header().Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRDNS_WorkerLoop(t *testing.T) {
|
func TestRDNS_WorkerLoop(t *testing.T) {
|
||||||
|
@ -198,34 +135,33 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||||
|
|
||||||
locUpstream := &aghtest.TestUpstream{
|
locUpstream := &aghtest.TestUpstream{
|
||||||
Reverse: map[string][]string{
|
Reverse: map[string][]string{
|
||||||
"1.1.168.192.in-addr.arpa.": {"local.domain"},
|
"192.168.1.1": {"local.domain"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
errUpstream := &aghtest.TestErrUpstream{
|
||||||
snd, err := aghnet.NewSubnetDetector()
|
Err: errors.New("1234"),
|
||||||
require.NoError(t, err)
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
ups upstream.Upstream
|
||||||
wantLog string
|
wantLog string
|
||||||
name string
|
name string
|
||||||
cliIP net.IP
|
cliIP net.IP
|
||||||
}{{
|
}{{
|
||||||
|
ups: locUpstream,
|
||||||
wantLog: "",
|
wantLog: "",
|
||||||
name: "all_good",
|
name: "all_good",
|
||||||
cliIP: net.IP{192, 168, 1, 1},
|
cliIP: net.IP{192, 168, 1, 1},
|
||||||
}, {
|
}, {
|
||||||
wantLog: `rdns: resolving "192.168.1.2": lookup for "2.1.168.192.in-addr.arpa.": ` +
|
ups: errUpstream,
|
||||||
string(rDNSEmptyAnswerErr),
|
wantLog: `rdns: resolving "192.168.1.2": errupstream: 1234`,
|
||||||
name: "resolve_error",
|
name: "resolve_error",
|
||||||
cliIP: net.IP{192, 168, 1, 2},
|
cliIP: net.IP{192, 168, 1, 2},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
w.Reset()
|
w.Reset()
|
||||||
|
|
||||||
lr := &aghtest.Exchanger{
|
|
||||||
Ups: locUpstream,
|
|
||||||
}
|
|
||||||
cc := &clientsContainer{
|
cc := &clientsContainer{
|
||||||
list: map[string]*Client{},
|
list: map[string]*Client{},
|
||||||
idIndex: map[string]*Client{},
|
idIndex: map[string]*Client{},
|
||||||
|
@ -234,11 +170,13 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||||
}
|
}
|
||||||
ch := make(chan net.IP)
|
ch := make(chan net.IP)
|
||||||
rdns := &RDNS{
|
rdns := &RDNS{
|
||||||
dnsServer: nil,
|
exchanger: &rDNSExchanger{
|
||||||
clients: cc,
|
Exchanger: aghtest.Exchanger{
|
||||||
subnetDetector: snd,
|
Ups: tc.ups,
|
||||||
localResolvers: lr,
|
},
|
||||||
ipCh: ch,
|
},
|
||||||
|
clients: cc,
|
||||||
|
ipCh: ch,
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
"github.com/AdguardTeam/golibs/cache"
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
@ -67,7 +67,7 @@ func whoisParse(data string) map[string]string {
|
||||||
descr := ""
|
descr := ""
|
||||||
netname := ""
|
netname := ""
|
||||||
for len(data) != 0 {
|
for len(data) != 0 {
|
||||||
ln := util.SplitNext(&data, '\n')
|
ln := aghstrings.SplitNext(&data, '\n')
|
||||||
if len(ln) == 0 || ln[0] == '#' || ln[0] == '%' {
|
if len(ln) == 0 || ln[0] == '#' || ln[0] == '%' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
"github.com/AdguardTeam/golibs/jsonutil"
|
"github.com/AdguardTeam/golibs/jsonutil"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
@ -125,17 +126,6 @@ func getDoubleQuotesEnclosedValue(s *string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// inStr checks if string is in the slice of strings.
|
|
||||||
func inStr(strs []string, str string) (ok bool) {
|
|
||||||
for _, s := range strs {
|
|
||||||
if s == str {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSearchCriteria - parses "searchCriteria" from the specified query parameter
|
// parseSearchCriteria - parses "searchCriteria" from the specified query parameter
|
||||||
func (l *queryLog) parseSearchCriteria(q url.Values, name string, ct criteriaType) (bool, searchCriteria, error) {
|
func (l *queryLog) parseSearchCriteria(q url.Values, name string, ct criteriaType) (bool, searchCriteria, error) {
|
||||||
val := q.Get(name)
|
val := q.Get(name)
|
||||||
|
@ -151,7 +141,7 @@ func (l *queryLog) parseSearchCriteria(q url.Values, name string, ct criteriaTyp
|
||||||
c.strict = true
|
c.strict = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ct == ctFilteringStatus && !inStr(filteringStatusValues, c.value) {
|
if ct == ctFilteringStatus && !aghstrings.InSlice(filteringStatusValues, c.value) {
|
||||||
return false, c, fmt.Errorf("invalid value %s", c.value)
|
return false, c, fmt.Errorf("invalid value %s", c.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,30 +12,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SplitNext - split string by a byte and return the first chunk
|
|
||||||
// Skip empty chunks
|
|
||||||
// Whitespace is trimmed
|
|
||||||
func SplitNext(str *string, splitBy byte) string {
|
|
||||||
i := strings.IndexByte(*str, splitBy)
|
|
||||||
s := ""
|
|
||||||
if i != -1 {
|
|
||||||
s = (*str)[0:i]
|
|
||||||
*str = (*str)[i+1:]
|
|
||||||
k := 0
|
|
||||||
ch := rune(0)
|
|
||||||
for k, ch = range *str {
|
|
||||||
if byte(ch) != splitBy {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*str = (*str)[k:]
|
|
||||||
} else {
|
|
||||||
s = *str
|
|
||||||
*str = ""
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsOpenWrt returns true if host OS is OpenWrt.
|
// IsOpenWrt returns true if host OS is OpenWrt.
|
||||||
func IsOpenWrt() bool {
|
func IsOpenWrt() bool {
|
||||||
if runtime.GOOS != "linux" {
|
if runtime.GOOS != "linux" {
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSplitNext(t *testing.T) {
|
|
||||||
s := " a,b , c "
|
|
||||||
|
|
||||||
assert.Equal(t, "a", SplitNext(&s, ','))
|
|
||||||
assert.Equal(t, "b", SplitNext(&s, ','))
|
|
||||||
assert.Equal(t, "c", SplitNext(&s, ','))
|
|
||||||
require.Empty(t, s)
|
|
||||||
}
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Channel constants.
|
// Channel constants.
|
||||||
|
@ -68,14 +70,6 @@ const (
|
||||||
nltb = nl + tb
|
nltb = nl + tb
|
||||||
)
|
)
|
||||||
|
|
||||||
// writeStrings is a convenient wrapper for strings.(*Builder).WriteString that
|
|
||||||
// deals with multiple strings and ignores errors that are guaranteed to be nil.
|
|
||||||
func writeStrings(b *strings.Builder, strs ...string) {
|
|
||||||
for _, s := range strs {
|
|
||||||
_, _ = b.WriteString(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constants defining the format of module information string.
|
// Constants defining the format of module information string.
|
||||||
const (
|
const (
|
||||||
modInfoAtSep = "@"
|
modInfoAtSep = "@"
|
||||||
|
@ -99,16 +93,16 @@ func fmtModule(m *debug.Module) (formatted string) {
|
||||||
|
|
||||||
b := &strings.Builder{}
|
b := &strings.Builder{}
|
||||||
|
|
||||||
writeStrings(b, m.Path)
|
aghstrings.WriteToBuilder(b, m.Path)
|
||||||
if ver := m.Version; ver != "" {
|
if ver := m.Version; ver != "" {
|
||||||
sep := modInfoAtSep
|
sep := modInfoAtSep
|
||||||
if ver == "(devel)" {
|
if ver == "(devel)" {
|
||||||
sep = modInfoDevSep
|
sep = modInfoDevSep
|
||||||
}
|
}
|
||||||
writeStrings(b, sep, ver)
|
aghstrings.WriteToBuilder(b, sep, ver)
|
||||||
}
|
}
|
||||||
if sum := m.Sum; sum != "" {
|
if sum := m.Sum; sum != "" {
|
||||||
writeStrings(b, modInfoSumLeft, sum, modInfoSumRight)
|
aghstrings.WriteToBuilder(b, modInfoSumLeft, sum, modInfoSumRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.String()
|
return b.String()
|
||||||
|
@ -149,7 +143,7 @@ const (
|
||||||
func Verbose() (v string) {
|
func Verbose() (v string) {
|
||||||
b := &strings.Builder{}
|
b := &strings.Builder{}
|
||||||
|
|
||||||
writeStrings(
|
aghstrings.WriteToBuilder(
|
||||||
b,
|
b,
|
||||||
vFmtAGHHdr,
|
vFmtAGHHdr,
|
||||||
nl,
|
nl,
|
||||||
|
@ -163,31 +157,31 @@ func Verbose() (v string) {
|
||||||
runtime.Version(),
|
runtime.Version(),
|
||||||
)
|
)
|
||||||
if buildtime != "" {
|
if buildtime != "" {
|
||||||
writeStrings(b, nl, vFmtTimeHdr, buildtime)
|
aghstrings.WriteToBuilder(b, nl, vFmtTimeHdr, buildtime)
|
||||||
}
|
}
|
||||||
writeStrings(b, nl, vFmtGOOSHdr, nl, vFmtGOARCHHdr)
|
aghstrings.WriteToBuilder(b, nl, vFmtGOOSHdr, nl, vFmtGOARCHHdr)
|
||||||
if goarm != "" {
|
if goarm != "" {
|
||||||
writeStrings(b, nl, vFmtGOARMHdr, "v", goarm)
|
aghstrings.WriteToBuilder(b, nl, vFmtGOARMHdr, "v", goarm)
|
||||||
} else if gomips != "" {
|
} else if gomips != "" {
|
||||||
writeStrings(b, nl, vFmtGOMIPSHdr, gomips)
|
aghstrings.WriteToBuilder(b, nl, vFmtGOMIPSHdr, gomips)
|
||||||
}
|
}
|
||||||
writeStrings(b, nl, vFmtRaceHdr, strconv.FormatBool(isRace))
|
aghstrings.WriteToBuilder(b, nl, vFmtRaceHdr, strconv.FormatBool(isRace))
|
||||||
|
|
||||||
info, ok := debug.ReadBuildInfo()
|
info, ok := debug.ReadBuildInfo()
|
||||||
if !ok {
|
if !ok {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
writeStrings(b, nl, vFmtMainHdr, nltb, fmtModule(&info.Main))
|
aghstrings.WriteToBuilder(b, nl, vFmtMainHdr, nltb, fmtModule(&info.Main))
|
||||||
|
|
||||||
if len(info.Deps) == 0 {
|
if len(info.Deps) == 0 {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
writeStrings(b, nl, vFmtDepsHdr)
|
aghstrings.WriteToBuilder(b, nl, vFmtDepsHdr)
|
||||||
for _, dep := range info.Deps {
|
for _, dep := range info.Deps {
|
||||||
if depStr := fmtModule(dep); depStr != "" {
|
if depStr := fmtModule(dep); depStr != "" {
|
||||||
writeStrings(b, nltb, depStr)
|
aghstrings.WriteToBuilder(b, nltb, depStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,21 @@
|
||||||
|
|
||||||
## v0.106: API changes
|
## v0.106: API changes
|
||||||
|
|
||||||
|
## New `"private_upstream"` field in `POST /test_upstream_dns`
|
||||||
|
|
||||||
|
* The new optional field `"private_upstream"` of `UpstreamConfig` contains the
|
||||||
|
upstream servers for resolving locally-served ip addresses to be checked.
|
||||||
|
|
||||||
|
### New fields `"resolve_clients"` and `"local_ptr_upstreams"` in DNS configuration
|
||||||
|
|
||||||
|
* The new optional field `"resolve_clients"` of `DNSConfig` is used to turn
|
||||||
|
resolving clients' addresses on and off.
|
||||||
|
|
||||||
|
* The new optional field `"local_ptr_upstreams"` of `"DNSConfig"` contains the
|
||||||
|
upstream servers for resolving addresses from locally-served networks. The
|
||||||
|
empty `"local_ptr_resolvers"` states that AGH should use resolvers provided by
|
||||||
|
the operating system.
|
||||||
|
|
||||||
### New `"client_info"` field in `GET /querylog` response
|
### New `"client_info"` field in `GET /querylog` response
|
||||||
|
|
||||||
* The new optional field `"client_info"` of `QueryLogItem` objects contains
|
* The new optional field `"client_info"` of `QueryLogItem` objects contains
|
||||||
|
|
|
@ -1294,6 +1294,18 @@
|
||||||
- ''
|
- ''
|
||||||
- 'parallel'
|
- 'parallel'
|
||||||
- 'fastest_addr'
|
- 'fastest_addr'
|
||||||
|
'resolve_clients':
|
||||||
|
'type': 'boolean'
|
||||||
|
'local_ptr_upstreams':
|
||||||
|
'type': 'array'
|
||||||
|
'description': >
|
||||||
|
Upstream servers, port is optional after colon. Empty value will
|
||||||
|
reset it to default values.
|
||||||
|
'items':
|
||||||
|
'type': 'string'
|
||||||
|
'example':
|
||||||
|
- 'tls://1.1.1.1'
|
||||||
|
- 'tls://1.0.0.1'
|
||||||
'UpstreamsConfig':
|
'UpstreamsConfig':
|
||||||
'type': 'object'
|
'type': 'object'
|
||||||
'description': 'Upstreams configuration'
|
'description': 'Upstreams configuration'
|
||||||
|
@ -1321,6 +1333,16 @@
|
||||||
'example':
|
'example':
|
||||||
- 'tls://1.1.1.1'
|
- 'tls://1.1.1.1'
|
||||||
- 'tls://1.0.0.1'
|
- 'tls://1.0.0.1'
|
||||||
|
'private_upstream':
|
||||||
|
'type': 'array'
|
||||||
|
'description': >
|
||||||
|
Local PTR resolvers, port is optional after colon. Empty value will
|
||||||
|
reset it to default values.
|
||||||
|
'items':
|
||||||
|
'type': 'string'
|
||||||
|
'example':
|
||||||
|
- 'tls://1.1.1.1'
|
||||||
|
- 'tls://1.0.0.1'
|
||||||
'UpstreamsConfigResponse':
|
'UpstreamsConfigResponse':
|
||||||
'type': 'object'
|
'type': 'object'
|
||||||
'description': 'Upstreams configuration response'
|
'description': 'Upstreams configuration response'
|
||||||
|
|
Loading…
Reference in New Issue