Compare commits

..

1 Commits

Author SHA1 Message Date
Artem Baskal
503974c210 client: 1383 Support device identifier - DOT and DOH 2020-12-11 17:49:50 +03:00
54 changed files with 678 additions and 2135 deletions

View File

@@ -1,13 +1,18 @@
#!/bin/sh #!/bin/bash
set -e;
set -e -f -u found=0
git diff --cached --name-only | grep -q '.js$' && found=1
if [ "$(git diff --cached --name-only -- '*.js')" ] if [ $found == 1 ]; then
then npm --prefix client run lint || exit 1
make js-lint js-test npm run test --prefix client || exit 1
fi fi
if [ "$(git diff --cached --name-only -- '*.go' 'go.mod')" ] found=0
then git diff --cached --name-only | grep -q '.go$' && found=1
make go-lint go-test if [ $found == 1 ]; then
make go-lint || exit 1
go test ./... || exit 1
fi fi
exit 0;

View File

@@ -1833,22 +1833,16 @@ Response:
200 OK 200 OK
{ {
"reason":"FilteredBlackList", "reason":"FilteredBlackList",
"rules":{ "filter_id":1,
"filter_list_id":42, "rule":"||doubleclick.net^",
"text":"||doubleclick.net^", "service_name": "...", // set if reason=FilteredBlockedService
},
// If we have "reason":"FilteredBlockedService". // if reason=ReasonRewrite:
"service_name": "...", "cname": "...",
// If we have "reason":"Rewrite". "ip_addrs": ["1.2.3.4", ...],
"cname": "...",
"ip_addrs": ["1.2.3.4", ...]
} }
There are also deprecated properties `filter_id` and `rule` on the top level of
the response object. Their usaga should be replaced with
`rules[*].filter_list_id` and `rules[*].text` correspondingly. See the
_OpenAPI_ documentation and the `./openapi/CHANGELOG.md` file.
## Log-in page ## Log-in page

View File

@@ -15,19 +15,15 @@ and this project adheres to
### Added ### Added
- `$dnsrewrite` modifier for filters ([#2102]). - Detecting of network interface configurated to have static IP address via
- The host checking API and the query logs API can now return multiple matched
rules ([#2102]).
- Detecting of network interface configured to have static IP address via
`/etc/network/interfaces` ([#2302]). `/etc/network/interfaces` ([#2302]).
- DNSCrypt protocol support ([#1361]). - DNSCrypt protocol support [#1361].
- A 5 second wait period until a DHCP server's network interface gets an IP - A 5 second wait period until a DHCP server's network interface gets an IP
address ([#2304]). address ([#2304]).
- `$dnstype` modifier for filters ([#2337]). - `$dnstype` modifier for filters ([#2337]).
- HTTP API request body size limit ([#2305]). - HTTP API request body size limit ([#2305]).
[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 [#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361
[#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102
[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302 [#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302
[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304
[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305
@@ -35,9 +31,6 @@ and this project adheres to
### Changed ### Changed
- When `dns.bogus_nxdomain` option is used, the server will now transform
responses if there is at least one bogus address instead of all of them
([#2394]). The new behavior is the same as in `dnsmasq`.
- Post-updating relaunch possibility is now determined OS-dependently ([#2231], - Post-updating relaunch possibility is now determined OS-dependently ([#2231],
[#2391]). [#2391]).
- Made the mobileconfig HTTP API more robust and predictable, add parameters and - Made the mobileconfig HTTP API more robust and predictable, add parameters and
@@ -54,27 +47,20 @@ and this project adheres to
[#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343 [#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343
[#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358 [#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358
[#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391 [#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391
[#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394
### Fixed ### Fixed
- Inability to set DNS cache TTL limits ([#2459]).
- Possible freezes on slower machines ([#2225]).
- A mitigation against records being shown in the wrong order on the query log - A mitigation against records being shown in the wrong order on the query log
page ([#2293]). page ([#2293]).
- A JSON parsing error in query log ([#2345]). - A JSON parsing error in query log ([#2345]).
- Incorrect detection of the IPv6 address of an interface as well as another - Incorrect detection of the IPv6 address of an interface as well as another
infinite loop in the `/dhcp/find_active_dhcp` HTTP API ([#2355]). infinite loop in the `/dhcp/find_active_dhcp` HTTP API ([#2355]).
[#2225]: https://github.com/AdguardTeam/AdGuardHome/issues/2225
[#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293 [#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293
[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 [#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345
[#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 [#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355
[#2459]: https://github.com/AdguardTeam/AdGuardHome/issues/2459
### Removed
- Support for pre-v0.99.3 format of query logs ([#2102]).
## [v0.104.3] - 2020-11-19 ## [v0.104.3] - 2020-11-19

View File

@@ -78,14 +78,6 @@ The rules are mostly sorted in the alphabetical order.
* Prefer constants to variables where possible. Reduce global variables. Use * Prefer constants to variables where possible. Reduce global variables. Use
[constant errors] instead of `errors.New`. [constant errors] instead of `errors.New`.
* Unused arguments in anonymous functions must be called `_`:
```go
v.onSuccess = func(_ int, msg string) {
// …
}
```
* Use linters. * Use linters.
* Use named returns to improve readability of function signatures. * Use named returns to improve readability of function signatures.
@@ -114,16 +106,7 @@ The rules are mostly sorted in the alphabetical order.
```go ```go
// Foo implements the Fooer interface for *foo. // Foo implements the Fooer interface for *foo.
func (f *foo) Foo() { func (f *foo) Foo() {
// … // …
}
```
When the implemented interface is unexported:
```go
// Unwrap implements the hidden wrapper interface for *fooError.
func (err *fooError) Unwrap() (unwrapped error) {
// …
} }
``` ```
@@ -163,15 +146,12 @@ The rules are mostly sorted in the alphabetical order.
## Shell Scripting ## Shell Scripting
* Avoid bashisms and GNUisms, prefer *POSIX* features only. * Avoid bashisms, prefer *POSIX* features only.
* Prefer `'raw strings'` to `"double quoted strings"` whenever possible. * Prefer `'raw strings'` to `"double quoted strings"` whenever possible.
* Put spaces within `$( cmd )`, `$(( expr ))`, and `{ cmd; }`. * Put spaces within `$( cmd )`, `$(( expr ))`, and `{ cmd; }`.
* Put utility flags in the ASCII order and **don't** group them together. For
example, `ls -1 -A -q`.
* `snake_case`, not `camelCase`. * `snake_case`, not `camelCase`.
* Use `set -e -f -u` and also `set -x` in verbose mode. * Use `set -e -f -u` and also `set -x` in verbose mode.
@@ -179,24 +159,6 @@ The rules are mostly sorted in the alphabetical order.
* Use the `"$var"` form instead of the `$var` form, unless word splitting is * Use the `"$var"` form instead of the `$var` form, unless word splitting is
required. required.
* When concatenating, always use the form with curly braces to prevent
accidental bad variable names. That is, `"${var}_tmp.txt"` and **not**
`"$var_tmp.txt"`. The latter will try to lookup variable `var_tmp`.
* When concatenating, surround the whole string with quotes. That is, use
this:
```sh
dir="${TOP_DIR}/sub"
```
And **not** this:
```sh
# Bad!
dir="${TOP_DIR}"/sub
```
## Text, Including Comments ## Text, Including Comments
* End sentences with appropriate punctuation. * End sentences with appropriate punctuation.

View File

@@ -35,7 +35,6 @@ GPG_KEY := devteam@adguard.com
GPG_KEY_PASSPHRASE := GPG_KEY_PASSPHRASE :=
GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE) GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE)
VERBOSE := -v VERBOSE := -v
REBUILD_CLIENT = 1
# See release target # See release target
DIST_DIR=dist DIST_DIR=dist
@@ -125,8 +124,7 @@ all: build
init: init:
git config core.hooksPath .githooks git config core.hooksPath .githooks
build: build: client_with_deps
test '$(REBUILD_CLIENT)' = '1' && $(MAKE) client_with_deps || exit 0
$(GO) mod download $(GO) mod download
PATH=$(GOPATH)/bin:$(PATH) $(GO) generate ./... PATH=$(GOPATH)/bin:$(PATH) $(GO) generate ./...
CGO_ENABLED=0 $(GO) build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)" CGO_ENABLED=0 $(GO) build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"

View File

@@ -255,7 +255,7 @@ curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scrip
* Beta channel builds * Beta channel builds
* Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz) * Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz) * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Rapsberry Pi), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz) * Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
* Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip) * Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip)
* MacOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip) * MacOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip)
@@ -264,7 +264,7 @@ curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scrip
* Edge channel builds * Edge channel builds
* Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz) * Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz) * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Rapsberry Pi), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz) * Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
* Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip) * Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip)
* MacOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip) * MacOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip)

12
client/package-lock.json generated vendored
View File

@@ -3066,12 +3066,6 @@
"pkg-up": "^2.0.0" "pkg-up": "^2.0.0"
} }
}, },
"caniuse-lite": {
"version": "1.0.30001062",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001062.tgz",
"integrity": "sha512-ei9ZqeOnN7edDrb24QfJ0OZicpEbsWxv7WusOiQGz/f2SfvBgHHbOEwBJ8HKGVSyx8Z6ndPjxzR6m0NQq+0bfw==",
"dev": true
},
"postcss": { "postcss": {
"version": "7.0.30", "version": "7.0.30",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.30.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.30.tgz",
@@ -3928,9 +3922,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001059", "version": "1.0.30001165",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001059.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz",
"integrity": "sha512-oOrc+jPJWooKIA0IrNZ5sYlsXc7NP7KLhNWrSGEJhnfSzDvDJ0zd3i6HXsslExY9bbu+x0FQ5C61LcqmPt7bOQ==", "integrity": "sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==",
"dev": true "dev": true
}, },
"capture-exit": { "capture-exit": {

View File

@@ -32,6 +32,7 @@
"form_error_ip_format": "Invalid IP format", "form_error_ip_format": "Invalid IP format",
"form_error_mac_format": "Invalid MAC format", "form_error_mac_format": "Invalid MAC format",
"form_error_client_id_format": "Invalid client ID format", "form_error_client_id_format": "Invalid client ID format",
"form_error_server_name": "Invalid server name or wildcard certificate",
"form_error_positive": "Must be greater than 0", "form_error_positive": "Must be greater than 0",
"form_error_negative": "Must be equal to 0 or greater", "form_error_negative": "Must be equal to 0 or greater",
"range_end_error": "Must be greater than range start", "range_end_error": "Must be greater than range start",
@@ -331,7 +332,7 @@
"encryption_config_saved": "Encryption config saved", "encryption_config_saved": "Encryption config saved",
"encryption_server": "Server name", "encryption_server": "Server name",
"encryption_server_enter": "Enter your domain name", "encryption_server_enter": "Enter your domain name",
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.", "encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate or wildcard certificate. If the field is not set, it will accept TLS connections for any domain.",
"encryption_redirect": "Redirect to HTTPS automatically", "encryption_redirect": "Redirect to HTTPS automatically",
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.", "encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
"encryption_https": "HTTPS port", "encryption_https": "HTTPS port",
@@ -387,7 +388,7 @@
"client_edit": "Edit Client", "client_edit": "Edit Client",
"client_identifier": "Identifier", "client_identifier": "Identifier",
"ip_address": "IP address", "ip_address": "IP address",
"client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address. Please note that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server</0>", "client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address or domain. Please note that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server</0>",
"form_enter_ip": "Enter IP", "form_enter_ip": "Enter IP",
"form_enter_mac": "Enter MAC", "form_enter_mac": "Enter MAC",
"form_enter_id": "Enter identifier", "form_enter_id": "Enter identifier",

View File

@@ -50,7 +50,7 @@ const CertificateStatus = ({
{dnsNames && ( {dnsNames && (
<li> <li>
<Trans>encryption_hostnames</Trans>:&nbsp; <Trans>encryption_hostnames</Trans>:&nbsp;
{dnsNames} {dnsNames.join(', ')}
</li> </li>
)} )}
</Fragment> </Fragment>
@@ -65,7 +65,7 @@ CertificateStatus.propTypes = {
subject: PropTypes.string, subject: PropTypes.string,
issuer: PropTypes.string, issuer: PropTypes.string,
notAfter: PropTypes.string, notAfter: PropTypes.string,
dnsNames: PropTypes.string, dnsNames: PropTypes.arrayOf(PropTypes.string),
}; };
export default withTranslation()(CertificateStatus); export default withTranslation()(CertificateStatus);

View File

@@ -12,7 +12,7 @@ import {
toNumber, toNumber,
} from '../../../helpers/form'; } from '../../../helpers/form';
import { import {
validateIsSafePort, validatePort, validatePortQuic, validatePortTLS, validateServerName, validateIsSafePort, validatePort, validatePortQuic, validatePortTLS,
} from '../../../helpers/validators'; } from '../../../helpers/validators';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import KeyStatus from './KeyStatus'; import KeyStatus from './KeyStatus';
@@ -127,6 +127,7 @@ let Form = (props) => {
placeholder={t('encryption_server_enter')} placeholder={t('encryption_server_enter')}
onChange={handleChange} onChange={handleChange}
disabled={!isEnabled} disabled={!isEnabled}
validate={validateServerName}
/> />
<div className="form__desc"> <div className="form__desc">
<Trans>encryption_server_desc</Trans> <Trans>encryption_server_desc</Trans>
@@ -413,7 +414,7 @@ Form.propTypes = {
valid_key: PropTypes.bool, valid_key: PropTypes.bool,
valid_cert: PropTypes.bool, valid_cert: PropTypes.bool,
valid_pair: PropTypes.bool, valid_pair: PropTypes.bool,
dns_names: PropTypes.string, dns_names: PropTypes.arrayOf(PropTypes.string),
key_type: PropTypes.string, key_type: PropTypes.string,
issuer: PropTypes.string, issuer: PropTypes.string,
subject: PropTypes.string, subject: PropTypes.string,

View File

@@ -11,15 +11,17 @@ const MOBILE_CONFIG_LINKS = {
DOT: '/apple/dot.mobileconfig', DOT: '/apple/dot.mobileconfig',
DOH: '/apple/doh.mobileconfig', DOH: '/apple/doh.mobileconfig',
}; };
/* FIXME: find out `client_id` */
const renderMobileconfigInfo = ({ label, components, server_name }) => <li key={label}> const renderMobileconfigInfo = ({ label, components, server_name }) => <li key={label}>
<Trans components={components}>{label}</Trans> <Trans components={components}>{label}</Trans>
<ul> <ul>
<li> <li>
<a href={getPathWithQueryString(MOBILE_CONFIG_LINKS.DOT, { host: server_name })} <a href={getPathWithQueryString(MOBILE_CONFIG_LINKS.DOT, { host: server_name, client_id: 'client_id' })}
download>{i18next.t('download_mobileconfig_dot')}</a> download>{i18next.t('download_mobileconfig_dot')}</a>
</li> </li>
<li> <li>
<a href={getPathWithQueryString(MOBILE_CONFIG_LINKS.DOH, { host: server_name })} <a href={getPathWithQueryString(MOBILE_CONFIG_LINKS.DOH, { host: server_name, client_id: 'client_id' })}
download>{i18next.t('download_mobileconfig_doh')}</a> download>{i18next.t('download_mobileconfig_doh')}</a>
</li> </li>
</ul> </ul>

View File

@@ -13,6 +13,10 @@ export const R_MAC = /^((([a-fA-F0-9][a-fA-F0-9]+[-]){5}|([a-fA-F0-9][a-fA-F0-9]
export const R_CIDR_IPV6 = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))$/; export const R_CIDR_IPV6 = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))$/;
export const R_DOMAIN = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/;
export const R_SERVER_NAME = /^(\*\.)?[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/;
export const R_PATH_LAST_PART = /\/[^/]*$/; export const R_PATH_LAST_PART = /\/[^/]*$/;
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex

View File

@@ -9,6 +9,8 @@ import {
R_URL_REQUIRES_PROTOCOL, R_URL_REQUIRES_PROTOCOL,
STANDARD_WEB_PORT, STANDARD_WEB_PORT,
UNSAFE_PORTS, UNSAFE_PORTS,
R_DOMAIN,
R_SERVER_NAME,
} from './constants'; } from './constants';
import { getLastIpv4Octet, isValidAbsolutePath } from './form'; import { getLastIpv4Octet, isValidAbsolutePath } from './form';
@@ -64,19 +66,35 @@ export const validateClientId = (value) => {
if (!value) { if (!value) {
return undefined; return undefined;
} }
const formattedValue = value.trim(); const formattedValue = value ? value.trim() : value;
if (formattedValue && !( if (formattedValue && !(
R_IPV4.test(formattedValue) R_IPV4.test(formattedValue)
|| R_IPV6.test(formattedValue) || R_IPV6.test(formattedValue)
|| R_MAC.test(formattedValue) || R_MAC.test(formattedValue)
|| R_CIDR.test(formattedValue) || R_CIDR.test(formattedValue)
|| R_CIDR_IPV6.test(formattedValue) || R_CIDR_IPV6.test(formattedValue)
|| R_DOMAIN.test(formattedValue)
)) { )) {
return 'form_error_client_id_format'; return 'form_error_client_id_format';
} }
return undefined; return undefined;
}; };
/**
* @param value {string}
* @returns {undefined|string}
*/
export const validateServerName = (value) => {
if (!value) {
return undefined;
}
const formattedValue = value ? value.trim() : value;
if (formattedValue && !R_SERVER_NAME.test(formattedValue)) {
return 'form_error_server_name';
}
return undefined;
};
/** /**
* @param value {string} * @param value {string}
* @returns {undefined|string} * @returns {undefined|string}

17
go.mod
View File

@@ -3,11 +3,12 @@ module github.com/AdguardTeam/AdGuardHome
go 1.14 go 1.14
require ( require (
github.com/AdguardTeam/dnsproxy v0.33.7 github.com/AdguardTeam/dnsproxy v0.33.2
github.com/AdguardTeam/golibs v0.4.4 github.com/AdguardTeam/golibs v0.4.4
github.com/AdguardTeam/urlfilter v0.14.1 github.com/AdguardTeam/urlfilter v0.13.0
github.com/NYTimes/gziphandler v1.1.1 github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.0.1 github.com/ameshkov/dnscrypt/v2 v2.0.0
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect
github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify v1.4.9
github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663
github.com/gobuffalo/envy v1.9.0 // indirect github.com/gobuffalo/envy v1.9.0 // indirect
@@ -15,8 +16,10 @@ require (
github.com/gobuffalo/packr/v2 v2.8.1 // indirect github.com/gobuffalo/packr/v2 v2.8.1 // indirect
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8
github.com/joomcode/errorx v1.0.3 // indirect
github.com/kardianos/service v1.2.0 github.com/kardianos/service v1.2.0
github.com/karrick/godirwalk v1.16.1 // indirect github.com/karrick/godirwalk v1.16.1 // indirect
github.com/lucas-clemente/quic-go v0.19.1 // indirect
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065
github.com/miekg/dns v1.1.35 github.com/miekg/dns v1.1.35
@@ -27,12 +30,14 @@ require (
github.com/stretchr/testify v1.6.1 github.com/stretchr/testify v1.6.1
github.com/u-root/u-root v7.0.0+incompatible github.com/u-root/u-root v7.0.0+incompatible
go.etcd.io/bbolt v1.3.5 go.etcd.io/bbolt v1.3.5
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
golang.org/x/net v0.0.0-20201216054612-986b41b23924 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
golang.org/x/text v0.3.4 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
howett.net/plist v0.0.0-20201026045517-117a925f2150 howett.net/plist v0.0.0-20201026045517-117a925f2150
) )

48
go.sum
View File

@@ -18,16 +18,18 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/AdguardTeam/dnsproxy v0.33.7 h1:DXsLTJoBSUejB2ZqVHyMG0/kXD8PzuVPbLCsGKBdaDc= github.com/AdguardTeam/dnsproxy v0.33.2 h1:k5aMcsw3TA/G2DR8EjIkwutDPuuRkKh8xij4cFWC6Fk=
github.com/AdguardTeam/dnsproxy v0.33.7/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= github.com/AdguardTeam/dnsproxy v0.33.2/go.mod h1:kLi6lMpErnZThy5haiRSis4q0KTB8uPWO4JQsU1EDJA=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.4.3 h1:nXTLLLlIyU4BSRF0An5azS0uimSK/YpIMOBAO0/v1RY=
github.com/AdguardTeam/golibs v0.4.3/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw= github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.14.1 h1:imYls0fit9ojA6pP1hWFUEIjyoXbDF85ZM+G67bI48c= github.com/AdguardTeam/urlfilter v0.13.0 h1:MfO46K81JVTkhgP6gRu/buKl5wAOSfusjiDwjT1JN1c=
github.com/AdguardTeam/urlfilter v0.14.1/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= github.com/AdguardTeam/urlfilter v0.13.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -42,8 +44,8 @@ github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyY
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/ameshkov/dnscrypt/v2 v2.0.1 h1:igNVNM6NLBOqYUzHXaDUxn8i+wJXOsosY0/xEBirixA= github.com/ameshkov/dnscrypt/v2 v2.0.0 h1:i83G8MeGLrAFgUL8GSu98TVhtFDEifF7SIS7Qi/RZ3U=
github.com/ameshkov/dnscrypt/v2 v2.0.1/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI= github.com/ameshkov/dnscrypt/v2 v2.0.0/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI=
github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug= github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug=
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
@@ -53,6 +55,8 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I=
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 h1:M57m0xQqZIhx7CEJgeLSvRFKEK1RjzRuIXiA3HfYU7g= github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 h1:M57m0xQqZIhx7CEJgeLSvRFKEK1RjzRuIXiA3HfYU7g=
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -247,8 +251,10 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4= github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys=
github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg=
github.com/lucas-clemente/quic-go v0.19.1 h1:J9TkQJGJVOR3UmGhd4zdVYwKSA0EoXbLRf15uQJ6gT4=
github.com/lucas-clemente/quic-go v0.19.1/go.mod h1:ZUygOqIoai0ASXXLJ92LTnKdbqh9MHCLTX6Nr1jUrK0=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -259,9 +265,12 @@ github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY
github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc= github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg=
github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ= github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ=
github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -280,6 +289,7 @@ github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00v
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.34/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -447,10 +457,10 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -506,12 +516,9 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgN
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -567,19 +574,14 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214095126-aec9a390925b h1:tv7/y4pd+sR8bcNb2D6o7BNU6zjWm0VjQLac+w7fNNM=
golang.org/x/sys v0.0.0-20201214095126-aec9a390925b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -694,6 +696,8 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=

View File

@@ -188,7 +188,7 @@ func BlockedSvcKnown(s string) bool {
} }
// ApplyBlockedServices - set blocked services settings for this DNS request // ApplyBlockedServices - set blocked services settings for this DNS request
func (d *DNSFilter) ApplyBlockedServices(setts *RequestFilteringSettings, list []string, global bool) { func (d *Dnsfilter) ApplyBlockedServices(setts *RequestFilteringSettings, list []string, global bool) {
setts.ServicesRules = []ServiceEntry{} setts.ServicesRules = []ServiceEntry{}
if global { if global {
d.confLock.RLock() d.confLock.RLock()
@@ -210,7 +210,7 @@ func (d *DNSFilter) ApplyBlockedServices(setts *RequestFilteringSettings, list [
} }
} }
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
d.confLock.RLock() d.confLock.RLock()
list := d.Config.BlockedServices list := d.Config.BlockedServices
d.confLock.RUnlock() d.confLock.RUnlock()
@@ -223,7 +223,7 @@ func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Req
} }
} }
func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
list := []string{} list := []string{}
err := json.NewDecoder(r.Body).Decode(&list) err := json.NewDecoder(r.Body).Decode(&list)
if err != nil { if err != nil {
@@ -241,7 +241,7 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
} }
// registerBlockedServicesHandlers - register HTTP handlers // registerBlockedServicesHandlers - register HTTP handlers
func (d *DNSFilter) registerBlockedServicesHandlers() { func (d *Dnsfilter) registerBlockedServicesHandlers() {
d.Config.HTTPRegister("GET", "/control/blocked_services/list", d.handleBlockedServicesList) d.Config.HTTPRegister("GET", "/control/blocked_services/list", d.handleBlockedServicesList)
d.Config.HTTPRegister("POST", "/control/blocked_services/set", d.handleBlockedServicesSet) d.Config.HTTPRegister("POST", "/control/blocked_services/set", d.handleBlockedServicesSet)
} }

View File

@@ -1,4 +1,4 @@
// Package dnsfilter implements a DNS request and response filter. // Package dnsfilter implements a DNS filter.
package dnsfilter package dnsfilter
import ( import (
@@ -91,12 +91,12 @@ type filtersInitializerParams struct {
blockFilters []Filter blockFilters []Filter
} }
// DNSFilter matches hostnames and DNS requests against filtering rules. // Dnsfilter holds added rules and performs hostname matches against the rules
type DNSFilter struct { type Dnsfilter struct {
rulesStorage *filterlist.RuleStorage rulesStorage *filterlist.RuleStorage
filteringEngine *urlfilter.DNSEngine filteringEngine *urlfilter.DNSEngine
rulesStorageAllow *filterlist.RuleStorage rulesStorageWhite *filterlist.RuleStorage
filteringEngineAllow *urlfilter.DNSEngine filteringEngineWhite *urlfilter.DNSEngine
engineLock sync.RWMutex engineLock sync.RWMutex
parentalServer string // access via methods parentalServer string // access via methods
@@ -127,16 +127,13 @@ const (
// NotFilteredNotFound - host was not find in any checks, default value for result // NotFilteredNotFound - host was not find in any checks, default value for result
NotFilteredNotFound Reason = iota NotFilteredNotFound Reason = iota
// NotFilteredAllowList - the host is explicitly allowed // NotFilteredWhiteList - the host is explicitly whitelisted
NotFilteredAllowList NotFilteredWhiteList
// NotFilteredError is returned when there was an error during
// checking. Reserved, currently unused.
NotFilteredError
// reasons for filtering // reasons for filtering
// FilteredBlockList - the host was matched to be advertising host // FilteredBlackList - the host was matched to be advertising host
FilteredBlockList FilteredBlackList
// FilteredSafeBrowsing - the host was matched to be malicious/phishing // FilteredSafeBrowsing - the host was matched to be malicious/phishing
FilteredSafeBrowsing FilteredSafeBrowsing
// FilteredParental - the host was matched to be outside of parental control settings // FilteredParental - the host was matched to be outside of parental control settings
@@ -148,45 +145,33 @@ const (
// FilteredBlockedService - the host is blocked by "blocked services" settings // FilteredBlockedService - the host is blocked by "blocked services" settings
FilteredBlockedService FilteredBlockedService
// ReasonRewrite is returned when there was a rewrite by // ReasonRewrite - rewrite rule was applied
// a legacy DNS Rewrite rule.
ReasonRewrite ReasonRewrite
// RewriteAutoHosts is returned when there was a rewrite by // RewriteEtcHosts - rewrite by /etc/hosts rule
// autohosts rules (/etc/hosts and so on). RewriteEtcHosts
RewriteAutoHosts
// DNSRewriteRule is returned when a $dnsrewrite filter rule was
// applied.
DNSRewriteRule
) )
// TODO(a.garipov): Resync with actual code names or replace completely
// in HTTP API v1.
var reasonNames = []string{ var reasonNames = []string{
NotFilteredNotFound: "NotFilteredNotFound", "NotFilteredNotFound",
NotFilteredAllowList: "NotFilteredWhiteList", "NotFilteredWhiteList",
NotFilteredError: "NotFilteredError", "NotFilteredError",
FilteredBlockList: "FilteredBlackList", "FilteredBlackList",
FilteredSafeBrowsing: "FilteredSafeBrowsing", "FilteredSafeBrowsing",
FilteredParental: "FilteredParental", "FilteredParental",
FilteredInvalid: "FilteredInvalid", "FilteredInvalid",
FilteredSafeSearch: "FilteredSafeSearch", "FilteredSafeSearch",
FilteredBlockedService: "FilteredBlockedService", "FilteredBlockedService",
ReasonRewrite: "Rewrite", "Rewrite",
"RewriteEtcHosts",
RewriteAutoHosts: "RewriteEtcHosts",
DNSRewriteRule: "DNSRewriteRule",
} }
func (r Reason) String() string { func (r Reason) String() string {
if r < 0 || int(r) >= len(reasonNames) { if uint(r) >= uint(len(reasonNames)) {
return "" return ""
} }
return reasonNames[r] return reasonNames[r]
} }
@@ -201,7 +186,7 @@ func (r Reason) In(reasons ...Reason) bool {
} }
// GetConfig - get configuration // GetConfig - get configuration
func (d *DNSFilter) GetConfig() RequestFilteringSettings { func (d *Dnsfilter) GetConfig() RequestFilteringSettings {
c := RequestFilteringSettings{} c := RequestFilteringSettings{}
// d.confLock.RLock() // d.confLock.RLock()
c.SafeSearchEnabled = d.Config.SafeSearchEnabled c.SafeSearchEnabled = d.Config.SafeSearchEnabled
@@ -212,7 +197,7 @@ func (d *DNSFilter) GetConfig() RequestFilteringSettings {
} }
// WriteDiskConfig - write configuration // WriteDiskConfig - write configuration
func (d *DNSFilter) WriteDiskConfig(c *Config) { func (d *Dnsfilter) WriteDiskConfig(c *Config) {
d.confLock.Lock() d.confLock.Lock()
*c = d.Config *c = d.Config
c.Rewrites = rewriteArrayDup(d.Config.Rewrites) c.Rewrites = rewriteArrayDup(d.Config.Rewrites)
@@ -223,7 +208,7 @@ func (d *DNSFilter) WriteDiskConfig(c *Config) {
// SetFilters - set new filters (synchronously or asynchronously) // SetFilters - set new filters (synchronously or asynchronously)
// When filters are set asynchronously, the old filters continue working until the new filters are ready. // When filters are set asynchronously, the old filters continue working until the new filters are ready.
// In this case the caller must ensure that the old filter files are intact. // In this case the caller must ensure that the old filter files are intact.
func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error { func (d *Dnsfilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
if async { if async {
params := filtersInitializerParams{ params := filtersInitializerParams{
allowFilters: allowFilters, allowFilters: allowFilters,
@@ -257,7 +242,7 @@ func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool)
} }
// Starts initializing new filters by signal from channel // Starts initializing new filters by signal from channel
func (d *DNSFilter) filtersInitializer() { func (d *Dnsfilter) filtersInitializer() {
for { for {
params := <-d.filtersInitializerChan params := <-d.filtersInitializerChan
err := d.initFiltering(params.allowFilters, params.blockFilters) err := d.initFiltering(params.allowFilters, params.blockFilters)
@@ -269,13 +254,13 @@ func (d *DNSFilter) filtersInitializer() {
} }
// Close - close the object // Close - close the object
func (d *DNSFilter) Close() { func (d *Dnsfilter) Close() {
d.engineLock.Lock() d.engineLock.Lock()
defer d.engineLock.Unlock() defer d.engineLock.Unlock()
d.reset() d.reset()
} }
func (d *DNSFilter) reset() { func (d *Dnsfilter) reset() {
var err error var err error
if d.rulesStorage != nil { if d.rulesStorage != nil {
@@ -285,15 +270,16 @@ func (d *DNSFilter) reset() {
} }
} }
if d.rulesStorageAllow != nil { if d.rulesStorageWhite != nil {
err = d.rulesStorageAllow.Close() err = d.rulesStorageWhite.Close()
if err != nil { if err != nil {
log.Error("dnsfilter: rulesStorageAllow.Close: %s", err) log.Error("dnsfilter: rulesStorageWhite.Close: %s", err)
} }
} }
} }
type dnsFilterContext struct { type dnsFilterContext struct {
stats Stats
safebrowsingCache cache.Cache safebrowsingCache cache.Cache
parentalCache cache.Cache parentalCache cache.Cache
safeSearchCache cache.Cache safeSearchCache cache.Cache
@@ -301,63 +287,34 @@ type dnsFilterContext struct {
var gctx dnsFilterContext // global dnsfilter context var gctx dnsFilterContext // global dnsfilter context
// ResultRule contains information about applied rules. // Result holds state of hostname check
type ResultRule struct {
// FilterListID is the ID of the rule's filter list.
FilterListID int64 `json:",omitempty"`
// Text is the text of the rule.
Text string `json:",omitempty"`
// IP is the host IP. It is nil unless the rule uses the
// /etc/hosts syntax or the reason is FilteredSafeSearch.
IP net.IP `json:",omitempty"`
}
// Result contains the result of a request check.
//
// All fields transitively have omitempty tags so that the query log
// doesn't become too large.
//
// TODO(a.garipov): Clarify relationships between fields. Perhaps
// replace with a sum type or an interface?
type Result struct { type Result struct {
// IsFiltered is true if the request is filtered. IsFiltered bool `json:",omitempty"` // True if the host name is filtered
IsFiltered bool `json:",omitempty"` Reason Reason `json:",omitempty"` // Reason for blocking / unblocking
Rule string `json:",omitempty"` // Original rule text
IP net.IP `json:",omitempty"` // Not nil only in the case of a hosts file syntax
FilterID int64 `json:",omitempty"` // Filter ID the rule belongs to
// Reason is the reason for blocking or unblocking the request. // for ReasonRewrite:
Reason Reason `json:",omitempty"` CanonName string `json:",omitempty"` // CNAME value
// Rules are applied rules. If Rules are not empty, each rule // for RewriteEtcHosts:
// is not nil.
Rules []*ResultRule `json:",omitempty"`
// ReverseHosts is the reverse lookup rewrite result. It is
// empty unless Reason is set to RewriteAutoHosts.
ReverseHosts []string `json:",omitempty"` ReverseHosts []string `json:",omitempty"`
// IPList is the lookup rewrite result. It is empty unless // for ReasonRewrite & RewriteEtcHosts:
// Reason is set to RewriteAutoHosts or ReasonRewrite. IPList []net.IP `json:",omitempty"` // list of IP addresses
IPList []net.IP `json:",omitempty"`
// CanonName is the CNAME value from the lookup rewrite result. // for FilteredBlockedService:
// It is empty unless Reason is set to ReasonRewrite. ServiceName string `json:",omitempty"` // Name of the blocked service
CanonName string `json:",omitempty"`
// ServiceName is the name of the blocked service. It is empty
// unless Reason is set to FilteredBlockedService.
ServiceName string `json:",omitempty"`
// DNSRewriteResult is the $dnsrewrite filter rule result.
DNSRewriteResult *DNSRewriteResult `json:",omitempty"`
} }
// Matched returns true if any match at all was found regardless of // Matched can be used to see if any match at all was found, no matter filtered or not
// whether it was filtered or not.
func (r Reason) Matched() bool { func (r Reason) Matched() bool {
return r != NotFilteredNotFound return r != NotFilteredNotFound
} }
// CheckHostRules tries to match the host against filtering rules only. // CheckHostRules tries to match the host against filtering rules only
func (d *DNSFilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) { func (d *Dnsfilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
if !setts.FilteringEnabled { if !setts.FilteringEnabled {
return Result{}, nil return Result{}, nil
} }
@@ -365,9 +322,9 @@ func (d *DNSFilter) CheckHostRules(host string, qtype uint16, setts *RequestFilt
return d.matchHost(host, qtype, *setts) return d.matchHost(host, qtype, *setts)
} }
// CheckHost tries to match the host against filtering rules, then // CheckHost tries to match the host against filtering rules,
// safebrowsing and parental control rules, if they are enabled. // then safebrowsing and parental if they are enabled
func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) { func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
// sometimes DNS clients will try to resolve ".", which is a request to get root servers // sometimes DNS clients will try to resolve ".", which is a request to get root servers
if host == "" { if host == "" {
return Result{Reason: NotFilteredNotFound}, nil return Result{Reason: NotFilteredNotFound}, nil
@@ -392,6 +349,9 @@ func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
} }
} }
// Then check the filter lists.
// if request is blocked -- it should be blocked.
// if it is whitelisted -- we should do nothing with it anymore.
if setts.FilteringEnabled { if setts.FilteringEnabled {
result, err = d.matchHost(host, qtype, *setts) result, err = d.matchHost(host, qtype, *setts)
if err != nil { if err != nil {
@@ -450,10 +410,10 @@ func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
return Result{}, nil return Result{}, nil
} }
func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool) { func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool) {
ips := d.Config.AutoHosts.Process(host, qtype) ips := d.Config.AutoHosts.Process(host, qtype)
if ips != nil { if ips != nil {
result.Reason = RewriteAutoHosts result.Reason = RewriteEtcHosts
result.IPList = ips result.IPList = ips
return true return true
@@ -461,7 +421,7 @@ func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (m
revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype) revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype)
if len(revHosts) != 0 { if len(revHosts) != 0 {
result.Reason = RewriteAutoHosts result.Reason = RewriteEtcHosts
// TODO(a.garipov): Optimize this with a buffer. // TODO(a.garipov): Optimize this with a buffer.
result.ReverseHosts = make([]string, len(revHosts)) result.ReverseHosts = make([]string, len(revHosts))
@@ -482,7 +442,9 @@ func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (m
// . repeat for the new domain name (Note: we return only the last CNAME) // . repeat for the new domain name (Note: we return only the last CNAME)
// . Find A or AAAA record for a domain name (exact match or by wildcard) // . Find A or AAAA record for a domain name (exact match or by wildcard)
// . if found, set IP addresses (IPv4 or IPv6 depending on qtype) in Result.IPList array // . if found, set IP addresses (IPv4 or IPv6 depending on qtype) in Result.IPList array
func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) { func (d *Dnsfilter) processRewrites(host string, qtype uint16) Result {
var res Result
d.confLock.RLock() d.confLock.RLock()
defer d.confLock.RUnlock() defer d.confLock.RUnlock()
@@ -497,8 +459,7 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
log.Debug("Rewrite: CNAME for %s is %s", host, rr[0].Answer) log.Debug("Rewrite: CNAME for %s is %s", host, rr[0].Answer)
if host == rr[0].Answer { // "host == CNAME" is an exception if host == rr[0].Answer { // "host == CNAME" is an exception
res.Reason = NotFilteredNotFound res.Reason = 0
return res return res
} }
@@ -540,16 +501,9 @@ func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result {
res.Reason = FilteredBlockedService res.Reason = FilteredBlockedService
res.IsFiltered = true res.IsFiltered = true
res.ServiceName = s.Name res.ServiceName = s.Name
res.Rule = rule.Text()
ruleText := rule.Text() log.Debug("Blocked Services: matched rule: %s host: %s service: %s",
res.Rules = []*ResultRule{{ res.Rule, host, s.Name)
FilterListID: int64(rule.GetFilterListID()),
Text: ruleText,
}}
log.Debug("blocked services: matched rule: %s host: %s service: %s",
ruleText, host, s.Name)
return res return res
} }
} }
@@ -616,12 +570,12 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
} }
// Initialize urlfilter objects. // Initialize urlfilter objects.
func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error { func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
rulesStorage, filteringEngine, err := createFilteringEngine(blockFilters) rulesStorage, filteringEngine, err := createFilteringEngine(blockFilters)
if err != nil { if err != nil {
return err return err
} }
rulesStorageAllow, filteringEngineAllow, err := createFilteringEngine(allowFilters) rulesStorageWhite, filteringEngineWhite, err := createFilteringEngine(allowFilters)
if err != nil { if err != nil {
return err return err
} }
@@ -630,8 +584,8 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
d.reset() d.reset()
d.rulesStorage = rulesStorage d.rulesStorage = rulesStorage
d.filteringEngine = filteringEngine d.filteringEngine = filteringEngine
d.rulesStorageAllow = rulesStorageAllow d.rulesStorageWhite = rulesStorageWhite
d.filteringEngineAllow = filteringEngineAllow d.filteringEngineWhite = filteringEngineWhite
d.engineLock.Unlock() d.engineLock.Unlock()
// Make sure that the OS reclaims memory as soon as possible // Make sure that the OS reclaims memory as soon as possible
@@ -641,31 +595,9 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
return nil return nil
} }
// matchHostProcessAllowList processes the allowlist logic of host
// matching.
func (d *DNSFilter) matchHostProcessAllowList(host string, dnsres urlfilter.DNSResult) (res Result, err error) {
var rule rules.Rule
if dnsres.NetworkRule != nil {
rule = dnsres.NetworkRule
} else if len(dnsres.HostRulesV4) > 0 {
rule = dnsres.HostRulesV4[0]
} else if len(dnsres.HostRulesV6) > 0 {
rule = dnsres.HostRulesV6[0]
}
if rule == nil {
return Result{}, fmt.Errorf("invalid dns result: rules are empty")
}
log.Debug("Filtering: found allowlist rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID())
return makeResult(rule, NotFilteredAllowList), nil
}
// matchHost is a low-level way to check only if hostname is filtered by rules, // matchHost is a low-level way to check only if hostname is filtered by rules,
// skipping expensive safebrowsing and parental lookups. // skipping expensive safebrowsing and parental lookups.
func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (res Result, err error) { func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (Result, error) {
d.engineLock.RLock() d.engineLock.RLock()
// Keep in mind that this lock must be held no just when calling Match() // Keep in mind that this lock must be held no just when calling Match()
// but also while using the rules returned by it. // but also while using the rules returned by it.
@@ -679,10 +611,22 @@ func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringS
DNSType: qtype, DNSType: qtype,
} }
if d.filteringEngineAllow != nil { if d.filteringEngineWhite != nil {
dnsres, ok := d.filteringEngineAllow.MatchRequest(ureq) rr, ok := d.filteringEngineWhite.MatchRequest(ureq)
if ok { if ok {
return d.matchHostProcessAllowList(host, dnsres) var rule rules.Rule
if rr.NetworkRule != nil {
rule = rr.NetworkRule
} else if rr.HostRulesV4 != nil {
rule = rr.HostRulesV4[0]
} else if rr.HostRulesV6 != nil {
rule = rr.HostRulesV6[0]
}
log.Debug("Filtering: found whitelist rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID())
res := makeResult(rule, NotFilteredWhiteList)
return res, nil
} }
} }
@@ -690,87 +634,68 @@ func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringS
return Result{}, nil return Result{}, nil
} }
dnsres, ok := d.filteringEngine.MatchRequest(ureq) rr, ok := d.filteringEngine.MatchRequest(ureq)
if !ok {
// Check DNS rewrites first, because the API there is a bit
// awkward.
if dnsr := dnsres.DNSRewrites(); len(dnsr) > 0 {
res = d.processDNSRewrites(dnsr)
if res.Reason == DNSRewriteRule && res.CanonName == host {
// A rewrite of a host to itself. Go on and
// try matching other things.
} else {
return res, nil
}
} else if !ok {
return Result{}, nil return Result{}, nil
} }
if dnsres.NetworkRule != nil { if rr.NetworkRule != nil {
log.Debug("Filtering: found rule for host %q: %q list_id: %d", log.Debug("Filtering: found rule for host %q: %q list_id: %d",
host, dnsres.NetworkRule.Text(), dnsres.NetworkRule.GetFilterListID()) host, rr.NetworkRule.Text(), rr.NetworkRule.GetFilterListID())
reason := FilteredBlockList reason := FilteredBlackList
if dnsres.NetworkRule.Whitelist { if rr.NetworkRule.Whitelist {
reason = NotFilteredAllowList reason = NotFilteredWhiteList
} }
res := makeResult(rr.NetworkRule, reason)
return makeResult(dnsres.NetworkRule, reason), nil
}
if qtype == dns.TypeA && dnsres.HostRulesV4 != nil {
rule := dnsres.HostRulesV4[0] // note that we process only 1 matched rule
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID())
res = makeResult(rule, FilteredBlockList)
res.Rules[0].IP = rule.IP.To4()
return res, nil return res, nil
} }
if qtype == dns.TypeAAAA && dnsres.HostRulesV6 != nil { if qtype == dns.TypeA && rr.HostRulesV4 != nil {
rule := dnsres.HostRulesV6[0] // note that we process only 1 matched rule rule := rr.HostRulesV4[0] // note that we process only 1 matched rule
log.Debug("Filtering: found rule for host %q: %q list_id: %d", log.Debug("Filtering: found rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID()) host, rule.Text(), rule.GetFilterListID())
res = makeResult(rule, FilteredBlockList) res := makeResult(rule, FilteredBlackList)
res.Rules[0].IP = rule.IP res.IP = rule.IP.To4()
return res, nil return res, nil
} }
if dnsres.HostRulesV4 != nil || dnsres.HostRulesV6 != nil { if qtype == dns.TypeAAAA && rr.HostRulesV6 != nil {
rule := rr.HostRulesV6[0] // note that we process only 1 matched rule
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID())
res := makeResult(rule, FilteredBlackList)
res.IP = rule.IP
return res, nil
}
if rr.HostRulesV4 != nil || rr.HostRulesV6 != nil {
// Question Type doesn't match the host rules // Question Type doesn't match the host rules
// Return the first matched host rule, but without an IP address // Return the first matched host rule, but without an IP address
var rule rules.Rule var rule rules.Rule
if dnsres.HostRulesV4 != nil { if rr.HostRulesV4 != nil {
rule = dnsres.HostRulesV4[0] rule = rr.HostRulesV4[0]
} else if dnsres.HostRulesV6 != nil { } else if rr.HostRulesV6 != nil {
rule = dnsres.HostRulesV6[0] rule = rr.HostRulesV6[0]
} }
log.Debug("Filtering: found rule for host %q: %q list_id: %d", log.Debug("Filtering: found rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID()) host, rule.Text(), rule.GetFilterListID())
res = makeResult(rule, FilteredBlockList) res := makeResult(rule, FilteredBlackList)
res.Rules[0].IP = net.IP{} res.IP = net.IP{}
return res, nil return res, nil
} }
return Result{}, nil return Result{}, nil
} }
// makeResult returns a properly constructed Result. // Construct Result object
func makeResult(rule rules.Rule, reason Reason) Result { func makeResult(rule rules.Rule, reason Reason) Result {
res := Result{ res := Result{}
Reason: reason, res.FilterID = int64(rule.GetFilterListID())
Rules: []*ResultRule{{ res.Rule = rule.Text()
FilterListID: int64(rule.GetFilterListID()), res.Reason = reason
Text: rule.Text(), if reason == FilteredBlackList {
}},
}
if reason == FilteredBlockList {
res.IsFiltered = true res.IsFiltered = true
} }
return res return res
} }
@@ -780,7 +705,7 @@ func InitModule() {
} }
// New creates properly initialized DNS Filter that is ready to be used. // New creates properly initialized DNS Filter that is ready to be used.
func New(c *Config, blockFilters []Filter) *DNSFilter { func New(c *Config, blockFilters []Filter) *Dnsfilter {
if c != nil { if c != nil {
cacheConf := cache.Config{ cacheConf := cache.Config{
EnableLRU: true, EnableLRU: true,
@@ -802,7 +727,7 @@ func New(c *Config, blockFilters []Filter) *DNSFilter {
} }
} }
d := new(DNSFilter) d := new(Dnsfilter)
err := d.initSecurityServices() err := d.initSecurityServices()
if err != nil { if err != nil {
@@ -840,7 +765,7 @@ func New(c *Config, blockFilters []Filter) *DNSFilter {
// Start - start the module: // Start - start the module:
// . start async filtering initializer goroutine // . start async filtering initializer goroutine
// . register web handlers // . register web handlers
func (d *DNSFilter) Start() { func (d *Dnsfilter) Start() {
d.filtersInitializerChan = make(chan filtersInitializerParams, 1) d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
go d.filtersInitializer() go d.filtersInitializer()

View File

@@ -41,7 +41,7 @@ func purgeCaches() {
} }
} }
func NewForTest(c *Config, filters []Filter) *DNSFilter { func NewForTest(c *Config, filters []Filter) *Dnsfilter {
setts = RequestFilteringSettings{} setts = RequestFilteringSettings{}
setts.FilteringEnabled = true setts.FilteringEnabled = true
if c != nil { if c != nil {
@@ -58,48 +58,38 @@ func NewForTest(c *Config, filters []Filter) *DNSFilter {
return d return d
} }
func (d *DNSFilter) checkMatch(t *testing.T, hostname string) { func (d *Dnsfilter) checkMatch(t *testing.T, hostname string) {
t.Helper() t.Helper()
res, err := d.CheckHost(hostname, dns.TypeA, &setts) ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
if err != nil { if err != nil {
t.Errorf("Error while matching host %s: %s", hostname, err) t.Errorf("Error while matching host %s: %s", hostname, err)
} }
if !res.IsFiltered { if !ret.IsFiltered {
t.Errorf("Expected hostname %s to match", hostname) t.Errorf("Expected hostname %s to match", hostname)
} }
} }
func (d *DNSFilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) { func (d *Dnsfilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) {
t.Helper() t.Helper()
ret, err := d.CheckHost(hostname, qtype, &setts)
res, err := d.CheckHost(hostname, qtype, &setts)
if err != nil { if err != nil {
t.Errorf("Error while matching host %s: %s", hostname, err) t.Errorf("Error while matching host %s: %s", hostname, err)
} }
if !ret.IsFiltered {
if !res.IsFiltered {
t.Errorf("Expected hostname %s to match", hostname) t.Errorf("Expected hostname %s to match", hostname)
} }
if ret.IP == nil || ret.IP.String() != ip {
if len(res.Rules) == 0 { t.Errorf("Expected ip %s to match, actual: %v", ip, ret.IP)
t.Errorf("Expected result to have rules")
return
}
r := res.Rules[0]
if r.IP == nil || r.IP.String() != ip {
t.Errorf("Expected ip %s to match, actual: %v", ip, r.IP)
} }
} }
func (d *DNSFilter) checkMatchEmpty(t *testing.T, hostname string) { func (d *Dnsfilter) checkMatchEmpty(t *testing.T, hostname string) {
t.Helper() t.Helper()
res, err := d.CheckHost(hostname, dns.TypeA, &setts) ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
if err != nil { if err != nil {
t.Errorf("Error while matching host %s: %s", hostname, err) t.Errorf("Error while matching host %s: %s", hostname, err)
} }
if res.IsFiltered { if ret.IsFiltered {
t.Errorf("Expected hostname %s to not match", hostname) t.Errorf("Expected hostname %s to not match", hostname)
} }
} }
@@ -130,43 +120,26 @@ func TestEtcHostsMatching(t *testing.T) {
d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA) d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA)
// ...but empty IPv6 // ...but empty IPv6
res, err := d.CheckHost("block.com", dns.TypeAAAA, &setts) ret, err := d.CheckHost("block.com", dns.TypeAAAA, &setts)
assert.Nil(t, err) assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0)
assert.True(t, res.IsFiltered) assert.True(t, ret.Rule == "0.0.0.0 block.com")
if assert.Len(t, res.Rules, 1) {
assert.Equal(t, "0.0.0.0 block.com", res.Rules[0].Text)
assert.Len(t, res.Rules[0].IP, 0)
}
// IPv6 // IPv6
d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA) d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA)
// ...but empty IPv4 // ...but empty IPv4
res, err = d.CheckHost("ipv6.com", dns.TypeA, &setts) ret, err = d.CheckHost("ipv6.com", dns.TypeA, &setts)
assert.Nil(t, err) assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0)
assert.True(t, res.IsFiltered)
if assert.Len(t, res.Rules, 1) {
assert.Equal(t, "::1 ipv6.com", res.Rules[0].Text)
assert.Len(t, res.Rules[0].IP, 0)
}
// 2 IPv4 (return only the first one) // 2 IPv4 (return only the first one)
res, err = d.CheckHost("host2", dns.TypeA, &setts) ret, err = d.CheckHost("host2", dns.TypeA, &setts)
assert.Nil(t, err) assert.True(t, err == nil && ret.IsFiltered)
assert.True(t, res.IsFiltered) assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("0.0.0.1")))
if assert.Len(t, res.Rules, 1) {
loopback4 := net.IP{0, 0, 0, 1}
assert.Equal(t, res.Rules[0].IP, loopback4)
}
// ...and 1 IPv6 address // ...and 1 IPv6 address
res, err = d.CheckHost("host2", dns.TypeAAAA, &setts) ret, err = d.CheckHost("host2", dns.TypeAAAA, &setts)
assert.Nil(t, err) assert.True(t, err == nil && ret.IsFiltered)
assert.True(t, res.IsFiltered) assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("::1")))
if assert.Len(t, res.Rules, 1) {
loopback6 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
assert.Equal(t, res.Rules[0].IP, loopback6)
}
} }
// SAFE BROWSING // SAFE BROWSING
@@ -178,6 +151,7 @@ func TestSafeBrowsing(t *testing.T) {
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
defer d.Close() defer d.Close()
gctx.stats.Safebrowsing.Requests = 0
d.checkMatch(t, "wmconvirus.narod.ru") d.checkMatch(t, "wmconvirus.narod.ru")
assert.True(t, strings.Contains(logOutput.String(), "SafeBrowsing lookup for wmconvirus.narod.ru")) assert.True(t, strings.Contains(logOutput.String(), "SafeBrowsing lookup for wmconvirus.narod.ru"))
@@ -232,11 +206,13 @@ func TestCheckHostSafeSearchYandex(t *testing.T) {
// Check host for each domain // Check host for each domain
for _, host := range yandex { for _, host := range yandex {
res, err := d.CheckHost(host, dns.TypeA, &setts) result, err := d.CheckHost(host, dns.TypeA, &setts)
assert.Nil(t, err) if err != nil {
assert.True(t, res.IsFiltered) t.Errorf("SafeSearch doesn't work for yandex domain `%s` cause %s", host, err)
if assert.Len(t, res.Rules, 1) { }
assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56")
if result.IP.String() != "213.180.193.56" {
t.Errorf("SafeSearch doesn't work for yandex domain `%s`", host)
} }
} }
} }
@@ -250,11 +226,13 @@ func TestCheckHostSafeSearchGoogle(t *testing.T) {
// Check host for each domain // Check host for each domain
for _, host := range googleDomains { for _, host := range googleDomains {
res, err := d.CheckHost(host, dns.TypeA, &setts) result, err := d.CheckHost(host, dns.TypeA, &setts)
assert.Nil(t, err) if err != nil {
assert.True(t, res.IsFiltered) t.Errorf("SafeSearch doesn't work for %s cause %s", host, err)
if assert.Len(t, res.Rules, 1) { }
assert.NotEqual(t, res.Rules[0].IP.String(), "0.0.0.0")
if result.IP == nil {
t.Errorf("SafeSearch doesn't work for %s", host)
} }
} }
} }
@@ -264,30 +242,40 @@ func TestSafeSearchCacheYandex(t *testing.T) {
defer d.Close() defer d.Close()
domain := "yandex.ru" domain := "yandex.ru"
// Check host with disabled safesearch. var result Result
res, err := d.CheckHost(domain, dns.TypeA, &setts) var err error
assert.Nil(t, err)
assert.False(t, res.IsFiltered) // Check host with disabled safesearch
assert.Len(t, res.Rules, 0) result, err = d.CheckHost(domain, dns.TypeA, &setts)
if err != nil {
t.Fatalf("Cannot check host due to %s", err)
}
if result.IP != nil {
t.Fatalf("SafeSearch is not enabled but there is an answer for `%s` !", domain)
}
d = NewForTest(&Config{SafeSearchEnabled: true}, nil) d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
defer d.Close() defer d.Close()
res, err = d.CheckHost(domain, dns.TypeA, &setts) result, err = d.CheckHost(domain, dns.TypeA, &setts)
if err != nil { if err != nil {
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err) t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
} }
// For yandex we already know valid ip. // Fir yandex we already know valid ip
if assert.Len(t, res.Rules, 1) { if result.IP.String() != "213.180.193.56" {
assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56") t.Fatalf("Wrong IP for %s safesearch: %s", domain, result.IP.String())
} }
// Check cache. // Check cache
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain) cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
assert.True(t, isFound)
if assert.Len(t, cachedValue.Rules, 1) { if !isFound {
assert.Equal(t, cachedValue.Rules[0].IP.String(), "213.180.193.56") t.Fatalf("Safesearch cache doesn't work for %s!", domain)
}
if cachedValue.IP.String() != "213.180.193.56" {
t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String())
} }
} }
@@ -295,10 +283,13 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
d := NewForTest(nil, nil) d := NewForTest(nil, nil)
defer d.Close() defer d.Close()
domain := "www.google.ru" domain := "www.google.ru"
res, err := d.CheckHost(domain, dns.TypeA, &setts) result, err := d.CheckHost(domain, dns.TypeA, &setts)
assert.Nil(t, err) if err != nil {
assert.False(t, res.IsFiltered) t.Fatalf("Cannot check host due to %s", err)
assert.Len(t, res.Rules, 0) }
if result.IP != nil {
t.Fatalf("SafeSearch is not enabled but there is an answer!")
}
d = NewForTest(&Config{SafeSearchEnabled: true}, nil) d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
defer d.Close() defer d.Close()
@@ -322,17 +313,25 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
} }
} }
res, err = d.CheckHost(domain, dns.TypeA, &setts) result, err = d.CheckHost(domain, dns.TypeA, &setts)
assert.Nil(t, err) if err != nil {
if assert.Len(t, res.Rules, 1) { t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
assert.True(t, res.Rules[0].IP.Equal(ip))
} }
// Check cache. if result.IP.String() != ip.String() {
t.Fatalf("Wrong IP for %s safesearch: %s. Should be: %s",
domain, result.IP.String(), ip)
}
// Check cache
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain) cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
assert.True(t, isFound)
if assert.Len(t, cachedValue.Rules, 1) { if !isFound {
assert.True(t, cachedValue.Rules[0].IP.Equal(ip)) t.Fatalf("Safesearch cache doesn't work for %s!", domain)
}
if cachedValue.IP.String() != ip.String() {
t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String())
} }
} }
@@ -365,7 +364,7 @@ const nl = "\n"
const ( const (
blockingRules = `||example.org^` + nl blockingRules = `||example.org^` + nl
allowlistRules = `||example.org^` + nl + `@@||test.example.org` + nl whitelistRules = `||example.org^` + nl + `@@||test.example.org` + nl
importantRules = `@@||example.org^` + nl + `||test.example.org^$important` + nl importantRules = `@@||example.org^` + nl + `||test.example.org^$important` + nl
regexRules = `/example\.org/` + nl + `@@||test.example.org^` + nl regexRules = `/example\.org/` + nl + `@@||test.example.org^` + nl
maskRules = `test*.example.org^` + nl + `exam*.com` + nl maskRules = `test*.example.org^` + nl + `exam*.com` + nl
@@ -380,49 +379,49 @@ var tests = []struct {
reason Reason reason Reason
dnsType uint16 dnsType uint16
}{ }{
{"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlockList, dns.TypeA}, {"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlackList, dns.TypeA},
{"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound, dns.TypeA}, {"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound, dns.TypeA},
{"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound, dns.TypeA}, {"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound, dns.TypeA},
{"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound, dns.TypeA}, {"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound, dns.TypeA},
{"blocking", blockingRules, "example.org", true, FilteredBlockList, dns.TypeA}, {"blocking", blockingRules, "example.org", true, FilteredBlackList, dns.TypeA},
{"blocking", blockingRules, "test.example.org", true, FilteredBlockList, dns.TypeA}, {"blocking", blockingRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA}, {"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA},
{"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, {"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
{"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, {"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
{"allowlist", allowlistRules, "example.org", true, FilteredBlockList, dns.TypeA}, {"whitelist", whitelistRules, "example.org", true, FilteredBlackList, dns.TypeA},
{"allowlist", allowlistRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA}, {"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
{"allowlist", allowlistRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA}, {"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA},
{"allowlist", allowlistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, {"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
{"allowlist", allowlistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, {"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
{"important", importantRules, "example.org", false, NotFilteredAllowList, dns.TypeA}, {"important", importantRules, "example.org", false, NotFilteredWhiteList, dns.TypeA},
{"important", importantRules, "test.example.org", true, FilteredBlockList, dns.TypeA}, {"important", importantRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
{"important", importantRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA}, {"important", importantRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA},
{"important", importantRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, {"important", importantRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
{"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, {"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
{"regex", regexRules, "example.org", true, FilteredBlockList, dns.TypeA}, {"regex", regexRules, "example.org", true, FilteredBlackList, dns.TypeA},
{"regex", regexRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA}, {"regex", regexRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
{"regex", regexRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA}, {"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA},
{"regex", regexRules, "testexample.org", true, FilteredBlockList, dns.TypeA}, {"regex", regexRules, "testexample.org", true, FilteredBlackList, dns.TypeA},
{"regex", regexRules, "onemoreexample.org", true, FilteredBlockList, dns.TypeA}, {"regex", regexRules, "onemoreexample.org", true, FilteredBlackList, dns.TypeA},
{"mask", maskRules, "test.example.org", true, FilteredBlockList, dns.TypeA}, {"mask", maskRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
{"mask", maskRules, "test2.example.org", true, FilteredBlockList, dns.TypeA}, {"mask", maskRules, "test2.example.org", true, FilteredBlackList, dns.TypeA},
{"mask", maskRules, "example.com", true, FilteredBlockList, dns.TypeA}, {"mask", maskRules, "example.com", true, FilteredBlackList, dns.TypeA},
{"mask", maskRules, "exampleeee.com", true, FilteredBlockList, dns.TypeA}, {"mask", maskRules, "exampleeee.com", true, FilteredBlackList, dns.TypeA},
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlockList, dns.TypeA}, {"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList, dns.TypeA},
{"mask", maskRules, "example.org", false, NotFilteredNotFound, dns.TypeA}, {"mask", maskRules, "example.org", false, NotFilteredNotFound, dns.TypeA},
{"mask", maskRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, {"mask", maskRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
{"mask", maskRules, "example.co.uk", false, NotFilteredNotFound, dns.TypeA}, {"mask", maskRules, "example.co.uk", false, NotFilteredNotFound, dns.TypeA},
{"dnstype", dnstypeRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, {"dnstype", dnstypeRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
{"dnstype", dnstypeRules, "example.org", false, NotFilteredNotFound, dns.TypeA}, {"dnstype", dnstypeRules, "example.org", false, NotFilteredNotFound, dns.TypeA},
{"dnstype", dnstypeRules, "example.org", true, FilteredBlockList, dns.TypeAAAA}, {"dnstype", dnstypeRules, "example.org", true, FilteredBlackList, dns.TypeAAAA},
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA}, {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeAAAA}, {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeAAAA},
} }
func TestMatching(t *testing.T) { func TestMatching(t *testing.T) {
@@ -434,15 +433,15 @@ func TestMatching(t *testing.T) {
d := NewForTest(nil, filters) d := NewForTest(nil, filters)
defer d.Close() defer d.Close()
res, err := d.CheckHost(test.hostname, test.dnsType, &setts) ret, err := d.CheckHost(test.hostname, test.dnsType, &setts)
if err != nil { if err != nil {
t.Errorf("Error while matching host %s: %s", test.hostname, err) t.Errorf("Error while matching host %s: %s", test.hostname, err)
} }
if res.IsFiltered != test.isFiltered { if ret.IsFiltered != test.isFiltered {
t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, res.IsFiltered, test.isFiltered) t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, ret.IsFiltered, test.isFiltered)
} }
if res.Reason != test.reason { if ret.Reason != test.reason {
t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, res.Reason.String(), test.reason.String()) t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, ret.Reason.String(), test.reason.String())
} }
}) })
} }
@@ -467,20 +466,16 @@ func TestWhitelist(t *testing.T) {
defer d.Close() defer d.Close()
// matched by white filter // matched by white filter
res, err := d.CheckHost("host1", dns.TypeA, &setts) ret, err := d.CheckHost("host1", dns.TypeA, &setts)
assert.True(t, err == nil) assert.True(t, err == nil)
assert.True(t, !res.IsFiltered && res.Reason == NotFilteredAllowList) assert.True(t, !ret.IsFiltered && ret.Reason == NotFilteredWhiteList)
if assert.Len(t, res.Rules, 1) { assert.True(t, ret.Rule == "||host1^")
assert.True(t, res.Rules[0].Text == "||host1^")
}
// not matched by white filter, but matched by block filter // not matched by white filter, but matched by block filter
res, err = d.CheckHost("host2", dns.TypeA, &setts) ret, err = d.CheckHost("host2", dns.TypeA, &setts)
assert.True(t, err == nil) assert.True(t, err == nil)
assert.True(t, res.IsFiltered && res.Reason == FilteredBlockList) assert.True(t, ret.IsFiltered && ret.Reason == FilteredBlackList)
if assert.Len(t, res.Rules, 1) { assert.True(t, ret.Rule == "||host2^")
assert.True(t, res.Rules[0].Text == "||host2^")
}
} }
// CLIENT SETTINGS // CLIENT SETTINGS
@@ -511,8 +506,8 @@ func TestClientSettings(t *testing.T) {
// blocked by filters // blocked by filters
r, _ = d.CheckHost("example.org", dns.TypeA, &setts) r, _ = d.CheckHost("example.org", dns.TypeA, &setts)
if !r.IsFiltered || r.Reason != FilteredBlockList { if !r.IsFiltered || r.Reason != FilteredBlackList {
t.Fatalf("CheckHost FilteredBlockList") t.Fatalf("CheckHost FilteredBlackList")
} }
// blocked by parental // blocked by parental
@@ -564,11 +559,11 @@ func BenchmarkSafeBrowsing(b *testing.B) {
defer d.Close() defer d.Close()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
hostname := "wmconvirus.narod.ru" hostname := "wmconvirus.narod.ru"
res, err := d.CheckHost(hostname, dns.TypeA, &setts) ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
if err != nil { if err != nil {
b.Errorf("Error while matching host %s: %s", hostname, err) b.Errorf("Error while matching host %s: %s", hostname, err)
} }
if !res.IsFiltered { if !ret.IsFiltered {
b.Errorf("Expected hostname %s to match", hostname) b.Errorf("Expected hostname %s to match", hostname)
} }
} }
@@ -580,11 +575,11 @@ func BenchmarkSafeBrowsingParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
hostname := "wmconvirus.narod.ru" hostname := "wmconvirus.narod.ru"
res, err := d.CheckHost(hostname, dns.TypeA, &setts) ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
if err != nil { if err != nil {
b.Errorf("Error while matching host %s: %s", hostname, err) b.Errorf("Error while matching host %s: %s", hostname, err)
} }
if !res.IsFiltered { if !ret.IsFiltered {
b.Errorf("Expected hostname %s to match", hostname) b.Errorf("Expected hostname %s to match", hostname)
} }
} }

View File

@@ -1,80 +0,0 @@
package dnsfilter
import (
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
)
// DNSRewriteResult is the result of application of $dnsrewrite rules.
type DNSRewriteResult struct {
Response DNSRewriteResultResponse `json:",omitempty"`
RCode rules.RCode `json:",omitempty"`
}
// DNSRewriteResultResponse is the collection of DNS response records
// the server returns.
type DNSRewriteResultResponse map[rules.RRType][]rules.RRValue
// processDNSRewrites processes DNS rewrite rules in dnsr. It returns
// an empty result if dnsr is empty. Otherwise, the result will have
// either CanonName or DNSRewriteResult set.
func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) {
if len(dnsr) == 0 {
return Result{}
}
var rules []*ResultRule
dnsrr := &DNSRewriteResult{
Response: DNSRewriteResultResponse{},
}
for _, nr := range dnsr {
dr := nr.DNSRewrite
if dr.NewCNAME != "" {
// NewCNAME rules have a higher priority than
// the other rules.
rules := []*ResultRule{{
FilterListID: int64(nr.GetFilterListID()),
Text: nr.RuleText,
}}
return Result{
Reason: DNSRewriteRule,
Rules: rules,
CanonName: dr.NewCNAME,
}
}
switch dr.RCode {
case dns.RcodeSuccess:
dnsrr.RCode = dr.RCode
dnsrr.Response[dr.RRType] = append(dnsrr.Response[dr.RRType], dr.Value)
rules = append(rules, &ResultRule{
FilterListID: int64(nr.GetFilterListID()),
Text: nr.RuleText,
})
default:
// RcodeRefused and other such codes have higher
// priority. Return immediately.
rules := []*ResultRule{{
FilterListID: int64(nr.GetFilterListID()),
Text: nr.RuleText,
}}
dnsrr = &DNSRewriteResult{
RCode: dr.RCode,
}
return Result{
Reason: DNSRewriteRule,
Rules: rules,
DNSRewriteResult: dnsrr,
}
}
}
return Result{
Reason: DNSRewriteRule,
Rules: rules,
DNSRewriteResult: dnsrr,
}
}

View File

@@ -1,202 +0,0 @@
package dnsfilter
import (
"net"
"path"
"testing"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
)
func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
const text = `
|cname^$dnsrewrite=new_cname
|a_record^$dnsrewrite=127.0.0.1
|aaaa_record^$dnsrewrite=::1
|txt_record^$dnsrewrite=NOERROR;TXT;hello_world
|refused^$dnsrewrite=REFUSED
|a_records^$dnsrewrite=127.0.0.1
|a_records^$dnsrewrite=127.0.0.2
|aaaa_records^$dnsrewrite=::1
|aaaa_records^$dnsrewrite=::2
|disable_one^$dnsrewrite=127.0.0.1
|disable_one^$dnsrewrite=127.0.0.2
@@||disable_one^$dnsrewrite=127.0.0.1
|disable_cname^$dnsrewrite=127.0.0.1
|disable_cname^$dnsrewrite=new_cname
@@||disable_cname^$dnsrewrite=new_cname
|disable_cname_many^$dnsrewrite=127.0.0.1
|disable_cname_many^$dnsrewrite=new_cname_1
|disable_cname_many^$dnsrewrite=new_cname_2
@@||disable_cname_many^$dnsrewrite=new_cname_1
|disable_all^$dnsrewrite=127.0.0.1
|disable_all^$dnsrewrite=127.0.0.2
@@||disable_all^$dnsrewrite
`
f := NewForTest(nil, []Filter{{ID: 0, Data: []byte(text)}})
setts := &RequestFilteringSettings{
FilteringEnabled: true,
}
ipv4p1 := net.IPv4(127, 0, 0, 1)
ipv4p2 := net.IPv4(127, 0, 0, 2)
ipv6p1 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
ipv6p2 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
t.Run("cname", func(t *testing.T) {
dtyp := dns.TypeA
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
assert.Equal(t, "new_cname", res.CanonName)
})
t.Run("a_record", func(t *testing.T) {
dtyp := dns.TypeA
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) {
assert.Equal(t, ipv4p1, ipVals[0])
}
}
})
t.Run("aaaa_record", func(t *testing.T) {
dtyp := dns.TypeAAAA
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) {
assert.Equal(t, ipv6p1, ipVals[0])
}
}
})
t.Run("txt_record", func(t *testing.T) {
dtyp := dns.TypeTXT
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
if strVals := dnsrr.Response[dtyp]; assert.Len(t, strVals, 1) {
assert.Equal(t, "hello_world", strVals[0])
}
}
})
t.Run("refused", func(t *testing.T) {
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dns.TypeA, setts)
assert.Nil(t, err)
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
assert.Equal(t, dns.RcodeRefused, dnsrr.RCode)
}
})
t.Run("a_records", func(t *testing.T) {
dtyp := dns.TypeA
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 2) {
assert.Equal(t, ipv4p1, ipVals[0])
assert.Equal(t, ipv4p2, ipVals[1])
}
}
})
t.Run("aaaa_records", func(t *testing.T) {
dtyp := dns.TypeAAAA
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 2) {
assert.Equal(t, ipv6p1, ipVals[0])
assert.Equal(t, ipv6p2, ipVals[1])
}
}
})
t.Run("disable_one", func(t *testing.T) {
dtyp := dns.TypeA
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) {
assert.Equal(t, ipv4p2, ipVals[0])
}
}
})
t.Run("disable_cname", func(t *testing.T) {
dtyp := dns.TypeA
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
assert.Equal(t, "", res.CanonName)
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) {
assert.Equal(t, ipv4p1, ipVals[0])
}
}
})
t.Run("disable_cname_many", func(t *testing.T) {
dtyp := dns.TypeA
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
assert.Equal(t, "new_cname_2", res.CanonName)
assert.Nil(t, res.DNSRewriteResult)
})
t.Run("disable_all", func(t *testing.T) {
dtyp := dns.TypeA
host := path.Base(t.Name())
res, err := f.CheckHostRules(host, dtyp, setts)
assert.Nil(t, err)
assert.Equal(t, "", res.CanonName)
assert.Len(t, res.Rules, 0)
})
}

View File

@@ -95,7 +95,7 @@ func (r *RewriteEntry) prepare() {
} }
} }
func (d *DNSFilter) prepareRewrites() { func (d *Dnsfilter) prepareRewrites() {
for i := range d.Rewrites { for i := range d.Rewrites {
d.Rewrites[i].prepare() d.Rewrites[i].prepare()
} }
@@ -148,7 +148,7 @@ type rewriteEntryJSON struct {
Answer string `json:"answer"` Answer string `json:"answer"`
} }
func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
arr := []*rewriteEntryJSON{} arr := []*rewriteEntryJSON{}
d.confLock.Lock() d.confLock.Lock()
@@ -169,7 +169,7 @@ func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
} }
} }
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
jsent := rewriteEntryJSON{} jsent := rewriteEntryJSON{}
err := json.NewDecoder(r.Body).Decode(&jsent) err := json.NewDecoder(r.Body).Decode(&jsent)
if err != nil { if err != nil {
@@ -191,7 +191,7 @@ func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
d.Config.ConfigModified() d.Config.ConfigModified()
} }
func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
jsent := rewriteEntryJSON{} jsent := rewriteEntryJSON{}
err := json.NewDecoder(r.Body).Decode(&jsent) err := json.NewDecoder(r.Body).Decode(&jsent)
if err != nil { if err != nil {
@@ -218,7 +218,7 @@ func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request)
d.Config.ConfigModified() d.Config.ConfigModified()
} }
func (d *DNSFilter) registerRewritesHandlers() { func (d *Dnsfilter) registerRewritesHandlers() {
d.Config.HTTPRegister("GET", "/control/rewrite/list", d.handleRewriteList) d.Config.HTTPRegister("GET", "/control/rewrite/list", d.handleRewriteList)
d.Config.HTTPRegister("POST", "/control/rewrite/add", d.handleRewriteAdd) d.Config.HTTPRegister("POST", "/control/rewrite/add", d.handleRewriteAdd)
d.Config.HTTPRegister("POST", "/control/rewrite/delete", d.handleRewriteDelete) d.Config.HTTPRegister("POST", "/control/rewrite/delete", d.handleRewriteDelete)

View File

@@ -9,7 +9,7 @@ import (
) )
func TestRewrites(t *testing.T) { func TestRewrites(t *testing.T) {
d := DNSFilter{} d := Dnsfilter{}
// CNAME, A, AAAA // CNAME, A, AAAA
d.Rewrites = []RewriteEntry{ d.Rewrites = []RewriteEntry{
{"somecname", "somehost.com", 0, nil}, {"somecname", "somehost.com", 0, nil},
@@ -104,7 +104,7 @@ func TestRewrites(t *testing.T) {
} }
func TestRewritesLevels(t *testing.T) { func TestRewritesLevels(t *testing.T) {
d := DNSFilter{} d := Dnsfilter{}
// exact host, wildcard L2, wildcard L3 // exact host, wildcard L2, wildcard L3
d.Rewrites = []RewriteEntry{ d.Rewrites = []RewriteEntry{
{"host.com", "1.1.1.1", 0, nil}, {"host.com", "1.1.1.1", 0, nil},
@@ -133,7 +133,7 @@ func TestRewritesLevels(t *testing.T) {
} }
func TestRewritesExceptionCNAME(t *testing.T) { func TestRewritesExceptionCNAME(t *testing.T) {
d := DNSFilter{} d := Dnsfilter{}
// wildcard; exception for a sub-domain // wildcard; exception for a sub-domain
d.Rewrites = []RewriteEntry{ d.Rewrites = []RewriteEntry{
{"*.host.com", "2.2.2.2", 0, nil}, {"*.host.com", "2.2.2.2", 0, nil},
@@ -153,7 +153,7 @@ func TestRewritesExceptionCNAME(t *testing.T) {
} }
func TestRewritesExceptionWC(t *testing.T) { func TestRewritesExceptionWC(t *testing.T) {
d := DNSFilter{} d := Dnsfilter{}
// wildcard; exception for a sub-wildcard // wildcard; exception for a sub-wildcard
d.Rewrites = []RewriteEntry{ d.Rewrites = []RewriteEntry{
{"*.host.com", "2.2.2.2", 0, nil}, {"*.host.com", "2.2.2.2", 0, nil},
@@ -173,7 +173,7 @@ func TestRewritesExceptionWC(t *testing.T) {
} }
func TestRewritesExceptionIP(t *testing.T) { func TestRewritesExceptionIP(t *testing.T) {
d := DNSFilter{} d := Dnsfilter{}
// exception for AAAA record // exception for AAAA record
d.Rewrites = []RewriteEntry{ d.Rewrites = []RewriteEntry{
{"host.com", "1.2.3.4", 0, nil}, {"host.com", "1.2.3.4", 0, nil},

View File

@@ -18,7 +18,7 @@ import (
expire byte[4] expire byte[4]
res Result res Result
*/ */
func (d *DNSFilter) setCacheResult(cache cache.Cache, host string, res Result) int { func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int {
var buf bytes.Buffer var buf bytes.Buffer
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60 expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
@@ -63,12 +63,12 @@ func getCachedResult(cache cache.Cache, host string) (Result, bool) {
} }
// SafeSearchDomain returns replacement address for search engine // SafeSearchDomain returns replacement address for search engine
func (d *DNSFilter) SafeSearchDomain(host string) (string, bool) { func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
val, ok := safeSearchDomains[host] val, ok := safeSearchDomains[host]
return val, ok return val, ok
} }
func (d *DNSFilter) checkSafeSearch(host string) (Result, error) { func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
if log.GetLevel() >= log.DEBUG { if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer() timer := log.StartTimer()
defer timer.LogElapsed("SafeSearch: lookup for %s", host) defer timer.LogElapsed("SafeSearch: lookup for %s", host)
@@ -87,52 +87,49 @@ func (d *DNSFilter) checkSafeSearch(host string) (Result, error) {
return Result{}, nil return Result{}, nil
} }
res := Result{ res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
IsFiltered: true,
Reason: FilteredSafeSearch,
Rules: []*ResultRule{{}},
}
if ip := net.ParseIP(safeHost); ip != nil { if ip := net.ParseIP(safeHost); ip != nil {
res.Rules[0].IP = ip res.IP = ip
valLen := d.setCacheResult(gctx.safeSearchCache, host, res) valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen) log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
return res, nil return res, nil
} }
// TODO this address should be resolved with upstream that was configured in dnsforward // TODO this address should be resolved with upstream that was configured in dnsforward
ips, err := net.LookupIP(safeHost) addrs, err := net.LookupIP(safeHost)
if err != nil { if err != nil {
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err) log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
return Result{}, err return Result{}, err
} }
for _, ip := range ips { for _, i := range addrs {
if ipv4 := ip.To4(); ipv4 != nil { if ipv4 := i.To4(); ipv4 != nil {
res.Rules[0].IP = ipv4 res.IP = ipv4
break
l := d.setCacheResult(gctx.safeSearchCache, host, res)
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, l)
return res, nil
} }
} }
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost) if len(res.IP) == 0 {
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
}
// Cache result
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
return res, nil
} }
func (d *DNSFilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeSearchEnabled = true d.Config.SafeSearchEnabled = true
d.Config.ConfigModified() d.Config.ConfigModified()
} }
func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeSearchEnabled = false d.Config.SafeSearchEnabled = false
d.Config.ConfigModified() d.Config.ConfigModified()
} }
func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{ data := map[string]interface{}{
"enabled": d.Config.SafeSearchEnabled, "enabled": d.Config.SafeSearchEnabled,
} }

View File

@@ -1,3 +1,5 @@
// Safe Browsing, Parental Control
package dnsfilter package dnsfilter
import ( import (
@@ -20,8 +22,6 @@ import (
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )
// Safe browsing and parental control methods.
const ( const (
dnsTimeout = 3 * time.Second dnsTimeout = 3 * time.Second
defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query` defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
@@ -30,7 +30,7 @@ const (
pcTXTSuffix = `pc.dns.adguard.com.` pcTXTSuffix = `pc.dns.adguard.com.`
) )
func (d *DNSFilter) initSecurityServices() error { func (d *Dnsfilter) initSecurityServices() error {
var err error var err error
d.safeBrowsingServer = defaultSafebrowsingServer d.safeBrowsingServer = defaultSafebrowsingServer
d.parentalServer = defaultParentalServer d.parentalServer = defaultParentalServer
@@ -287,7 +287,7 @@ func check(c *sbCtx, r Result, u upstream.Upstream) (Result, error) {
return Result{}, nil return Result{}, nil
} }
func (d *DNSFilter) checkSafeBrowsing(host string) (Result, error) { func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
if log.GetLevel() >= log.DEBUG { if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer() timer := log.StartTimer()
defer timer.LogElapsed("SafeBrowsing lookup for %s", host) defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
@@ -301,14 +301,12 @@ func (d *DNSFilter) checkSafeBrowsing(host string) (Result, error) {
res := Result{ res := Result{
IsFiltered: true, IsFiltered: true,
Reason: FilteredSafeBrowsing, Reason: FilteredSafeBrowsing,
Rules: []*ResultRule{{ Rule: "adguard-malware-shavar",
Text: "adguard-malware-shavar",
}},
} }
return check(ctx, res, d.safeBrowsingUpstream) return check(ctx, res, d.safeBrowsingUpstream)
} }
func (d *DNSFilter) checkParental(host string) (Result, error) { func (d *Dnsfilter) checkParental(host string) (Result, error) {
if log.GetLevel() >= log.DEBUG { if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer() timer := log.StartTimer()
defer timer.LogElapsed("Parental lookup for %s", host) defer timer.LogElapsed("Parental lookup for %s", host)
@@ -322,9 +320,7 @@ func (d *DNSFilter) checkParental(host string) (Result, error) {
res := Result{ res := Result{
IsFiltered: true, IsFiltered: true,
Reason: FilteredParental, Reason: FilteredParental,
Rules: []*ResultRule{{ Rule: "parental CATEGORY_BLACKLISTED",
Text: "parental CATEGORY_BLACKLISTED",
}},
} }
return check(ctx, res, d.parentalUpstream) return check(ctx, res, d.parentalUpstream)
} }
@@ -335,17 +331,17 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string,
http.Error(w, text, code) http.Error(w, text, code)
} }
func (d *DNSFilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeBrowsingEnabled = true d.Config.SafeBrowsingEnabled = true
d.Config.ConfigModified() d.Config.ConfigModified()
} }
func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeBrowsingEnabled = false d.Config.SafeBrowsingEnabled = false
d.Config.ConfigModified() d.Config.ConfigModified()
} }
func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{ data := map[string]interface{}{
"enabled": d.Config.SafeBrowsingEnabled, "enabled": d.Config.SafeBrowsingEnabled,
} }
@@ -362,17 +358,17 @@ func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Requ
} }
} }
func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
d.Config.ParentalEnabled = true d.Config.ParentalEnabled = true
d.Config.ConfigModified() d.Config.ConfigModified()
} }
func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) {
d.Config.ParentalEnabled = false d.Config.ParentalEnabled = false
d.Config.ConfigModified() d.Config.ConfigModified()
} }
func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) { func (d *Dnsfilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{ data := map[string]interface{}{
"enabled": d.Config.ParentalEnabled, "enabled": d.Config.ParentalEnabled,
} }
@@ -390,7 +386,7 @@ func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request)
} }
} }
func (d *DNSFilter) registerSecurityHandlers() { func (d *Dnsfilter) registerSecurityHandlers() {
d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable) d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable) d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus) d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus)

View File

@@ -366,9 +366,7 @@ func processFilteringAfterResponse(ctx *dnsContext) int {
var err error var err error
switch res.Reason { switch res.Reason {
case dnsfilter.ReasonRewrite, case dnsfilter.ReasonRewrite:
dnsfilter.DNSRewriteRule:
if len(ctx.origQuestion.Name) == 0 { if len(ctx.origQuestion.Name) == 0 {
// origQuestion is set in case we get only CNAME without IP from rewrites table // origQuestion is set in case we get only CNAME without IP from rewrites table
break break
@@ -379,12 +377,12 @@ func processFilteringAfterResponse(ctx *dnsContext) int {
if len(d.Res.Answer) != 0 { if len(d.Res.Answer) != 0 {
answer := []dns.RR{} answer := []dns.RR{}
answer = append(answer, s.genAnswerCNAME(d.Req, res.CanonName)) answer = append(answer, s.genCNAMEAnswer(d.Req, res.CanonName))
answer = append(answer, d.Res.Answer...) answer = append(answer, d.Res.Answer...) // host -> IP
d.Res.Answer = answer d.Res.Answer = answer
} }
case dnsfilter.NotFilteredAllowList: case dnsfilter.NotFilteredWhiteList:
// nothing // nothing
default: default:

View File

@@ -48,7 +48,7 @@ var webRegistered bool
// The zero Server is empty and ready for use. // The zero Server is empty and ready for use.
type Server struct { type Server struct {
dnsProxy *proxy.Proxy // DNS proxy instance dnsProxy *proxy.Proxy // DNS proxy instance
dnsFilter *dnsfilter.DNSFilter // DNS filter instance dnsFilter *dnsfilter.Dnsfilter // DNS filter instance
dhcpServer dhcpd.ServerInterface // DHCP server instance (optional) dhcpServer dhcpd.ServerInterface // DHCP server instance (optional)
queryLog querylog.QueryLog // Query log instance queryLog querylog.QueryLog // Query log instance
stats stats.Stats stats stats.Stats
@@ -74,7 +74,7 @@ type Server struct {
// DNSCreateParams - parameters for NewServer() // DNSCreateParams - parameters for NewServer()
type DNSCreateParams struct { type DNSCreateParams struct {
DNSFilter *dnsfilter.DNSFilter DNSFilter *dnsfilter.Dnsfilter
Stats stats.Stats Stats stats.Stats
QueryLog querylog.QueryLog QueryLog querylog.QueryLog
DHCPServer dhcpd.ServerInterface DHCPServer dhcpd.ServerInterface

View File

@@ -296,7 +296,7 @@ func TestBlockedRequest(t *testing.T) {
func TestServerCustomClientUpstream(t *testing.T) { func TestServerCustomClientUpstream(t *testing.T) {
s := createTestServer(t) s := createTestServer(t)
s.conf.GetCustomUpstreamByClient = func(_ string) *proxy.UpstreamConfig { s.conf.GetCustomUpstreamByClient = func(clientAddr string) *proxy.UpstreamConfig {
uc := &proxy.UpstreamConfig{} uc := &proxy.UpstreamConfig{}
u := &testUpstream{} u := &testUpstream{}
u.ipv4 = map[string][]net.IP{} u.ipv4 = map[string][]net.IP{}
@@ -473,7 +473,7 @@ func TestBlockCNAME(t *testing.T) {
func TestClientRulesForCNAMEMatching(t *testing.T) { func TestClientRulesForCNAMEMatching(t *testing.T) {
s := createTestServer(t) s := createTestServer(t)
testUpstm := &testUpstream{testCNAMEs, testIPv4, nil} testUpstm := &testUpstream{testCNAMEs, testIPv4, nil}
s.conf.FilterHandler = func(_ string, settings *dnsfilter.RequestFilteringSettings) { s.conf.FilterHandler = func(clientAddr string, settings *dnsfilter.RequestFilteringSettings) {
settings.FilteringEnabled = false settings.FilteringEnabled = false
} }
err := s.startWithUpstream(testUpstm) err := s.startWithUpstream(testUpstm)
@@ -863,8 +863,6 @@ func sendTestMessages(t *testing.T, conn *dns.Conn) {
} }
func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host, ip string) { func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host, ip string) {
t.Helper()
req := createTestMessage(host) req := createTestMessage(host)
reply, _, err := client.Exchange(req, addr.String()) reply, _, err := client.Exchange(req, addr.String())
if err != nil { if err != nil {
@@ -902,8 +900,6 @@ func assertGoogleAResponse(t *testing.T, reply *dns.Msg) {
} }
func assertResponse(t *testing.T, reply *dns.Msg, ip string) { func assertResponse(t *testing.T, reply *dns.Msg, ip string) {
t.Helper()
if len(reply.Answer) != 1 { if len(reply.Answer) != 1 {
t.Fatalf("DNS server returned reply with wrong number of answers - %d", len(reply.Answer)) t.Fatalf("DNS server returned reply with wrong number of answers - %d", len(reply.Answer))
} }

View File

@@ -1,107 +0,0 @@
package dnsforward
import (
"fmt"
"net"
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
)
// filterDNSRewriteResponse handles a single DNS rewrite response entry.
// It returns the properly constructed answer resource record.
func (s *Server) filterDNSRewriteResponse(req *dns.Msg, rr rules.RRType, v rules.RRValue) (ans dns.RR, err error) {
// TODO(a.garipov): As more types are added, we will probably want to
// use a handler-oriented approach here. So, think of a way to decouple
// the answer generation logic from the Server.
switch rr {
case dns.TypeA, dns.TypeAAAA:
ip, ok := v.(net.IP)
if !ok {
return nil, fmt.Errorf("value for rr type %d has type %T, not net.IP", rr, v)
}
if rr == dns.TypeA {
return s.genAnswerA(req, ip.To4()), nil
}
return s.genAnswerAAAA(req, ip), nil
case dns.TypePTR,
dns.TypeTXT:
str, ok := v.(string)
if !ok {
return nil, fmt.Errorf("value for rr type %d has type %T, not string", rr, v)
}
if rr == dns.TypeTXT {
return s.genAnswerTXT(req, []string{str}), nil
}
return s.genAnswerPTR(req, str), nil
case dns.TypeMX:
mx, ok := v.(*rules.DNSMX)
if !ok {
return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSMX", rr, v)
}
return s.genAnswerMX(req, mx), nil
case dns.TypeHTTPS,
dns.TypeSVCB:
svcb, ok := v.(*rules.DNSSVCB)
if !ok {
return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSSVCB", rr, v)
}
if rr == dns.TypeHTTPS {
return s.genAnswerHTTPS(req, svcb), nil
}
return s.genAnswerSVCB(req, svcb), nil
default:
log.Debug("don't know how to handle dns rr type %d, skipping", rr)
return nil, nil
}
}
// filterDNSRewrite handles dnsrewrite filters. It constructs a DNS
// response and sets it into d.Res.
func (s *Server) filterDNSRewrite(req *dns.Msg, res dnsfilter.Result, d *proxy.DNSContext) (err error) {
resp := s.makeResponse(req)
dnsrr := res.DNSRewriteResult
if dnsrr == nil {
return agherr.Error("no dns rewrite rule content")
}
resp.Rcode = dnsrr.RCode
if resp.Rcode != dns.RcodeSuccess {
d.Res = resp
return nil
}
if dnsrr.Response == nil {
return agherr.Error("no dns rewrite rule responses")
}
rr := req.Question[0].Qtype
values := dnsrr.Response[rr]
for i, v := range values {
var ans dns.RR
ans, err = s.filterDNSRewriteResponse(req, rr, v)
if err != nil {
return fmt.Errorf("dns rewrite response for %d[%d]: %w", rr, i, err)
}
resp.Answer = append(resp.Answer, ans)
}
d.Res = resp
return nil
}

View File

@@ -42,8 +42,7 @@ func (s *Server) getClientRequestFilteringSettings(d *proxy.DNSContext) *dnsfilt
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 filtered
// was 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
req := d.Req req := d.Req
@@ -53,17 +52,13 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
// 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)
} else if res.IsFiltered { } else if res.IsFiltered {
log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rules[0].Text) log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule)
d.Res = s.genDNSFilterMessage(d, &res) d.Res = s.genDNSFilterMessage(d, &res)
} else if res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.DNSRewriteRule) && } else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
res.CanonName != "" &&
len(res.IPList) == 0 {
// Resolve the new canonical name, not the original host
// name. The original question is readded in
// processFilteringAfterResponse.
ctx.origQuestion = d.Req.Question[0] ctx.origQuestion = d.Req.Question[0]
// resolve canonical name, not the original host name
d.Req.Question[0].Name = dns.Fqdn(res.CanonName) d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
} else if res.Reason == dnsfilter.RewriteAutoHosts && len(res.ReverseHosts) != 0 { } else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHosts) != 0 {
resp := s.makeResponse(req) resp := s.makeResponse(req)
for _, h := range res.ReverseHosts { for _, h := range res.ReverseHosts {
hdr := dns.RR_Header{ hdr := dns.RR_Header{
@@ -82,33 +77,28 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
} }
d.Res = resp d.Res = resp
} else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteAutoHosts { } else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts {
resp := s.makeResponse(req) resp := s.makeResponse(req)
name := host name := host
if len(res.CanonName) != 0 { if len(res.CanonName) != 0 {
resp.Answer = append(resp.Answer, s.genAnswerCNAME(req, res.CanonName)) resp.Answer = append(resp.Answer, s.genCNAMEAnswer(req, res.CanonName))
name = res.CanonName name = res.CanonName
} }
for _, ip := range res.IPList { for _, ip := range res.IPList {
if req.Question[0].Qtype == dns.TypeA { if req.Question[0].Qtype == dns.TypeA {
a := s.genAnswerA(req, ip.To4()) a := s.genAAnswer(req, ip.To4())
a.Hdr.Name = dns.Fqdn(name) a.Hdr.Name = dns.Fqdn(name)
resp.Answer = append(resp.Answer, a) resp.Answer = append(resp.Answer, a)
} else if req.Question[0].Qtype == dns.TypeAAAA { } else if req.Question[0].Qtype == dns.TypeAAAA {
a := s.genAnswerAAAA(req, ip) a := s.genAAAAAnswer(req, ip)
a.Hdr.Name = dns.Fqdn(name) a.Hdr.Name = dns.Fqdn(name)
resp.Answer = append(resp.Answer, a) resp.Answer = append(resp.Answer, a)
} }
} }
d.Res = resp d.Res = resp
} else if res.Reason == dnsfilter.DNSRewriteRule {
err = s.filterDNSRewrite(req, res, d)
if err != nil {
return nil, err
}
} }
return &res, err return &res, err

View File

@@ -167,12 +167,11 @@ func (req *dnsConfig) checkCacheTTL() bool {
if req.CacheMinTTL == nil && req.CacheMaxTTL == nil { if req.CacheMinTTL == nil && req.CacheMaxTTL == nil {
return true return true
} }
var min, max uint32 var min, max uint32
if req.CacheMinTTL != nil { if req.CacheMinTTL != nil {
min = *req.CacheMinTTL min = *req.CacheMinTTL
} }
if req.CacheMaxTTL != nil { if req.CacheMaxTTL == nil {
max = *req.CacheMaxTTL max = *req.CacheMaxTTL
} }

View File

@@ -7,22 +7,16 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
"github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
// Create a DNS response by DNS request and set necessary flags // Create a DNS response by DNS request and set necessary flags
func (s *Server) makeResponse(req *dns.Msg) (resp *dns.Msg) { func (s *Server) makeResponse(req *dns.Msg) *dns.Msg {
resp = &dns.Msg{ resp := dns.Msg{}
MsgHdr: dns.MsgHdr{
RecursionAvailable: true,
},
Compress: true,
}
resp.SetReply(req) resp.SetReply(req)
resp.RecursionAvailable = true
return resp resp.Compress = true
return &resp
} }
// genDNSFilterMessage generates a DNS message corresponding to the filtering result // genDNSFilterMessage generates a DNS message corresponding to the filtering result
@@ -45,10 +39,8 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
// If the query was filtered by "Safe search", dnsfilter also must return // If the query was filtered by "Safe search", dnsfilter also must return
// the IP address that must be used in response. // the IP address that must be used in response.
// In this case regardless of the filtering method, we should return it // In this case regardless of the filtering method, we should return it
if result.Reason == dnsfilter.FilteredSafeSearch && if result.Reason == dnsfilter.FilteredSafeSearch && result.IP != nil {
len(result.Rules) > 0 && return s.genResponseWithIP(m, result.IP)
result.Rules[0].IP != nil {
return s.genResponseWithIP(m, result.Rules[0].IP)
} }
if s.conf.BlockingMode == "null_ip" { if s.conf.BlockingMode == "null_ip" {
@@ -76,8 +68,8 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
// Default blocking mode // Default blocking mode
// If there's an IP specified in the rule, return it // If there's an IP specified in the rule, return it
// For host-type rules, return null IP // For host-type rules, return null IP
if len(result.Rules) > 0 && result.Rules[0].IP != nil { if result.IP != nil {
return s.genResponseWithIP(m, result.Rules[0].IP) return s.genResponseWithIP(m, result.IP)
} }
return s.makeResponseNullIP(m) return s.makeResponseNullIP(m)
@@ -93,66 +85,38 @@ func (s *Server) genServerFailure(request *dns.Msg) *dns.Msg {
func (s *Server) genARecord(request *dns.Msg, ip net.IP) *dns.Msg { func (s *Server) genARecord(request *dns.Msg, ip net.IP) *dns.Msg {
resp := s.makeResponse(request) resp := s.makeResponse(request)
resp.Answer = append(resp.Answer, s.genAnswerA(request, ip)) resp.Answer = append(resp.Answer, s.genAAnswer(request, ip))
return resp return resp
} }
func (s *Server) genAAAARecord(request *dns.Msg, ip net.IP) *dns.Msg { func (s *Server) genAAAARecord(request *dns.Msg, ip net.IP) *dns.Msg {
resp := s.makeResponse(request) resp := s.makeResponse(request)
resp.Answer = append(resp.Answer, s.genAnswerAAAA(request, ip)) resp.Answer = append(resp.Answer, s.genAAAAAnswer(request, ip))
return resp return resp
} }
func (s *Server) hdr(req *dns.Msg, rrType rules.RRType) (h dns.RR_Header) { func (s *Server) genAAnswer(req *dns.Msg, ip net.IP) *dns.A {
return dns.RR_Header{ answer := new(dns.A)
answer.Hdr = dns.RR_Header{
Name: req.Question[0].Name, Name: req.Question[0].Name,
Rrtype: rrType, Rrtype: dns.TypeA,
Ttl: s.conf.BlockedResponseTTL, Ttl: s.conf.BlockedResponseTTL,
Class: dns.ClassINET, Class: dns.ClassINET,
} }
answer.A = ip
return answer
} }
func (s *Server) genAnswerA(req *dns.Msg, ip net.IP) (ans *dns.A) { func (s *Server) genAAAAAnswer(req *dns.Msg, ip net.IP) *dns.AAAA {
return &dns.A{ answer := new(dns.AAAA)
Hdr: s.hdr(req, dns.TypeA), answer.Hdr = dns.RR_Header{
A: ip, Name: req.Question[0].Name,
} Rrtype: dns.TypeAAAA,
} Ttl: s.conf.BlockedResponseTTL,
Class: dns.ClassINET,
func (s *Server) genAnswerAAAA(req *dns.Msg, ip net.IP) (ans *dns.AAAA) {
return &dns.AAAA{
Hdr: s.hdr(req, dns.TypeAAAA),
AAAA: ip,
}
}
func (s *Server) genAnswerCNAME(req *dns.Msg, cname string) (ans *dns.CNAME) {
return &dns.CNAME{
Hdr: s.hdr(req, dns.TypeCNAME),
Target: dns.Fqdn(cname),
}
}
func (s *Server) genAnswerMX(req *dns.Msg, mx *rules.DNSMX) (ans *dns.MX) {
return &dns.MX{
Hdr: s.hdr(req, dns.TypePTR),
Preference: mx.Preference,
Mx: mx.Exchange,
}
}
func (s *Server) genAnswerPTR(req *dns.Msg, ptr string) (ans *dns.PTR) {
return &dns.PTR{
Hdr: s.hdr(req, dns.TypePTR),
Ptr: ptr,
}
}
func (s *Server) genAnswerTXT(req *dns.Msg, strs []string) (ans *dns.TXT) {
return &dns.TXT{
Hdr: s.hdr(req, dns.TypeTXT),
Txt: strs,
} }
answer.AAAA = ip
return answer
} }
// generate DNS response message with an IP address // generate DNS response message with an IP address
@@ -215,6 +179,19 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
return resp return resp
} }
// Make a CNAME response
func (s *Server) genCNAMEAnswer(req *dns.Msg, cname string) *dns.CNAME {
answer := new(dns.CNAME)
answer.Hdr = dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypeCNAME,
Ttl: s.conf.BlockedResponseTTL,
Class: dns.ClassINET,
}
answer.Target = dns.Fqdn(cname)
return answer
}
// Create REFUSED DNS response // Create REFUSED DNS response
func (s *Server) makeResponseREFUSED(request *dns.Msg) *dns.Msg { func (s *Server) makeResponseREFUSED(request *dns.Msg) *dns.Msg {
resp := dns.Msg{} resp := dns.Msg{}

View File

@@ -91,7 +91,7 @@ func (s *Server) updateStats(d *proxy.DNSContext, elapsed time.Duration, res dns
case dnsfilter.FilteredSafeSearch: case dnsfilter.FilteredSafeSearch:
e.Result = stats.RSafeSearch e.Result = stats.RSafeSearch
case dnsfilter.FilteredBlockList: case dnsfilter.FilteredBlackList:
fallthrough fallthrough
case dnsfilter.FilteredInvalid: case dnsfilter.FilteredInvalid:
fallthrough fallthrough

View File

@@ -1,168 +0,0 @@
package dnsforward
import (
"encoding/base64"
"net"
"strconv"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
)
// genAnswerHTTPS returns a properly initialized HTTPS resource record.
//
// See the comment on genAnswerSVCB for a list of current restrictions on
// parameter values.
func (s *Server) genAnswerHTTPS(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns.HTTPS) {
ans = &dns.HTTPS{
SVCB: *s.genAnswerSVCB(req, svcb),
}
ans.Hdr.Rrtype = dns.TypeHTTPS
return ans
}
// strToSVCBKey is the string-to-svcb-key mapping.
//
// See https://github.com/miekg/dns/blob/23c4faca9d32b0abbb6e179aa1aadc45ac53a916/svcb.go#L27.
//
// TODO(a.garipov): Propose exporting this API or something similar in the
// github.com/miekg/dns module.
var strToSVCBKey = map[string]dns.SVCBKey{
"alpn": dns.SVCB_ALPN,
"echconfig": dns.SVCB_ECHCONFIG,
"ipv4hint": dns.SVCB_IPV4HINT,
"ipv6hint": dns.SVCB_IPV6HINT,
"mandatory": dns.SVCB_MANDATORY,
"no-default-alpn": dns.SVCB_NO_DEFAULT_ALPN,
"port": dns.SVCB_PORT,
}
// svcbKeyHandler is a handler for one SVCB parameter key.
type svcbKeyHandler func(valStr string) (val dns.SVCBKeyValue)
// svcbKeyHandlers are the supported SVCB parameters handlers.
var svcbKeyHandlers = map[string]svcbKeyHandler{
"alpn": func(valStr string) (val dns.SVCBKeyValue) {
return &dns.SVCBAlpn{
Alpn: []string{valStr},
}
},
"echconfig": func(valStr string) (val dns.SVCBKeyValue) {
ech, err := base64.StdEncoding.DecodeString(valStr)
if err != nil {
log.Debug("can't parse svcb/https echconfig: %s; ignoring", err)
return nil
}
return &dns.SVCBECHConfig{
ECH: ech,
}
},
"ipv4hint": func(valStr string) (val dns.SVCBKeyValue) {
ip := net.ParseIP(valStr)
if ip4 := ip.To4(); ip == nil || ip4 == nil {
log.Debug("can't parse svcb/https ipv4 hint %q; ignoring", valStr)
return nil
}
return &dns.SVCBIPv4Hint{
Hint: []net.IP{ip},
}
},
"ipv6hint": func(valStr string) (val dns.SVCBKeyValue) {
ip := net.ParseIP(valStr)
if ip == nil {
log.Debug("can't parse svcb/https ipv6 hint %q; ignoring", valStr)
return nil
}
return &dns.SVCBIPv6Hint{
Hint: []net.IP{ip},
}
},
"mandatory": func(valStr string) (val dns.SVCBKeyValue) {
code, ok := strToSVCBKey[valStr]
if !ok {
log.Debug("unknown svcb/https mandatory key %q, ignoring", valStr)
return nil
}
return &dns.SVCBMandatory{
Code: []dns.SVCBKey{code},
}
},
"no-default-alpn": func(_ string) (val dns.SVCBKeyValue) {
return &dns.SVCBNoDefaultAlpn{}
},
"port": func(valStr string) (val dns.SVCBKeyValue) {
port64, err := strconv.ParseUint(valStr, 10, 16)
if err != nil {
log.Debug("can't parse svcb/https port: %s; ignoring", err)
return nil
}
return &dns.SVCBPort{
Port: uint16(port64),
}
},
}
// genAnswerSVCB returns a properly initialized SVCB resource record.
//
// Currently, there are several restrictions on how the parameters are parsed.
// Firstly, the parsing of non-contiguous values isn't supported. Secondly, the
// parsing of value-lists is not supported either.
//
// ipv4hint=127.0.0.1 // Supported.
// ipv4hint="127.0.0.1" // Unsupported.
// ipv4hint=127.0.0.1,127.0.0.2 // Unsupported.
// ipv4hint="127.0.0.1,127.0.0.2" // Unsupported.
//
// TODO(a.garipov): Support all of these.
func (s *Server) genAnswerSVCB(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns.SVCB) {
ans = &dns.SVCB{
Hdr: s.hdr(req, dns.TypeSVCB),
Priority: svcb.Priority,
Target: svcb.Target,
}
if len(svcb.Params) == 0 {
return ans
}
values := make([]dns.SVCBKeyValue, 0, len(svcb.Params))
for k, valStr := range svcb.Params {
handler, ok := svcbKeyHandlers[k]
if !ok {
log.Debug("unknown svcb/https key %q, ignoring", k)
continue
}
val := handler(valStr)
if val == nil {
continue
}
values = append(values, val)
}
if len(values) > 0 {
ans.Value = values
}
return ans
}

View File

@@ -1,154 +0,0 @@
package dnsforward
import (
"net"
"testing"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
)
func TestGenAnswerHTTPS_andSVCB(t *testing.T) {
// Preconditions.
s := &Server{
conf: ServerConfig{
FilteringConfig: FilteringConfig{
BlockedResponseTTL: 3600,
},
},
}
req := &dns.Msg{
Question: []dns.Question{{
Name: "abcd",
}},
}
// Constants and helper values.
const host = "example.com"
const prio = 32
ip4 := net.IPv4(127, 0, 0, 1)
ip6 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
// Helper functions.
dnssvcb := func(key, value string) (svcb *rules.DNSSVCB) {
svcb = &rules.DNSSVCB{
Target: host,
Priority: prio,
}
if key == "" {
return svcb
}
svcb.Params = map[string]string{
key: value,
}
return svcb
}
wantsvcb := func(kv dns.SVCBKeyValue) (want *dns.SVCB) {
want = &dns.SVCB{
Hdr: s.hdr(req, dns.TypeSVCB),
Priority: prio,
Target: host,
}
if kv == nil {
return want
}
want.Value = []dns.SVCBKeyValue{kv}
return want
}
// Tests.
testCases := []struct {
svcb *rules.DNSSVCB
want *dns.SVCB
name string
}{{
svcb: dnssvcb("", ""),
want: wantsvcb(nil),
name: "no_params",
}, {
svcb: dnssvcb("foo", "bar"),
want: wantsvcb(nil),
name: "invalid",
}, {
svcb: dnssvcb("alpn", "h3"),
want: wantsvcb(&dns.SVCBAlpn{Alpn: []string{"h3"}}),
name: "alpn",
}, {
svcb: dnssvcb("echconfig", "AAAA"),
want: wantsvcb(&dns.SVCBECHConfig{ECH: []byte{0, 0, 0}}),
name: "echconfig",
}, {
svcb: dnssvcb("echconfig", "%BAD%"),
want: wantsvcb(nil),
name: "echconfig_invalid",
}, {
svcb: dnssvcb("ipv4hint", "127.0.0.1"),
want: wantsvcb(&dns.SVCBIPv4Hint{Hint: []net.IP{ip4}}),
name: "ipv4hint",
}, {
svcb: dnssvcb("ipv4hint", "127.0.01"),
want: wantsvcb(nil),
name: "ipv4hint_invalid",
}, {
svcb: dnssvcb("ipv6hint", "::1"),
want: wantsvcb(&dns.SVCBIPv6Hint{Hint: []net.IP{ip6}}),
name: "ipv6hint",
}, {
svcb: dnssvcb("ipv6hint", ":::1"),
want: wantsvcb(nil),
name: "ipv6hint_invalid",
}, {
svcb: dnssvcb("mandatory", "alpn"),
want: wantsvcb(&dns.SVCBMandatory{Code: []dns.SVCBKey{dns.SVCB_ALPN}}),
name: "mandatory",
}, {
svcb: dnssvcb("mandatory", "alpnn"),
want: wantsvcb(nil),
name: "mandatory_invalid",
}, {
svcb: dnssvcb("no-default-alpn", ""),
want: wantsvcb(&dns.SVCBNoDefaultAlpn{}),
name: "no-default-alpn",
}, {
svcb: dnssvcb("port", "8080"),
want: wantsvcb(&dns.SVCBPort{Port: 8080}),
name: "port",
}, {
svcb: dnssvcb("port", "1005008080"),
want: wantsvcb(nil),
name: "port",
}}
for _, tc := range testCases {
t.Run("https", func(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
want := &dns.HTTPS{SVCB: *tc.want}
want.Hdr.Rrtype = dns.TypeHTTPS
got := s.genAnswerHTTPS(req, tc.svcb)
assert.Equal(t, want, got)
})
})
t.Run("svcb", func(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
got := s.genAnswerSVCB(req, tc.svcb)
assert.Equal(t, tc.want, got)
})
})
}
}

View File

@@ -59,10 +59,10 @@ func (s *session) deserialize(data []byte) bool {
// Auth - global object // Auth - global object
type Auth struct { type Auth struct {
db *bbolt.DB db *bbolt.DB
sessions map[string]*session sessions map[string]*session // session name -> session data
users []User
lock sync.Mutex lock sync.Mutex
sessionTTL uint32 users []User
sessionTTL uint32 // in seconds
} }
// User object // User object
@@ -223,35 +223,24 @@ func (a *Auth) removeSession(sess []byte) {
log.Debug("Auth: removed session from DB") log.Debug("Auth: removed session from DB")
} }
// checkSessionResult is the result of checking a session. // CheckSession - check if session is valid
type checkSessionResult int // Return 0 if OK; -1 if session doesn't exist; 1 if session has expired
func (a *Auth) CheckSession(sess string) int {
// checkSessionResult constants.
const (
checkSessionOK checkSessionResult = 0
checkSessionNotFound checkSessionResult = -1
checkSessionExpired checkSessionResult = 1
)
// checkSession checks if the session is valid.
func (a *Auth) checkSession(sess string) (res checkSessionResult) {
now := uint32(time.Now().UTC().Unix()) now := uint32(time.Now().UTC().Unix())
update := false update := false
a.lock.Lock() a.lock.Lock()
defer a.lock.Unlock()
s, ok := a.sessions[sess] s, ok := a.sessions[sess]
if !ok { if !ok {
return checkSessionNotFound a.lock.Unlock()
return -1
} }
if s.expire <= now { if s.expire <= now {
delete(a.sessions, sess) delete(a.sessions, sess)
key, _ := hex.DecodeString(sess) key, _ := hex.DecodeString(sess)
a.removeSession(key) a.removeSession(key)
a.lock.Unlock()
return checkSessionExpired return 1
} }
newExpire := now + a.sessionTTL newExpire := now + a.sessionTTL
@@ -261,6 +250,8 @@ func (a *Auth) checkSession(sess string) (res checkSessionResult) {
s.expire = newExpire s.expire = newExpire
} }
a.lock.Unlock()
if update { if update {
key, _ := hex.DecodeString(sess) key, _ := hex.DecodeString(sess)
if a.storeSession(key, s) { if a.storeSession(key, s) {
@@ -268,7 +259,7 @@ func (a *Auth) checkSession(sess string) (res checkSessionResult) {
} }
} }
return checkSessionOK return 0
} }
// RemoveSession - remove session // RemoveSession - remove session
@@ -401,8 +392,8 @@ func optionalAuthThird(w http.ResponseWriter, r *http.Request) (authFirst bool)
ok = true ok = true
} else if err == nil { } else if err == nil {
r := Context.auth.checkSession(cookie.Value) r := Context.auth.CheckSession(cookie.Value)
if r == checkSessionOK { if r == 0 {
ok = true ok = true
} else if r < 0 { } else if r < 0 {
log.Debug("Auth: invalid cookie value: %s", cookie) log.Debug("Auth: invalid cookie value: %s", cookie)
@@ -443,13 +434,12 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
authRequired := Context.auth != nil && Context.auth.AuthRequired() authRequired := Context.auth != nil && Context.auth.AuthRequired()
cookie, err := r.Cookie(sessionCookieName) cookie, err := r.Cookie(sessionCookieName)
if authRequired && err == nil { if authRequired && err == nil {
r := Context.auth.checkSession(cookie.Value) r := Context.auth.CheckSession(cookie.Value)
if r == checkSessionOK { if r == 0 {
w.Header().Set("Location", "/") w.Header().Set("Location", "/")
w.WriteHeader(http.StatusFound) w.WriteHeader(http.StatusFound)
return return
} else if r == checkSessionNotFound { } else if r < 0 {
log.Debug("Auth: invalid cookie value: %s", cookie) log.Debug("Auth: invalid cookie value: %s", cookie)
} }
} }
@@ -513,34 +503,32 @@ func (a *Auth) UserFind(login, password string) User {
return User{} return User{}
} }
// getCurrentUser returns the current user. It returns an empty User if the // GetCurrentUser - get the current user
// user is not found. func (a *Auth) GetCurrentUser(r *http.Request) User {
func (a *Auth) getCurrentUser(r *http.Request) User {
cookie, err := r.Cookie(sessionCookieName) cookie, err := r.Cookie(sessionCookieName)
if err != nil { if err != nil {
// There's no Cookie, check Basic authentication. // there's no Cookie, check Basic authentication
user, pass, ok := r.BasicAuth() user, pass, ok := r.BasicAuth()
if ok { if ok {
return Context.auth.UserFind(user, pass) u := Context.auth.UserFind(user, pass)
return u
} }
return User{} return User{}
} }
a.lock.Lock() a.lock.Lock()
defer a.lock.Unlock()
s, ok := a.sessions[cookie.Value] s, ok := a.sessions[cookie.Value]
if !ok { if !ok {
a.lock.Unlock()
return User{} return User{}
} }
for _, u := range a.users { for _, u := range a.users {
if u.Name == s.userName { if u.Name == s.userName {
a.lock.Unlock()
return u return u
} }
} }
a.lock.Unlock()
return User{} return User{}
} }

View File

@@ -38,7 +38,7 @@ func TestAuth(t *testing.T) {
user := User{Name: "name"} user := User{Name: "name"}
a.UserAdd(&user, "password") a.UserAdd(&user, "password")
assert.Equal(t, checkSessionNotFound, a.checkSession("notfound")) assert.True(t, a.CheckSession("notfound") == -1)
a.RemoveSession("notfound") a.RemoveSession("notfound")
sess, err := getSession(&users[0]) sess, err := getSession(&users[0])
@@ -49,13 +49,13 @@ func TestAuth(t *testing.T) {
// check expiration // check expiration
s.expire = uint32(now) s.expire = uint32(now)
a.addSession(sess, &s) a.addSession(sess, &s)
assert.Equal(t, checkSessionExpired, a.checkSession(sessStr)) assert.True(t, a.CheckSession(sessStr) == 1)
// add session with TTL = 2 sec // add session with TTL = 2 sec
s = session{} s = session{}
s.expire = uint32(time.Now().UTC().Unix() + 2) s.expire = uint32(time.Now().UTC().Unix() + 2)
a.addSession(sess, &s) a.addSession(sess, &s)
assert.Equal(t, checkSessionOK, a.checkSession(sessStr)) assert.True(t, a.CheckSession(sessStr) == 0)
a.Close() a.Close()
@@ -63,8 +63,8 @@ func TestAuth(t *testing.T) {
a = InitAuth(fn, users, 60) a = InitAuth(fn, users, 60)
// the session is still alive // the session is still alive
assert.Equal(t, checkSessionOK, a.checkSession(sessStr)) assert.True(t, a.CheckSession(sessStr) == 0)
// reset our expiration time because checkSession() has just updated it // reset our expiration time because CheckSession() has just updated it
s.expire = uint32(time.Now().UTC().Unix() + 2) s.expire = uint32(time.Now().UTC().Unix() + 2)
a.storeSession(sess, &s) a.storeSession(sess, &s)
a.Close() a.Close()
@@ -76,7 +76,7 @@ func TestAuth(t *testing.T) {
// load and remove expired sessions // load and remove expired sessions
a = InitAuth(fn, users, 60) a = InitAuth(fn, users, 60)
assert.Equal(t, checkSessionNotFound, a.checkSession(sessStr)) assert.True(t, a.CheckSession(sessStr) == -1)
a.Close() a.Close()
os.Remove(fn) os.Remove(fn)
@@ -111,7 +111,7 @@ func TestAuthHTTP(t *testing.T) {
Context.auth = InitAuth(fn, users, 60) Context.auth = InitAuth(fn, users, 60)
handlerCalled := false handlerCalled := false
handler := func(_ http.ResponseWriter, _ *http.Request) { handler := func(w http.ResponseWriter, r *http.Request) {
handlerCalled = true handlerCalled = true
} }
handler2 := optionalAuth(handler) handler2 := optionalAuth(handler)

View File

@@ -89,7 +89,7 @@ type profileJSON struct {
func handleGetProfile(w http.ResponseWriter, r *http.Request) { func handleGetProfile(w http.ResponseWriter, r *http.Request) {
pj := profileJSON{} pj := profileJSON{}
u := Context.auth.getCurrentUser(r) u := Context.auth.GetCurrentUser(r)
pj.Name = u.Name pj.Name = u.Name
data, err := json.Marshal(pj) data, err := json.Marshal(pj)

View File

@@ -346,25 +346,10 @@ func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request
enableFilters(true) enableFilters(true)
} }
type checkHostRespRule struct {
FilterListID int64 `json:"filter_list_id"`
Text string `json:"text"`
}
type checkHostResp struct { type checkHostResp struct {
Reason string `json:"reason"` Reason string `json:"reason"`
FilterID int64 `json:"filter_id"`
// FilterID is the ID of the rule's filter list. Rule string `json:"rule"`
//
// Deprecated: Use Rules[*].FilterListID.
FilterID int64 `json:"filter_id"`
// Rule is the text of the matched rule.
//
// Deprecated: Use Rules[*].Text.
Rule string `json:"rule"`
Rules []*checkHostRespRule `json:"rules"`
// for FilteredBlockedService: // for FilteredBlockedService:
SvcName string `json:"service_name"` SvcName string `json:"service_name"`
@@ -389,23 +374,11 @@ func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) {
resp := checkHostResp{} resp := checkHostResp{}
resp.Reason = result.Reason.String() resp.Reason = result.Reason.String()
resp.FilterID = result.FilterID
resp.Rule = result.Rule
resp.SvcName = result.ServiceName resp.SvcName = result.ServiceName
resp.CanonName = result.CanonName resp.CanonName = result.CanonName
resp.IPList = result.IPList resp.IPList = result.IPList
if len(result.Rules) > 0 {
resp.FilterID = result.Rules[0].FilterListID
resp.Rule = result.Rules[0].Text
}
resp.Rules = make([]*checkHostRespRule, len(result.Rules))
for i, r := range result.Rules {
resp.Rules[i] = &checkHostRespRule{
FilterListID: r.FilterListID,
Text: r.Text,
}
}
js, err := json.Marshal(resp) js, err := json.Marshal(resp)
if err != nil { if err != nil {
httpError(w, http.StatusInternalServerError, "json encode: %s", err) httpError(w, http.StatusInternalServerError, "json encode: %s", err)

View File

@@ -58,7 +58,7 @@ type homeContext struct {
dnsServer *dnsforward.Server // DNS module dnsServer *dnsforward.Server // DNS module
rdns *RDNS // rDNS module rdns *RDNS // rDNS module
whois *Whois // WHOIS module whois *Whois // WHOIS module
dnsFilter *dnsfilter.DNSFilter // DNS filtering module dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
dhcpServer *dhcpd.Server // DHCP module dhcpServer *dhcpd.Server // DHCP module
auth *Auth // HTTP authentication module auth *Auth // HTTP authentication module
filters Filtering // DNS filtering module filters Filtering // DNS filtering module

View File

@@ -4,13 +4,11 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"io" "io"
"net"
"strings" "strings"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -87,6 +85,54 @@ var logEntryHandlers = map[string]logEntryHandler{
ent.OrigAnswer, err = base64.StdEncoding.DecodeString(v) ent.OrigAnswer, err = base64.StdEncoding.DecodeString(v)
return err return err
}, },
"IsFiltered": func(t json.Token, ent *logEntry) error {
v, ok := t.(bool)
if !ok {
return nil
}
ent.Result.IsFiltered = v
return nil
},
"Rule": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
ent.Result.Rule = v
return nil
},
"FilterID": func(t json.Token, ent *logEntry) error {
v, ok := t.(json.Number)
if !ok {
return nil
}
i, err := v.Int64()
if err != nil {
return err
}
ent.Result.FilterID = i
return nil
},
"Reason": func(t json.Token, ent *logEntry) error {
v, ok := t.(json.Number)
if !ok {
return nil
}
i, err := v.Int64()
if err != nil {
return err
}
ent.Result.Reason = dnsfilter.Reason(i)
return nil
},
"ServiceName": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
if !ok {
return nil
}
ent.Result.ServiceName = v
return nil
},
"Upstream": func(t json.Token, ent *logEntry) error { "Upstream": func(t json.Token, ent *logEntry) error {
v, ok := t.(string) v, ok := t.(string)
if !ok { if !ok {
@@ -107,411 +153,42 @@ var logEntryHandlers = map[string]logEntryHandler{
ent.Elapsed = time.Duration(i) ent.Elapsed = time.Duration(i)
return nil return nil
}, },
} "Result": func(json.Token, *logEntry) error {
var resultHandlers = map[string]logEntryHandler{
"IsFiltered": func(t json.Token, ent *logEntry) error {
v, ok := t.(bool)
if !ok {
return nil
}
ent.Result.IsFiltered = v
return nil return nil
}, },
"Rule": func(t json.Token, ent *logEntry) error { "Question": func(t json.Token, ent *logEntry) error {
s, ok := t.(string) v, ok := t.(string)
if !ok { if !ok {
return nil return nil
} }
var qstr []byte
l := len(ent.Result.Rules) qstr, err := base64.StdEncoding.DecodeString(v)
if l == 0 {
ent.Result.Rules = []*dnsfilter.ResultRule{{}}
l++
}
ent.Result.Rules[l-1].Text = s
return nil
},
"FilterID": func(t json.Token, ent *logEntry) error {
n, ok := t.(json.Number)
if !ok {
return nil
}
i, err := n.Int64()
if err != nil { if err != nil {
return err return err
} }
q := new(dns.Msg)
l := len(ent.Result.Rules) err = q.Unpack(qstr)
if l == 0 {
ent.Result.Rules = []*dnsfilter.ResultRule{{}}
l++
}
ent.Result.Rules[l-1].FilterListID = i
return nil
},
"Reason": func(t json.Token, ent *logEntry) error {
v, ok := t.(json.Number)
if !ok {
return nil
}
i, err := v.Int64()
if err != nil { if err != nil {
return err return err
} }
ent.Result.Reason = dnsfilter.Reason(i) ent.QHost = q.Question[0].Name
if len(ent.QHost) == 0 {
return nil // nil???
}
ent.QHost = ent.QHost[:len(ent.QHost)-1]
ent.QType = dns.TypeToString[q.Question[0].Qtype]
ent.QClass = dns.ClassToString[q.Question[0].Qclass]
return nil return nil
}, },
"ServiceName": func(t json.Token, ent *logEntry) error { "Time": func(t json.Token, ent *logEntry) error {
s, ok := t.(string) v, ok := t.(string)
if !ok { if !ok {
return nil return nil
} }
var err error
ent.Result.ServiceName = s ent.Time, err = time.Parse(time.RFC3339, v)
return err
return nil
}, },
"CanonName": func(t json.Token, ent *logEntry) error {
s, ok := t.(string)
if !ok {
return nil
}
ent.Result.CanonName = s
return nil
},
}
func decodeResultRuleKey(key string, i int, dec *json.Decoder, ent *logEntry) {
switch key {
case "FilterListID":
vToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultRuleKey %s err: %s", key, err)
}
return
}
if len(ent.Result.Rules) < i+1 {
ent.Result.Rules = append(ent.Result.Rules, &dnsfilter.ResultRule{})
}
if n, ok := vToken.(json.Number); ok {
ent.Result.Rules[i].FilterListID, _ = n.Int64()
}
case "IP":
vToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultRuleKey %s err: %s", key, err)
}
return
}
if len(ent.Result.Rules) < i+1 {
ent.Result.Rules = append(ent.Result.Rules, &dnsfilter.ResultRule{})
}
if ipStr, ok := vToken.(string); ok {
ent.Result.Rules[i].IP = net.ParseIP(ipStr)
}
case "Text":
vToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultRuleKey %s err: %s", key, err)
}
return
}
if len(ent.Result.Rules) < i+1 {
ent.Result.Rules = append(ent.Result.Rules, &dnsfilter.ResultRule{})
}
if s, ok := vToken.(string); ok {
ent.Result.Rules[i].Text = s
}
default:
// Go on.
}
}
func decodeResultRules(dec *json.Decoder, ent *logEntry) {
for {
delimToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultRules err: %s", err)
}
return
}
if d, ok := delimToken.(json.Delim); ok {
if d != '[' {
log.Debug("decodeResultRules: unexpected delim %q", d)
}
} else {
return
}
i := 0
for {
keyToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultRules err: %s", err)
}
return
}
if d, ok := keyToken.(json.Delim); ok {
if d == '}' {
i++
} else if d == ']' {
return
}
continue
}
key, ok := keyToken.(string)
if !ok {
log.Debug("decodeResultRules: keyToken is %T (%[1]v) and not string", keyToken)
return
}
decodeResultRuleKey(key, i, dec, ent)
}
}
}
func decodeResultReverseHosts(dec *json.Decoder, ent *logEntry) {
for {
itemToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultReverseHosts err: %s", err)
}
return
}
switch v := itemToken.(type) {
case json.Delim:
if v == '[' {
continue
} else if v == ']' {
return
}
log.Debug("decodeResultReverseHosts: unexpected delim %q", v)
return
case string:
ent.Result.ReverseHosts = append(ent.Result.ReverseHosts, v)
default:
continue
}
}
}
func decodeResultIPList(dec *json.Decoder, ent *logEntry) {
for {
itemToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultIPList err: %s", err)
}
return
}
switch v := itemToken.(type) {
case json.Delim:
if v == '[' {
continue
} else if v == ']' {
return
}
log.Debug("decodeResultIPList: unexpected delim %q", v)
return
case string:
ip := net.ParseIP(v)
if ip != nil {
ent.Result.IPList = append(ent.Result.IPList, ip)
}
default:
continue
}
}
}
func decodeResultDNSRewriteResult(dec *json.Decoder, ent *logEntry) {
for {
keyToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultDNSRewriteResult err: %s", err)
}
return
}
if d, ok := keyToken.(json.Delim); ok {
if d == '}' {
return
}
continue
}
key, ok := keyToken.(string)
if !ok {
log.Debug("decodeResultDNSRewriteResult: keyToken is %T (%[1]v) and not string", keyToken)
return
}
// TODO(a.garipov): Refactor this into a separate
// function à la decodeResultRuleKey if we keep this
// code for a longer time than planned.
switch key {
case "RCode":
vToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultDNSRewriteResult err: %s", err)
}
return
}
if ent.Result.DNSRewriteResult == nil {
ent.Result.DNSRewriteResult = &dnsfilter.DNSRewriteResult{}
}
if n, ok := vToken.(json.Number); ok {
rcode64, _ := n.Int64()
ent.Result.DNSRewriteResult.RCode = rules.RCode(rcode64)
}
continue
case "Response":
if ent.Result.DNSRewriteResult == nil {
ent.Result.DNSRewriteResult = &dnsfilter.DNSRewriteResult{}
}
if ent.Result.DNSRewriteResult.Response == nil {
ent.Result.DNSRewriteResult.Response = dnsfilter.DNSRewriteResultResponse{}
}
// TODO(a.garipov): I give up. This whole file
// is a mess. Luckily, we can assume that this
// field is relatively rare and just use the
// normal decoding and correct the values.
err = dec.Decode(&ent.Result.DNSRewriteResult.Response)
if err != nil {
log.Debug("decodeResultDNSRewriteResult response err: %s", err)
}
for rrType, rrValues := range ent.Result.DNSRewriteResult.Response {
switch rrType {
case dns.TypeA, dns.TypeAAAA:
for i, v := range rrValues {
s, _ := v.(string)
rrValues[i] = net.ParseIP(s)
}
default:
// Go on.
}
}
continue
default:
// Go on.
}
}
}
func decodeResult(dec *json.Decoder, ent *logEntry) {
for {
keyToken, err := dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResult err: %s", err)
}
return
}
if d, ok := keyToken.(json.Delim); ok {
if d == '}' {
return
}
continue
}
key, ok := keyToken.(string)
if !ok {
log.Debug("decodeResult: keyToken is %T (%[1]v) and not string", keyToken)
return
}
switch key {
case "ReverseHosts":
decodeResultReverseHosts(dec, ent)
continue
case "IPList":
decodeResultIPList(dec, ent)
continue
case "Rules":
decodeResultRules(dec, ent)
continue
case "DNSRewriteResult":
decodeResultDNSRewriteResult(dec, ent)
continue
default:
// Go on.
}
handler, ok := resultHandlers[key]
if !ok {
continue
}
val, err := dec.Token()
if err != nil {
return
}
if err = handler(val, ent); err != nil {
log.Debug("decodeResult handler err: %s", err)
return
}
}
} }
func decodeLogEntry(ent *logEntry, str string) { func decodeLogEntry(ent *logEntry, str string) {
@@ -523,27 +200,18 @@ func decodeLogEntry(ent *logEntry, str string) {
if err != io.EOF { if err != io.EOF {
log.Debug("decodeLogEntry err: %s", err) log.Debug("decodeLogEntry err: %s", err)
} }
return return
} }
if _, ok := keyToken.(json.Delim); ok { if _, ok := keyToken.(json.Delim); ok {
continue continue
} }
key, ok := keyToken.(string) key, ok := keyToken.(string)
if !ok { if !ok {
log.Debug("decodeLogEntry: keyToken is %T (%[1]v) and not string", keyToken) log.Debug("decodeLogEntry: keyToken is %T and not string", keyToken)
return return
} }
if key == "Result" {
decodeResult(dec, ent)
continue
}
handler, ok := logEntryHandlers[key] handler, ok := logEntryHandlers[key]
if !ok { if !ok {
continue continue
@@ -555,8 +223,7 @@ func decodeLogEntry(ent *logEntry, str string) {
} }
if err = handler(val, ent); err != nil { if err = handler(val, ent); err != nil {
log.Debug("decodeLogEntry handler err: %s", err) log.Debug("decodeLogEntry err: %s", err)
return return
} }
} }

View File

@@ -2,181 +2,107 @@ package querylog
import ( import (
"bytes" "bytes"
"encoding/base64"
"net"
"strings" "strings"
"testing" "testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
"github.com/AdguardTeam/AdGuardHome/internal/testutil" "github.com/AdguardTeam/AdGuardHome/internal/testutil"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestDecodeLogEntry(t *testing.T) { func TestDecode_decodeQueryLog(t *testing.T) {
logOutput := &bytes.Buffer{} logOutput := &bytes.Buffer{}
testutil.ReplaceLogWriter(t, logOutput) testutil.ReplaceLogWriter(t, logOutput)
testutil.ReplaceLogLevel(t, log.DEBUG) testutil.ReplaceLogLevel(t, log.DEBUG)
t.Run("success", func(t *testing.T) {
const ansStr = `Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==`
const data = `{"IP":"127.0.0.1",` +
`"T":"2020-11-25T18:55:56.519796+03:00",` +
`"QH":"an.yandex.ru",` +
`"QT":"A",` +
`"QC":"IN",` +
`"CP":"",` +
`"Answer":"` + ansStr + `",` +
`"Result":{` +
`"IsFiltered":true,` +
`"Reason":3,` +
`"ReverseHosts":["example.net"],` +
`"IPList":["127.0.0.2"],` +
`"Rules":[{"FilterListID":42,"Text":"||an.yandex.ru","IP":"127.0.0.2"},` +
`{"FilterListID":43,"Text":"||an2.yandex.ru","IP":"127.0.0.3"}],` +
`"CanonName":"example.com",` +
`"ServiceName":"example.org",` +
`"DNSRewriteResult":{"RCode":0,"Response":{"1":["127.0.0.2"]}}},` +
`"Elapsed":837429}`
ans, err := base64.StdEncoding.DecodeString(ansStr)
assert.Nil(t, err)
want := &logEntry{
IP: "127.0.0.1",
Time: time.Date(2020, 11, 25, 15, 55, 56, 519796000, time.UTC),
QHost: "an.yandex.ru",
QType: "A",
QClass: "IN",
ClientProto: "",
Answer: ans,
Result: dnsfilter.Result{
IsFiltered: true,
Reason: dnsfilter.FilteredBlockList,
ReverseHosts: []string{"example.net"},
IPList: []net.IP{net.IPv4(127, 0, 0, 2)},
Rules: []*dnsfilter.ResultRule{{
FilterListID: 42,
Text: "||an.yandex.ru",
IP: net.IPv4(127, 0, 0, 2),
}, {
FilterListID: 43,
Text: "||an2.yandex.ru",
IP: net.IPv4(127, 0, 0, 3),
}},
CanonName: "example.com",
ServiceName: "example.org",
DNSRewriteResult: &dnsfilter.DNSRewriteResult{
RCode: dns.RcodeSuccess,
Response: dnsfilter.DNSRewriteResultResponse{
dns.TypeA: []rules.RRValue{net.IPv4(127, 0, 0, 2)},
},
},
},
Elapsed: 837429,
}
got := &logEntry{}
decodeLogEntry(got, data)
s := logOutput.String()
assert.Equal(t, "", s)
// Correct for time zones.
got.Time = got.Time.UTC()
assert.Equal(t, want, got)
})
testCases := []struct { testCases := []struct {
name string name string
log string log string
want string want string
}{{ }{{
name: "all_right_old_rule", name: "back_compatibility_all_right",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1,"ReverseHosts":["example.com"],"IPList":["127.0.0.1"]},"Elapsed":837429}`, log: `{"Question":"ULgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
want: "", want: "default",
}, { }, {
name: "bad_filter_id_old_rule", name: "back_compatibility_bad_msg",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"FilterID":1.5},"Elapsed":837429}`, log: `{"Question":"","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
want: "decodeResult handler err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n", want: "decodeLogEntry err: dns: overflow unpacking uint16\n",
}, {
name: "back_compatibility_bad_decoding",
log: `{"Question":"LgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
want: "decodeLogEntry err: illegal base64 data at input byte 48\n",
}, {
name: "modern_all_right",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "default",
}, {
name: "bad_filter_id",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1.5},"Elapsed":837429}`,
want: "decodeLogEntry err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n",
}, { }, {
name: "bad_is_filtered", name: "bad_is_filtered",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":trooe,"Reason":3},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":trooe,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "decodeLogEntry err: invalid character 'o' in literal true (expecting 'u')\n", want: "default",
}, { }, {
name: "bad_elapsed", name: "bad_elapsed",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":-1}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":-1}`,
want: "", want: "default",
}, { }, {
name: "bad_ip", name: "bad_ip",
log: `{"IP":127001,"T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, log: `{"IP":127001,"T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "", want: "default",
}, { }, {
name: "bad_time", name: "bad_time",
log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "decodeLogEntry handler err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n", want: "decodeLogEntry err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n",
}, { }, {
name: "bad_host", name: "bad_host",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "", want: "default",
}, { }, {
name: "bad_type", name: "bad_type",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":true,"QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":true,"QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "", want: "default",
}, { }, {
name: "bad_class", name: "bad_class",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":false,"CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":false,"CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "", want: "default",
}, { }, {
name: "bad_client_proto", name: "bad_client_proto",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":8,"Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":8,"Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "", want: "default",
}, { }, {
name: "very_bad_client_proto", name: "very_bad_client_proto",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"dog","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"dog","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "decodeLogEntry handler err: invalid client proto: \"dog\"\n", want: "decodeLogEntry err: invalid client proto: \"dog\"\n",
}, { }, {
name: "bad_answer", name: "bad_answer",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":0.9,"Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":0.9,"Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "", want: "default",
}, { }, {
name: "very_bad_answer", name: "very_bad_answer",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "decodeLogEntry handler err: illegal base64 data at input byte 61\n", want: "decodeLogEntry err: illegal base64 data at input byte 61\n",
}, { }, {
name: "bad_rule", name: "bad_rule",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":false},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":false,"FilterID":1},"Elapsed":837429}`,
want: "", want: "default",
}, { }, {
name: "bad_reason", name: "bad_reason",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":true},"Elapsed":837429}`, log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":true,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
want: "", want: "default",
}, {
name: "bad_reverse_hosts",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"ReverseHosts":[{}]},"Elapsed":837429}`,
want: "decodeResultReverseHosts: unexpected delim \"{\"\n",
}, {
name: "bad_ip_list",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"ReverseHosts":["example.net"],"IPList":[{}]},"Elapsed":837429}`,
want: "decodeResultIPList: unexpected delim \"{\"\n",
}} }}
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
_, err := logOutput.Write([]byte("default"))
assert.Nil(t, err)
l := &logEntry{} l := &logEntry{}
decodeLogEntry(l, tc.log) decodeLogEntry(l, tc.log)
s := logOutput.String() assert.True(t, strings.HasSuffix(logOutput.String(), tc.want), logOutput.String())
if tc.want == "" {
assert.Equal(t, "", s)
} else {
assert.True(t, strings.HasSuffix(s, tc.want),
"got %q", s)
}
logOutput.Reset() logOutput.Reset()
}) })

View File

@@ -6,13 +6,10 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
// TODO(a.garipov): Use a proper structured approach here.
// Get Client IP address // Get Client IP address
func (l *queryLog) getClientIP(clientIP string) string { func (l *queryLog) getClientIP(clientIP string) string {
if l.conf.AnonymizeClientIP { if l.conf.AnonymizeClientIP {
@@ -32,12 +29,10 @@ func (l *queryLog) getClientIP(clientIP string) string {
return clientIP return clientIP
} }
// jobject is a JSON object alias. // entriesToJSON - converts log entries to JSON
type jobject = map[string]interface{} func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[string]interface{} {
// init the response object
// entriesToJSON converts query log entries to JSON. data := []map[string]interface{}{}
func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) (res jobject) {
data := []jobject{}
// the elements order is already reversed (from newer to older) // the elements order is already reversed (from newer to older)
for i := 0; i < len(entries); i++ { for i := 0; i < len(entries); i++ {
@@ -46,18 +41,17 @@ func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) (res job
data = append(data, jsonEntry) data = append(data, jsonEntry)
} }
res = jobject{ result := map[string]interface{}{}
"data": data, result["oldest"] = ""
"oldest": "",
}
if !oldest.IsZero() { if !oldest.IsZero() {
res["oldest"] = oldest.Format(time.RFC3339Nano) result["oldest"] = oldest.Format(time.RFC3339Nano)
} }
result["data"] = data
return res return result
} }
func (l *queryLog) logEntryToJSONEntry(entry *logEntry) (jsonEntry jobject) { func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
var msg *dns.Msg var msg *dns.Msg
if len(entry.Answer) > 0 { if len(entry.Answer) > 0 {
@@ -68,18 +62,17 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) (jsonEntry jobject) {
} }
} }
jsonEntry = jobject{ jsonEntry := map[string]interface{}{
"reason": entry.Result.Reason.String(), "reason": entry.Result.Reason.String(),
"elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64), "elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64),
"time": entry.Time.Format(time.RFC3339Nano), "time": entry.Time.Format(time.RFC3339Nano),
"client": l.getClientIP(entry.IP), "client": l.getClientIP(entry.IP),
"client_proto": entry.ClientProto, "client_proto": entry.ClientProto,
"upstream": entry.Upstream, }
"question": jobject{ jsonEntry["question"] = map[string]interface{}{
"host": entry.QHost, "host": entry.QHost,
"type": entry.QType, "type": entry.QType,
"class": entry.QClass, "class": entry.QClass,
},
} }
if msg != nil { if msg != nil {
@@ -90,15 +83,12 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) (jsonEntry jobject) {
if opt != nil { if opt != nil {
dnssecOk = opt.Do() dnssecOk = opt.Do()
} }
jsonEntry["answer_dnssec"] = dnssecOk jsonEntry["answer_dnssec"] = dnssecOk
} }
jsonEntry["rules"] = resultRulesToJSONRules(entry.Result.Rules) if len(entry.Result.Rule) > 0 {
jsonEntry["rule"] = entry.Result.Rule
if len(entry.Result.Rules) > 0 && len(entry.Result.Rules[0].Text) > 0 { jsonEntry["filterId"] = entry.Result.FilterID
jsonEntry["rule"] = entry.Result.Rules[0].Text
jsonEntry["filterId"] = entry.Result.Rules[0].FilterListID
} }
if len(entry.Result.ServiceName) != 0 { if len(entry.Result.ServiceName) != 0 {
@@ -123,30 +113,20 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) (jsonEntry jobject) {
} }
} }
jsonEntry["upstream"] = entry.Upstream
return jsonEntry return jsonEntry
} }
func resultRulesToJSONRules(rules []*dnsfilter.ResultRule) (jsonRules []jobject) { func answerToMap(a *dns.Msg) []map[string]interface{} {
jsonRules = make([]jobject, len(rules))
for i, r := range rules {
jsonRules[i] = jobject{
"filter_list_id": r.FilterListID,
"text": r.Text,
}
}
return jsonRules
}
func answerToMap(a *dns.Msg) (answers []jobject) {
if a == nil || len(a.Answer) == 0 { if a == nil || len(a.Answer) == 0 {
return nil return nil
} }
answers = []jobject{} answers := []map[string]interface{}{}
for _, k := range a.Answer { for _, k := range a.Answer {
header := k.Header() header := k.Header()
answer := jobject{ answer := map[string]interface{}{
"type": dns.TypeToString[header.Rrtype], "type": dns.TypeToString[header.Rrtype],
"ttl": header.Ttl, "ttl": header.Ttl,
} }

View File

@@ -236,12 +236,10 @@ func addEntry(l *queryLog, host, answerStr, client string) {
a.Answer = append(a.Answer, answer) a.Answer = append(a.Answer, answer)
res := dnsfilter.Result{ res := dnsfilter.Result{
IsFiltered: true, IsFiltered: true,
Rule: "SomeRule",
Reason: dnsfilter.ReasonRewrite, Reason: dnsfilter.ReasonRewrite,
ServiceName: "SomeService", ServiceName: "SomeService",
Rules: []*dnsfilter.ResultRule{{ FilterID: 1,
FilterListID: 1,
Text: "SomeRule",
}},
} }
params := AddParams{ params := AddParams{
Question: &q, Question: &q,

View File

@@ -115,14 +115,14 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool {
case filteringStatusFiltered: case filteringStatusFiltered:
return res.IsFiltered || return res.IsFiltered ||
res.Reason.In( res.Reason.In(
dnsfilter.NotFilteredAllowList, dnsfilter.NotFilteredWhiteList,
dnsfilter.ReasonRewrite, dnsfilter.ReasonRewrite,
dnsfilter.RewriteAutoHosts, dnsfilter.RewriteEtcHosts,
) )
case filteringStatusBlocked: case filteringStatusBlocked:
return res.IsFiltered && return res.IsFiltered &&
res.Reason.In(dnsfilter.FilteredBlockList, dnsfilter.FilteredBlockedService) res.Reason.In(dnsfilter.FilteredBlackList, dnsfilter.FilteredBlockedService)
case filteringStatusBlockedService: case filteringStatusBlockedService:
return res.IsFiltered && res.Reason == dnsfilter.FilteredBlockedService return res.IsFiltered && res.Reason == dnsfilter.FilteredBlockedService
@@ -134,19 +134,19 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool {
return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeBrowsing return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeBrowsing
case filteringStatusWhitelisted: case filteringStatusWhitelisted:
return res.Reason == dnsfilter.NotFilteredAllowList return res.Reason == dnsfilter.NotFilteredWhiteList
case filteringStatusRewritten: case filteringStatusRewritten:
return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteAutoHosts) return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteEtcHosts)
case filteringStatusSafeSearch: case filteringStatusSafeSearch:
return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeSearch return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeSearch
case filteringStatusProcessed: case filteringStatusProcessed:
return !res.Reason.In( return !res.Reason.In(
dnsfilter.FilteredBlockList, dnsfilter.FilteredBlackList,
dnsfilter.FilteredBlockedService, dnsfilter.FilteredBlockedService,
dnsfilter.NotFilteredAllowList, dnsfilter.NotFilteredWhiteList,
) )
default: default:

View File

@@ -18,7 +18,7 @@ require (
golang.org/x/mod v0.4.0 // indirect golang.org/x/mod v0.4.0 // indirect
golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7 golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
honnef.co/go/tools v0.1.0 honnef.co/go/tools v0.0.1-2020.1.6
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7
) )

View File

@@ -123,7 +123,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
@@ -159,8 +158,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc= honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc=
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
honnef.co/go/tools v0.1.0 h1:AWNL1W1i7f0wNZ8VwOKNJ0sliKvOF/adn0EHenfUh+c=
honnef.co/go/tools v0.1.0/go.mod h1:XtegFAyX/PfluP4921rXU5IkjkqBCDnUq4W8VCIoKvM=
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 h1:5ZmJGYyuTlhdlIpRxSFhdJqkXQweXETFCEaLhRAX3e8= mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 h1:5ZmJGYyuTlhdlIpRxSFhdJqkXQweXETFCEaLhRAX3e8=
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0= mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0=
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY= mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY=

View File

@@ -1,67 +1,5 @@
# AdGuard Home API Change Log # AdGuard Home API Change Log
<!-- TODO(a.garipov): Reformat in accordance with the KeepAChangelog spec. -->
## v0.105: API changes
### New `"reason"` in `GET /filtering/check_host` and `GET /querylog`
* The new `DNSRewriteRule` reason is added to `GET /filtering/check_host` and
`GET /querylog`.
* Also, the reason which was incorrectly documented as `"ReasonRewrite"` is now
correctly documented as `"Rewrite"`, and the previously undocumented
`"RewriteEtcHosts"` is now documented as well.
### Multiple matched rules in `GET /filtering/check_host` and `GET /querylog`
* The properties `rule` and `filter_id` are now deprecated. API users should
inspect the newly-added `rules` object array instead. For most rules, it's
either empty or contains one object, which contains the same things as the old
two properties did, but under more correct names:
```js
{
// …
// Deprecated.
"rule": "||example.com^",
// Deprecated.
"filter_id": 42,
// Newly-added.
"rules": [{
"text": "||example.com^",
"filter_list_id": 42
}]
}
```
For `$dnsrewrite` rules, they contain all rules that contributed to the
result. For example, if you have the following filtering rules:
```
||example.com^$dnsrewrite=127.0.0.1
||example.com^$dnsrewrite=127.0.0.2
```
The `"rules"` will be something like:
```js
{
// …
"rules": [{
"text": "||example.com^$dnsrewrite=127.0.0.1",
"filter_list_id": 0
}, {
"text": "||example.com^$dnsrewrite=127.0.0.2",
"filter_list_id": 0
}]
}
```
The old fields will be removed in v0.106.0.
## v0.103: API changes ## v0.103: API changes
### API: replace settings in GET /control/dns_info & POST /control/dns_config ### API: replace settings in GET /control/dns_info & POST /control/dns_config

View File

@@ -2,9 +2,9 @@
'info': 'info':
'title': 'AdGuard Home' 'title': 'AdGuard Home'
'description': > 'description': >
AdGuard Home REST-ish API. Our admin web interface is built on top of this AdGuard Home REST API. Our admin web interface is built on top of this REST
REST-ish API. API.
'version': '0.105' 'version': '0.104'
'contact': 'contact':
'name': 'AdGuard Home' 'name': 'AdGuard Home'
'url': 'https://github.com/AdguardTeam/AdGuardHome' 'url': 'https://github.com/AdguardTeam/AdGuardHome'
@@ -523,7 +523,7 @@
Reload filtering rules from URLs. This might be needed if new URL was Reload filtering rules from URLs. This might be needed if new URL was
just added and you dont want to wait for automatic refresh to kick in. just added and you dont want to wait for automatic refresh to kick in.
This API request is ratelimited, so you can call it freely as often as This API request is ratelimited, so you can call it freely as often as
you like, it wont create unnecessary burden on servers that host the you like, it wont create unneccessary burden on servers that host the
URL. This should work as intended, a `force` parameter is offered as URL. This should work as intended, a `force` parameter is offered as
last-resort attempt to make filter lists fresh. If you ever find last-resort attempt to make filter lists fresh. If you ever find
yourself using `force` to make something work that otherwise wont, this yourself using `force` to make something work that otherwise wont, this
@@ -1246,7 +1246,7 @@
'properties': 'properties':
'reason': 'reason':
'type': 'string' 'type': 'string'
'description': 'Request filtering status.' 'description': 'DNS filter status'
'enum': 'enum':
- 'NotFilteredNotFound' - 'NotFilteredNotFound'
- 'NotFilteredWhiteList' - 'NotFilteredWhiteList'
@@ -1257,41 +1257,24 @@
- 'FilteredInvalid' - 'FilteredInvalid'
- 'FilteredSafeSearch' - 'FilteredSafeSearch'
- 'FilteredBlockedService' - 'FilteredBlockedService'
- 'Rewrite' - 'ReasonRewrite'
- 'RewriteEtcHosts'
- 'DNSRewriteRule'
'filter_id': 'filter_id':
'deprecated': true
'description': >
In case if there's a rule applied to this DNS request, this is ID of
the filter list that the rule belongs to.
Deprecated: use `rules[*].filter_list_id` instead.
'type': 'integer' 'type': 'integer'
'rule': 'rule':
'deprecated': true
'type': 'string' 'type': 'string'
'example': '||example.org^' 'example': '||example.org^'
'description': > 'description': 'Filtering rule applied to the request (if any)'
Filtering rule applied to the request (if any).
Deprecated: use `rules[*].text` instead.
'rules':
'description': 'Applied rules.'
'type': 'array'
'items':
'$ref': '#/components/schemas/ResultRule'
'service_name': 'service_name':
'type': 'string' 'type': 'string'
'description': 'Set if reason=FilteredBlockedService' 'description': 'Set if reason=FilteredBlockedService'
'cname': 'cname':
'type': 'string' 'type': 'string'
'description': 'Set if reason=Rewrite' 'description': 'Set if reason=ReasonRewrite'
'ip_addrs': 'ip_addrs':
'type': 'array' 'type': 'array'
'items': 'items':
'type': 'string' 'type': 'string'
'description': 'Set if reason=Rewrite' 'description': 'Set if reason=ReasonRewrite'
'FilterRefreshResponse': 'FilterRefreshResponse':
'type': 'object' 'type': 'object'
'description': '/filtering/refresh response data' 'description': '/filtering/refresh response data'
@@ -1627,30 +1610,18 @@
'question': 'question':
'$ref': '#/components/schemas/DnsQuestion' '$ref': '#/components/schemas/DnsQuestion'
'filterId': 'filterId':
'deprecated': true
'type': 'integer' 'type': 'integer'
'example': 123123 'example': 123123
'description': > 'description': >
In case if there's a rule applied to this DNS request, this is ID of In case if there's a rule applied to this DNS request, this is ID of
the filter list that the rule belongs to. the filter that rule belongs to.
Deprecated: use `rules[*].filter_list_id` instead.
'rule': 'rule':
'deprecated': true
'type': 'string' 'type': 'string'
'example': '||example.org^' 'example': '||example.org^'
'description': > 'description': 'Filtering rule applied to the request (if any)'
Filtering rule applied to the request (if any).
Deprecated: use `rules[*].text` instead.
'rules':
'description': 'Applied rules.'
'type': 'array'
'items':
'$ref': '#/components/schemas/ResultRule'
'reason': 'reason':
'type': 'string' 'type': 'string'
'description': 'Request filtering status.' 'description': 'DNS filter status'
'enum': 'enum':
- 'NotFilteredNotFound' - 'NotFilteredNotFound'
- 'NotFilteredWhiteList' - 'NotFilteredWhiteList'
@@ -1661,9 +1632,7 @@
- 'FilteredInvalid' - 'FilteredInvalid'
- 'FilteredSafeSearch' - 'FilteredSafeSearch'
- 'FilteredBlockedService' - 'FilteredBlockedService'
- 'Rewrite' - 'ReasonRewrite'
- 'RewriteEtcHosts'
- 'DNSRewriteRule'
'service_name': 'service_name':
'type': 'string' 'type': 'string'
'description': 'Set if reason=FilteredBlockedService' 'description': 'Set if reason=FilteredBlockedService'
@@ -1699,22 +1668,6 @@
'anonymize_client_ip': 'anonymize_client_ip':
'type': 'boolean' 'type': 'boolean'
'description': "Anonymize clients' IP addresses" 'description': "Anonymize clients' IP addresses"
'ResultRule':
'description': 'Applied rule.'
'properties':
'filter_list_id':
'description': >
In case if there's a rule applied to this DNS request, this is ID of
the filter list that the rule belongs to.
'example': 123123
'format': 'int64'
'type': 'integer'
'text':
'description': >
The text of the filtering rule applied to the request (if any).
'example': '||example.org^'
'type': 'string'
'type': 'object'
'TlsConfig': 'TlsConfig':
'type': 'object' 'type': 'object'
'description': 'TLS configuration settings and status' 'description': 'TLS configuration settings and status'

View File

@@ -95,7 +95,7 @@ ineffassign .
unparam ./... unparam ./...
git ls-files -- '*.go' '*.md' '*.yaml' '*.yml' | xargs misspell --error misspell --error ./...
looppointer ./... looppointer ./...
@@ -112,4 +112,4 @@ exit_on_output sh -c '
{ grep -e "defer" -e "_test\.go:" -v || exit 0; } { grep -e "defer" -e "_test\.go:" -v || exit 0; }
' '
staticcheck ./... staticcheck --checks='all' ./...

View File

@@ -190,7 +190,6 @@ main() {
SCRIPT_URL="https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh" SCRIPT_URL="https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh"
URL="https://static.adguard.com/adguardhome/${CHANNEL}/${PKG_NAME}" URL="https://static.adguard.com/adguardhome/${CHANNEL}/${PKG_NAME}"
OUT_DIR=/opt OUT_DIR=/opt
AGH_DIR="${OUT_DIR}/AdGuardHome"
# Root check # Root check
if [ "$(id -u)" -eq 0 ]; then if [ "$(id -u)" -eq 0 ]; then
@@ -209,22 +208,22 @@ main() {
fi fi
fi fi
log_info "AdGuard Home will be installed to ${AGH_DIR}" log_info "AdGuard Home will be installed to ${OUT_DIR}/AdGuardHome"
[ -d "${AGH_DIR}" ] && [ -n "$(ls -1 -A -q ${AGH_DIR})" ] && error_exit "Directory ${AGH_DIR} is not empty, abort installation" [ -d "${OUT_DIR}/AdGuardHome" ] && error_exit "Directory ${OUT_DIR}/AdGuardHome already exists, abort installation"
download "${URL}" "${PKG_NAME}" || error_exit "Cannot download the package" download "${URL}" "${PKG_NAME}" || error_exit "Cannot download the package"
unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package" unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package"
# Install AdGuard Home service and run it # Install AdGuard Home service and run it
${AGH_DIR}/AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service" ${OUT_DIR}/AdGuardHome/AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service"
rm "${PKG_NAME}" rm "${PKG_NAME}"
log_info "AdGuard Home is now installed and running." log_info "AdGuard Home is now installed and running."
log_info "You can control the service status with the following commands:" log_info "You can control the service status with the following commands:"
log_info " sudo ${AGH_DIR}/AdGuardHome -s start|stop|restart|status|install|uninstall" log_info " sudo ${OUT_DIR}/AdGuardHome/AdGuardHome -s start|stop|restart|status|install|uninstall"
} }
main "$@" main "$@"

View File

@@ -2,6 +2,11 @@ const fs = require('fs');
const readline = require('readline'); const readline = require('readline');
const dnsPacket = require('dns-packet') const dnsPacket = require('dns-packet')
const decodeBase64 = (data) => {
let buff = new Buffer(data, 'base64');
return buff.toString('ascii');
}
const processLineByLine = async (source, callback) => { const processLineByLine = async (source, callback) => {
const fileStream = fs.createReadStream(source); const fileStream = fs.createReadStream(source);

View File

@@ -1,4 +1,4 @@
## Twosky integration script ## Twosky intergration script
### Usage ### Usage

View File

@@ -1,17 +0,0 @@
checks = ["all"]
initialisms = [
# See https://github.com/dominikh/go-tools/blob/master/config/config.go.
"inherit"
, "DHCP"
, "DOH"
, "DOQ"
, "DOT"
, "EDNS"
, "MX"
, "PTR"
, "QUIC"
, "SDNS"
, "SVCB"
]
dot_import_whitelist = []
http_status_code_whitelist = []