Compare commits
1 Commits
v0.105.0-b
...
1383-devic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
503974c210 |
@@ -1,13 +1,18 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
set -e;
|
||||
|
||||
set -e -f -u
|
||||
|
||||
if [ "$(git diff --cached --name-only -- '*.js')" ]
|
||||
then
|
||||
make js-lint js-test
|
||||
found=0
|
||||
git diff --cached --name-only | grep -q '.js$' && found=1
|
||||
if [ $found == 1 ]; then
|
||||
npm --prefix client run lint || exit 1
|
||||
npm run test --prefix client || exit 1
|
||||
fi
|
||||
|
||||
if [ "$(git diff --cached --name-only -- '*.go' 'go.mod')" ]
|
||||
then
|
||||
make go-lint go-test
|
||||
found=0
|
||||
git diff --cached --name-only | grep -q '.go$' && found=1
|
||||
if [ $found == 1 ]; then
|
||||
make go-lint || exit 1
|
||||
go test ./... || exit 1
|
||||
fi
|
||||
|
||||
exit 0;
|
||||
|
||||
@@ -1833,22 +1833,16 @@ Response:
|
||||
200 OK
|
||||
|
||||
{
|
||||
"reason":"FilteredBlackList",
|
||||
"rules":{
|
||||
"filter_list_id":42,
|
||||
"text":"||doubleclick.net^",
|
||||
},
|
||||
// If we have "reason":"FilteredBlockedService".
|
||||
"service_name": "...",
|
||||
// If we have "reason":"Rewrite".
|
||||
"cname": "...",
|
||||
"ip_addrs": ["1.2.3.4", ...]
|
||||
"reason":"FilteredBlackList",
|
||||
"filter_id":1,
|
||||
"rule":"||doubleclick.net^",
|
||||
"service_name": "...", // set if reason=FilteredBlockedService
|
||||
|
||||
// if reason=ReasonRewrite:
|
||||
"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
|
||||
|
||||
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -15,19 +15,15 @@ and this project adheres to
|
||||
|
||||
### Added
|
||||
|
||||
- `$dnsrewrite` modifier for filters ([#2102]).
|
||||
- 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
|
||||
- Detecting of network interface configurated to have static IP address via
|
||||
`/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
|
||||
address ([#2304]).
|
||||
- `$dnstype` modifier for filters ([#2337]).
|
||||
- HTTP API request body size limit ([#2305]).
|
||||
|
||||
[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361
|
||||
[#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102
|
||||
[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302
|
||||
[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304
|
||||
[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305
|
||||
@@ -35,9 +31,6 @@ and this project adheres to
|
||||
|
||||
### 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],
|
||||
[#2391]).
|
||||
- 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
|
||||
[#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358
|
||||
[#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391
|
||||
[#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394
|
||||
|
||||
### 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
|
||||
page ([#2293]).
|
||||
- A JSON parsing error in query log ([#2345]).
|
||||
- Incorrect detection of the IPv6 address of an interface as well as another
|
||||
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
|
||||
[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345
|
||||
[#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
|
||||
|
||||
|
||||
42
HACKING.md
42
HACKING.md
@@ -78,14 +78,6 @@ The rules are mostly sorted in the alphabetical order.
|
||||
* Prefer constants to variables where possible. Reduce global variables. Use
|
||||
[constant errors] instead of `errors.New`.
|
||||
|
||||
* Unused arguments in anonymous functions must be called `_`:
|
||||
|
||||
```go
|
||||
v.onSuccess = func(_ int, msg string) {
|
||||
// …
|
||||
}
|
||||
```
|
||||
|
||||
* Use linters.
|
||||
|
||||
* Use named returns to improve readability of function signatures.
|
||||
@@ -114,16 +106,7 @@ The rules are mostly sorted in the alphabetical order.
|
||||
```go
|
||||
// Foo implements the Fooer interface for *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
|
||||
|
||||
* Avoid bashisms and GNUisms, prefer *POSIX* features only.
|
||||
* Avoid bashisms, prefer *POSIX* features only.
|
||||
|
||||
* Prefer `'raw strings'` to `"double quoted strings"` whenever possible.
|
||||
|
||||
* 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`.
|
||||
|
||||
* 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
|
||||
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
|
||||
|
||||
* End sentences with appropriate punctuation.
|
||||
|
||||
4
Makefile
4
Makefile
@@ -35,7 +35,6 @@ GPG_KEY := devteam@adguard.com
|
||||
GPG_KEY_PASSPHRASE :=
|
||||
GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE)
|
||||
VERBOSE := -v
|
||||
REBUILD_CLIENT = 1
|
||||
|
||||
# See release target
|
||||
DIST_DIR=dist
|
||||
@@ -125,8 +124,7 @@ all: build
|
||||
init:
|
||||
git config core.hooksPath .githooks
|
||||
|
||||
build:
|
||||
test '$(REBUILD_CLIENT)' = '1' && $(MAKE) client_with_deps || exit 0
|
||||
build: client_with_deps
|
||||
$(GO) mod download
|
||||
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)"
|
||||
|
||||
@@ -255,7 +255,7 @@ curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scrip
|
||||
|
||||
* 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 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)
|
||||
* 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)
|
||||
@@ -264,7 +264,7 @@ curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scrip
|
||||
|
||||
* 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 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)
|
||||
* 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)
|
||||
|
||||
12
client/package-lock.json
generated
vendored
12
client/package-lock.json
generated
vendored
@@ -3066,12 +3066,6 @@
|
||||
"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": {
|
||||
"version": "7.0.30",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.30.tgz",
|
||||
@@ -3928,9 +3922,9 @@
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001059",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001059.tgz",
|
||||
"integrity": "sha512-oOrc+jPJWooKIA0IrNZ5sYlsXc7NP7KLhNWrSGEJhnfSzDvDJ0zd3i6HXsslExY9bbu+x0FQ5C61LcqmPt7bOQ==",
|
||||
"version": "1.0.30001165",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz",
|
||||
"integrity": "sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==",
|
||||
"dev": true
|
||||
},
|
||||
"capture-exit": {
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"form_error_ip_format": "Invalid IP format",
|
||||
"form_error_mac_format": "Invalid MAC 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_negative": "Must be equal to 0 or greater",
|
||||
"range_end_error": "Must be greater than range start",
|
||||
@@ -331,7 +332,7 @@
|
||||
"encryption_config_saved": "Encryption config saved",
|
||||
"encryption_server": "Server 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_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||
"encryption_https": "HTTPS port",
|
||||
@@ -387,7 +388,7 @@
|
||||
"client_edit": "Edit Client",
|
||||
"client_identifier": "Identifier",
|
||||
"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_mac": "Enter MAC",
|
||||
"form_enter_id": "Enter identifier",
|
||||
|
||||
@@ -50,7 +50,7 @@ const CertificateStatus = ({
|
||||
{dnsNames && (
|
||||
<li>
|
||||
<Trans>encryption_hostnames</Trans>:
|
||||
{dnsNames}
|
||||
{dnsNames.join(', ')}
|
||||
</li>
|
||||
)}
|
||||
</Fragment>
|
||||
@@ -65,7 +65,7 @@ CertificateStatus.propTypes = {
|
||||
subject: PropTypes.string,
|
||||
issuer: PropTypes.string,
|
||||
notAfter: PropTypes.string,
|
||||
dnsNames: PropTypes.string,
|
||||
dnsNames: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export default withTranslation()(CertificateStatus);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
toNumber,
|
||||
} from '../../../helpers/form';
|
||||
import {
|
||||
validateIsSafePort, validatePort, validatePortQuic, validatePortTLS,
|
||||
validateServerName, validateIsSafePort, validatePort, validatePortQuic, validatePortTLS,
|
||||
} from '../../../helpers/validators';
|
||||
import i18n from '../../../i18n';
|
||||
import KeyStatus from './KeyStatus';
|
||||
@@ -127,6 +127,7 @@ let Form = (props) => {
|
||||
placeholder={t('encryption_server_enter')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
validate={validateServerName}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_server_desc</Trans>
|
||||
@@ -413,7 +414,7 @@ Form.propTypes = {
|
||||
valid_key: PropTypes.bool,
|
||||
valid_cert: PropTypes.bool,
|
||||
valid_pair: PropTypes.bool,
|
||||
dns_names: PropTypes.string,
|
||||
dns_names: PropTypes.arrayOf(PropTypes.string),
|
||||
key_type: PropTypes.string,
|
||||
issuer: PropTypes.string,
|
||||
subject: PropTypes.string,
|
||||
|
||||
@@ -11,15 +11,17 @@ const MOBILE_CONFIG_LINKS = {
|
||||
DOT: '/apple/dot.mobileconfig',
|
||||
DOH: '/apple/doh.mobileconfig',
|
||||
};
|
||||
|
||||
/* FIXME: find out `client_id` */
|
||||
const renderMobileconfigInfo = ({ label, components, server_name }) => <li key={label}>
|
||||
<Trans components={components}>{label}</Trans>
|
||||
<ul>
|
||||
<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>
|
||||
</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>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -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_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 = /\/[^/]*$/;
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
R_URL_REQUIRES_PROTOCOL,
|
||||
STANDARD_WEB_PORT,
|
||||
UNSAFE_PORTS,
|
||||
R_DOMAIN,
|
||||
R_SERVER_NAME,
|
||||
} from './constants';
|
||||
import { getLastIpv4Octet, isValidAbsolutePath } from './form';
|
||||
|
||||
@@ -64,19 +66,35 @@ export const validateClientId = (value) => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const formattedValue = value.trim();
|
||||
const formattedValue = value ? value.trim() : value;
|
||||
if (formattedValue && !(
|
||||
R_IPV4.test(formattedValue)
|
||||
|| R_IPV6.test(formattedValue)
|
||||
|| R_MAC.test(formattedValue)
|
||||
|| R_CIDR.test(formattedValue)
|
||||
|| R_CIDR_IPV6.test(formattedValue)
|
||||
|| R_DOMAIN.test(formattedValue)
|
||||
)) {
|
||||
return 'form_error_client_id_format';
|
||||
}
|
||||
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}
|
||||
* @returns {undefined|string}
|
||||
|
||||
17
go.mod
17
go.mod
@@ -3,11 +3,12 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.14
|
||||
|
||||
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/urlfilter v0.14.1
|
||||
github.com/AdguardTeam/urlfilter v0.13.0
|
||||
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/go-ping/ping v0.0.0-20201115131931-3300c582a663
|
||||
github.com/gobuffalo/envy v1.9.0 // indirect
|
||||
@@ -15,8 +16,10 @@ require (
|
||||
github.com/gobuffalo/packr/v2 v2.8.1 // indirect
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714
|
||||
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/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/raw v0.0.0-20191009151244-50f2db8cc065
|
||||
github.com/miekg/dns v1.1.35
|
||||
@@ -27,12 +30,14 @@ require (
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/u-root/u-root v7.0.0+incompatible
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
|
||||
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
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.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
|
||||
)
|
||||
|
||||
48
go.sum
48
go.sum
@@ -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/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=
|
||||
github.com/AdguardTeam/dnsproxy v0.33.7 h1:DXsLTJoBSUejB2ZqVHyMG0/kXD8PzuVPbLCsGKBdaDc=
|
||||
github.com/AdguardTeam/dnsproxy v0.33.7/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs=
|
||||
github.com/AdguardTeam/dnsproxy v0.33.2 h1:k5aMcsw3TA/G2DR8EjIkwutDPuuRkKh8xij4cFWC6Fk=
|
||||
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.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=
|
||||
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/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
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.14.1/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
|
||||
github.com/AdguardTeam/urlfilter v0.13.0 h1:MfO46K81JVTkhgP6gRu/buKl5wAOSfusjiDwjT1JN1c=
|
||||
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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
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/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/ameshkov/dnscrypt/v2 v2.0.1 h1:igNVNM6NLBOqYUzHXaDUxn8i+wJXOsosY0/xEBirixA=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.0.1/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.0.0 h1:i83G8MeGLrAFgUL8GSu98TVhtFDEifF7SIS7Qi/RZ3U=
|
||||
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/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
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/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/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/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8=
|
||||
github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys=
|
||||
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/magiconair/properties v1.8.0/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/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
||||
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/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
|
||||
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/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
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.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
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/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
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-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
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-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA=
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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-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-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/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-20181017192945-9dcd33a902f4/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-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-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-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||
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/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/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.3.0/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/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-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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
|
||||
@@ -188,7 +188,7 @@ func BlockedSvcKnown(s string) bool {
|
||||
}
|
||||
|
||||
// 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{}
|
||||
if global {
|
||||
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()
|
||||
list := d.Config.BlockedServices
|
||||
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{}
|
||||
err := json.NewDecoder(r.Body).Decode(&list)
|
||||
if err != nil {
|
||||
@@ -241,7 +241,7 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
// 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("POST", "/control/blocked_services/set", d.handleBlockedServicesSet)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package dnsfilter implements a DNS request and response filter.
|
||||
// Package dnsfilter implements a DNS filter.
|
||||
package dnsfilter
|
||||
|
||||
import (
|
||||
@@ -91,12 +91,12 @@ type filtersInitializerParams struct {
|
||||
blockFilters []Filter
|
||||
}
|
||||
|
||||
// DNSFilter matches hostnames and DNS requests against filtering rules.
|
||||
type DNSFilter struct {
|
||||
// Dnsfilter holds added rules and performs hostname matches against the rules
|
||||
type Dnsfilter struct {
|
||||
rulesStorage *filterlist.RuleStorage
|
||||
filteringEngine *urlfilter.DNSEngine
|
||||
rulesStorageAllow *filterlist.RuleStorage
|
||||
filteringEngineAllow *urlfilter.DNSEngine
|
||||
rulesStorageWhite *filterlist.RuleStorage
|
||||
filteringEngineWhite *urlfilter.DNSEngine
|
||||
engineLock sync.RWMutex
|
||||
|
||||
parentalServer string // access via methods
|
||||
@@ -127,16 +127,13 @@ const (
|
||||
|
||||
// NotFilteredNotFound - host was not find in any checks, default value for result
|
||||
NotFilteredNotFound Reason = iota
|
||||
// NotFilteredAllowList - the host is explicitly allowed
|
||||
NotFilteredAllowList
|
||||
// NotFilteredError is returned when there was an error during
|
||||
// checking. Reserved, currently unused.
|
||||
NotFilteredError
|
||||
// NotFilteredWhiteList - the host is explicitly whitelisted
|
||||
NotFilteredWhiteList
|
||||
|
||||
// reasons for filtering
|
||||
|
||||
// FilteredBlockList - the host was matched to be advertising host
|
||||
FilteredBlockList
|
||||
// FilteredBlackList - the host was matched to be advertising host
|
||||
FilteredBlackList
|
||||
// FilteredSafeBrowsing - the host was matched to be malicious/phishing
|
||||
FilteredSafeBrowsing
|
||||
// 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
|
||||
|
||||
// ReasonRewrite is returned when there was a rewrite by
|
||||
// a legacy DNS Rewrite rule.
|
||||
// ReasonRewrite - rewrite rule was applied
|
||||
ReasonRewrite
|
||||
|
||||
// RewriteAutoHosts is returned when there was a rewrite by
|
||||
// autohosts rules (/etc/hosts and so on).
|
||||
RewriteAutoHosts
|
||||
|
||||
// DNSRewriteRule is returned when a $dnsrewrite filter rule was
|
||||
// applied.
|
||||
DNSRewriteRule
|
||||
// RewriteEtcHosts - rewrite by /etc/hosts rule
|
||||
RewriteEtcHosts
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Resync with actual code names or replace completely
|
||||
// in HTTP API v1.
|
||||
var reasonNames = []string{
|
||||
NotFilteredNotFound: "NotFilteredNotFound",
|
||||
NotFilteredAllowList: "NotFilteredWhiteList",
|
||||
NotFilteredError: "NotFilteredError",
|
||||
"NotFilteredNotFound",
|
||||
"NotFilteredWhiteList",
|
||||
"NotFilteredError",
|
||||
|
||||
FilteredBlockList: "FilteredBlackList",
|
||||
FilteredSafeBrowsing: "FilteredSafeBrowsing",
|
||||
FilteredParental: "FilteredParental",
|
||||
FilteredInvalid: "FilteredInvalid",
|
||||
FilteredSafeSearch: "FilteredSafeSearch",
|
||||
FilteredBlockedService: "FilteredBlockedService",
|
||||
"FilteredBlackList",
|
||||
"FilteredSafeBrowsing",
|
||||
"FilteredParental",
|
||||
"FilteredInvalid",
|
||||
"FilteredSafeSearch",
|
||||
"FilteredBlockedService",
|
||||
|
||||
ReasonRewrite: "Rewrite",
|
||||
|
||||
RewriteAutoHosts: "RewriteEtcHosts",
|
||||
|
||||
DNSRewriteRule: "DNSRewriteRule",
|
||||
"Rewrite",
|
||||
"RewriteEtcHosts",
|
||||
}
|
||||
|
||||
func (r Reason) String() string {
|
||||
if r < 0 || int(r) >= len(reasonNames) {
|
||||
if uint(r) >= uint(len(reasonNames)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return reasonNames[r]
|
||||
}
|
||||
|
||||
@@ -201,7 +186,7 @@ func (r Reason) In(reasons ...Reason) bool {
|
||||
}
|
||||
|
||||
// GetConfig - get configuration
|
||||
func (d *DNSFilter) GetConfig() RequestFilteringSettings {
|
||||
func (d *Dnsfilter) GetConfig() RequestFilteringSettings {
|
||||
c := RequestFilteringSettings{}
|
||||
// d.confLock.RLock()
|
||||
c.SafeSearchEnabled = d.Config.SafeSearchEnabled
|
||||
@@ -212,7 +197,7 @@ func (d *DNSFilter) GetConfig() RequestFilteringSettings {
|
||||
}
|
||||
|
||||
// WriteDiskConfig - write configuration
|
||||
func (d *DNSFilter) WriteDiskConfig(c *Config) {
|
||||
func (d *Dnsfilter) WriteDiskConfig(c *Config) {
|
||||
d.confLock.Lock()
|
||||
*c = d.Config
|
||||
c.Rewrites = rewriteArrayDup(d.Config.Rewrites)
|
||||
@@ -223,7 +208,7 @@ func (d *DNSFilter) WriteDiskConfig(c *Config) {
|
||||
// SetFilters - set new filters (synchronously or asynchronously)
|
||||
// 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.
|
||||
func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
|
||||
func (d *Dnsfilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
|
||||
if async {
|
||||
params := filtersInitializerParams{
|
||||
allowFilters: allowFilters,
|
||||
@@ -257,7 +242,7 @@ func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool)
|
||||
}
|
||||
|
||||
// Starts initializing new filters by signal from channel
|
||||
func (d *DNSFilter) filtersInitializer() {
|
||||
func (d *Dnsfilter) filtersInitializer() {
|
||||
for {
|
||||
params := <-d.filtersInitializerChan
|
||||
err := d.initFiltering(params.allowFilters, params.blockFilters)
|
||||
@@ -269,13 +254,13 @@ func (d *DNSFilter) filtersInitializer() {
|
||||
}
|
||||
|
||||
// Close - close the object
|
||||
func (d *DNSFilter) Close() {
|
||||
func (d *Dnsfilter) Close() {
|
||||
d.engineLock.Lock()
|
||||
defer d.engineLock.Unlock()
|
||||
d.reset()
|
||||
}
|
||||
|
||||
func (d *DNSFilter) reset() {
|
||||
func (d *Dnsfilter) reset() {
|
||||
var err error
|
||||
|
||||
if d.rulesStorage != nil {
|
||||
@@ -285,15 +270,16 @@ func (d *DNSFilter) reset() {
|
||||
}
|
||||
}
|
||||
|
||||
if d.rulesStorageAllow != nil {
|
||||
err = d.rulesStorageAllow.Close()
|
||||
if d.rulesStorageWhite != nil {
|
||||
err = d.rulesStorageWhite.Close()
|
||||
if err != nil {
|
||||
log.Error("dnsfilter: rulesStorageAllow.Close: %s", err)
|
||||
log.Error("dnsfilter: rulesStorageWhite.Close: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type dnsFilterContext struct {
|
||||
stats Stats
|
||||
safebrowsingCache cache.Cache
|
||||
parentalCache cache.Cache
|
||||
safeSearchCache cache.Cache
|
||||
@@ -301,63 +287,34 @@ type dnsFilterContext struct {
|
||||
|
||||
var gctx dnsFilterContext // global dnsfilter context
|
||||
|
||||
// ResultRule contains information about applied rules.
|
||||
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?
|
||||
// Result holds state of hostname check
|
||||
type Result struct {
|
||||
// IsFiltered is true if the request is filtered.
|
||||
IsFiltered bool `json:",omitempty"`
|
||||
IsFiltered bool `json:",omitempty"` // True if the host name is filtered
|
||||
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.
|
||||
Reason Reason `json:",omitempty"`
|
||||
// for ReasonRewrite:
|
||||
CanonName string `json:",omitempty"` // CNAME value
|
||||
|
||||
// Rules are applied rules. If Rules are not empty, each rule
|
||||
// is not nil.
|
||||
Rules []*ResultRule `json:",omitempty"`
|
||||
|
||||
// ReverseHosts is the reverse lookup rewrite result. It is
|
||||
// empty unless Reason is set to RewriteAutoHosts.
|
||||
// for RewriteEtcHosts:
|
||||
ReverseHosts []string `json:",omitempty"`
|
||||
|
||||
// IPList is the lookup rewrite result. It is empty unless
|
||||
// Reason is set to RewriteAutoHosts or ReasonRewrite.
|
||||
IPList []net.IP `json:",omitempty"`
|
||||
// for ReasonRewrite & RewriteEtcHosts:
|
||||
IPList []net.IP `json:",omitempty"` // list of IP addresses
|
||||
|
||||
// CanonName is the CNAME value from the lookup rewrite result.
|
||||
// It is empty unless Reason is set to ReasonRewrite.
|
||||
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"`
|
||||
// for FilteredBlockedService:
|
||||
ServiceName string `json:",omitempty"` // Name of the blocked service
|
||||
}
|
||||
|
||||
// Matched returns true if any match at all was found regardless of
|
||||
// whether it was filtered or not.
|
||||
// Matched can be used to see if any match at all was found, no matter filtered or not
|
||||
func (r Reason) Matched() bool {
|
||||
return r != NotFilteredNotFound
|
||||
}
|
||||
|
||||
// CheckHostRules tries to match the host against filtering rules only.
|
||||
func (d *DNSFilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
||||
// CheckHostRules tries to match the host against filtering rules only
|
||||
func (d *Dnsfilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
||||
if !setts.FilteringEnabled {
|
||||
return Result{}, nil
|
||||
}
|
||||
@@ -365,9 +322,9 @@ func (d *DNSFilter) CheckHostRules(host string, qtype uint16, setts *RequestFilt
|
||||
return d.matchHost(host, qtype, *setts)
|
||||
}
|
||||
|
||||
// CheckHost tries to match the host against filtering rules, then
|
||||
// safebrowsing and parental control rules, if they are enabled.
|
||||
func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
||||
// CheckHost tries to match the host against filtering rules,
|
||||
// then safebrowsing and parental if they are enabled
|
||||
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
|
||||
if host == "" {
|
||||
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 {
|
||||
result, err = d.matchHost(host, qtype, *setts)
|
||||
if err != nil {
|
||||
@@ -450,10 +410,10 @@ func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||
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)
|
||||
if ips != nil {
|
||||
result.Reason = RewriteAutoHosts
|
||||
result.Reason = RewriteEtcHosts
|
||||
result.IPList = ips
|
||||
|
||||
return true
|
||||
@@ -461,7 +421,7 @@ func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (m
|
||||
|
||||
revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype)
|
||||
if len(revHosts) != 0 {
|
||||
result.Reason = RewriteAutoHosts
|
||||
result.Reason = RewriteEtcHosts
|
||||
|
||||
// TODO(a.garipov): Optimize this with a buffer.
|
||||
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)
|
||||
// . 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
|
||||
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()
|
||||
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)
|
||||
|
||||
if host == rr[0].Answer { // "host == CNAME" is an exception
|
||||
res.Reason = NotFilteredNotFound
|
||||
|
||||
res.Reason = 0
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -540,16 +501,9 @@ func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result {
|
||||
res.Reason = FilteredBlockedService
|
||||
res.IsFiltered = true
|
||||
res.ServiceName = s.Name
|
||||
|
||||
ruleText := rule.Text()
|
||||
res.Rules = []*ResultRule{{
|
||||
FilterListID: int64(rule.GetFilterListID()),
|
||||
Text: ruleText,
|
||||
}}
|
||||
|
||||
log.Debug("blocked services: matched rule: %s host: %s service: %s",
|
||||
ruleText, host, s.Name)
|
||||
|
||||
res.Rule = rule.Text()
|
||||
log.Debug("Blocked Services: matched rule: %s host: %s service: %s",
|
||||
res.Rule, host, s.Name)
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -616,12 +570,12 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
|
||||
}
|
||||
|
||||
// Initialize urlfilter objects.
|
||||
func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||
func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||
rulesStorage, filteringEngine, err := createFilteringEngine(blockFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rulesStorageAllow, filteringEngineAllow, err := createFilteringEngine(allowFilters)
|
||||
rulesStorageWhite, filteringEngineWhite, err := createFilteringEngine(allowFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -630,8 +584,8 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||
d.reset()
|
||||
d.rulesStorage = rulesStorage
|
||||
d.filteringEngine = filteringEngine
|
||||
d.rulesStorageAllow = rulesStorageAllow
|
||||
d.filteringEngineAllow = filteringEngineAllow
|
||||
d.rulesStorageWhite = rulesStorageWhite
|
||||
d.filteringEngineWhite = filteringEngineWhite
|
||||
d.engineLock.Unlock()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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()
|
||||
// Keep in mind that this lock must be held no just when calling Match()
|
||||
// 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,
|
||||
}
|
||||
|
||||
if d.filteringEngineAllow != nil {
|
||||
dnsres, ok := d.filteringEngineAllow.MatchRequest(ureq)
|
||||
if d.filteringEngineWhite != nil {
|
||||
rr, ok := d.filteringEngineWhite.MatchRequest(ureq)
|
||||
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
|
||||
}
|
||||
|
||||
dnsres, ok := d.filteringEngine.MatchRequest(ureq)
|
||||
|
||||
// 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 {
|
||||
rr, ok := d.filteringEngine.MatchRequest(ureq)
|
||||
if !ok {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
if dnsres.NetworkRule != nil {
|
||||
if rr.NetworkRule != nil {
|
||||
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
||||
host, dnsres.NetworkRule.Text(), dnsres.NetworkRule.GetFilterListID())
|
||||
reason := FilteredBlockList
|
||||
if dnsres.NetworkRule.Whitelist {
|
||||
reason = NotFilteredAllowList
|
||||
host, rr.NetworkRule.Text(), rr.NetworkRule.GetFilterListID())
|
||||
reason := FilteredBlackList
|
||||
if rr.NetworkRule.Whitelist {
|
||||
reason = NotFilteredWhiteList
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
res := makeResult(rr.NetworkRule, reason)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if qtype == dns.TypeAAAA && dnsres.HostRulesV6 != nil {
|
||||
rule := dnsres.HostRulesV6[0] // note that we process only 1 matched rule
|
||||
if qtype == dns.TypeA && rr.HostRulesV4 != nil {
|
||||
rule := rr.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
|
||||
|
||||
res := makeResult(rule, FilteredBlackList)
|
||||
res.IP = rule.IP.To4()
|
||||
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
|
||||
// Return the first matched host rule, but without an IP address
|
||||
var rule rules.Rule
|
||||
if dnsres.HostRulesV4 != nil {
|
||||
rule = dnsres.HostRulesV4[0]
|
||||
} else if dnsres.HostRulesV6 != nil {
|
||||
rule = dnsres.HostRulesV6[0]
|
||||
if rr.HostRulesV4 != nil {
|
||||
rule = rr.HostRulesV4[0]
|
||||
} else if rr.HostRulesV6 != nil {
|
||||
rule = rr.HostRulesV6[0]
|
||||
}
|
||||
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 = net.IP{}
|
||||
|
||||
res := makeResult(rule, FilteredBlackList)
|
||||
res.IP = net.IP{}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
// makeResult returns a properly constructed Result.
|
||||
// Construct Result object
|
||||
func makeResult(rule rules.Rule, reason Reason) Result {
|
||||
res := Result{
|
||||
Reason: reason,
|
||||
Rules: []*ResultRule{{
|
||||
FilterListID: int64(rule.GetFilterListID()),
|
||||
Text: rule.Text(),
|
||||
}},
|
||||
}
|
||||
|
||||
if reason == FilteredBlockList {
|
||||
res := Result{}
|
||||
res.FilterID = int64(rule.GetFilterListID())
|
||||
res.Rule = rule.Text()
|
||||
res.Reason = reason
|
||||
if reason == FilteredBlackList {
|
||||
res.IsFiltered = true
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -780,7 +705,7 @@ func InitModule() {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
cacheConf := cache.Config{
|
||||
EnableLRU: true,
|
||||
@@ -802,7 +727,7 @@ func New(c *Config, blockFilters []Filter) *DNSFilter {
|
||||
}
|
||||
}
|
||||
|
||||
d := new(DNSFilter)
|
||||
d := new(Dnsfilter)
|
||||
|
||||
err := d.initSecurityServices()
|
||||
if err != nil {
|
||||
@@ -840,7 +765,7 @@ func New(c *Config, blockFilters []Filter) *DNSFilter {
|
||||
// Start - start the module:
|
||||
// . start async filtering initializer goroutine
|
||||
// . register web handlers
|
||||
func (d *DNSFilter) Start() {
|
||||
func (d *Dnsfilter) Start() {
|
||||
d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
|
||||
go d.filtersInitializer()
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ func purgeCaches() {
|
||||
}
|
||||
}
|
||||
|
||||
func NewForTest(c *Config, filters []Filter) *DNSFilter {
|
||||
func NewForTest(c *Config, filters []Filter) *Dnsfilter {
|
||||
setts = RequestFilteringSettings{}
|
||||
setts.FilteringEnabled = true
|
||||
if c != nil {
|
||||
@@ -58,48 +58,38 @@ func NewForTest(c *Config, filters []Filter) *DNSFilter {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DNSFilter) checkMatch(t *testing.T, hostname string) {
|
||||
func (d *Dnsfilter) checkMatch(t *testing.T, hostname string) {
|
||||
t.Helper()
|
||||
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||
if err != nil {
|
||||
t.Errorf("Error while matching host %s: %s", hostname, err)
|
||||
}
|
||||
if !res.IsFiltered {
|
||||
if !ret.IsFiltered {
|
||||
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()
|
||||
|
||||
res, err := d.CheckHost(hostname, qtype, &setts)
|
||||
ret, err := d.CheckHost(hostname, qtype, &setts)
|
||||
if err != nil {
|
||||
t.Errorf("Error while matching host %s: %s", hostname, err)
|
||||
}
|
||||
|
||||
if !res.IsFiltered {
|
||||
if !ret.IsFiltered {
|
||||
t.Errorf("Expected hostname %s to match", hostname)
|
||||
}
|
||||
|
||||
if len(res.Rules) == 0 {
|
||||
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)
|
||||
if ret.IP == nil || ret.IP.String() != ip {
|
||||
t.Errorf("Expected ip %s to match, actual: %v", ip, ret.IP)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSFilter) checkMatchEmpty(t *testing.T, hostname string) {
|
||||
func (d *Dnsfilter) checkMatchEmpty(t *testing.T, hostname string) {
|
||||
t.Helper()
|
||||
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -130,43 +120,26 @@ func TestEtcHostsMatching(t *testing.T) {
|
||||
d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA)
|
||||
|
||||
// ...but empty IPv6
|
||||
res, err := d.CheckHost("block.com", dns.TypeAAAA, &setts)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, res.IsFiltered)
|
||||
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)
|
||||
}
|
||||
ret, err := d.CheckHost("block.com", dns.TypeAAAA, &setts)
|
||||
assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0)
|
||||
assert.True(t, ret.Rule == "0.0.0.0 block.com")
|
||||
|
||||
// IPv6
|
||||
d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA)
|
||||
|
||||
// ...but empty IPv4
|
||||
res, err = d.CheckHost("ipv6.com", dns.TypeA, &setts)
|
||||
assert.Nil(t, err)
|
||||
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)
|
||||
}
|
||||
ret, err = d.CheckHost("ipv6.com", dns.TypeA, &setts)
|
||||
assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0)
|
||||
|
||||
// 2 IPv4 (return only the first one)
|
||||
res, err = d.CheckHost("host2", dns.TypeA, &setts)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, res.IsFiltered)
|
||||
if assert.Len(t, res.Rules, 1) {
|
||||
loopback4 := net.IP{0, 0, 0, 1}
|
||||
assert.Equal(t, res.Rules[0].IP, loopback4)
|
||||
}
|
||||
ret, err = d.CheckHost("host2", dns.TypeA, &setts)
|
||||
assert.True(t, err == nil && ret.IsFiltered)
|
||||
assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("0.0.0.1")))
|
||||
|
||||
// ...and 1 IPv6 address
|
||||
res, err = d.CheckHost("host2", dns.TypeAAAA, &setts)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, res.IsFiltered)
|
||||
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)
|
||||
}
|
||||
ret, err = d.CheckHost("host2", dns.TypeAAAA, &setts)
|
||||
assert.True(t, err == nil && ret.IsFiltered)
|
||||
assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("::1")))
|
||||
}
|
||||
|
||||
// SAFE BROWSING
|
||||
@@ -178,6 +151,7 @@ func TestSafeBrowsing(t *testing.T) {
|
||||
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Close()
|
||||
gctx.stats.Safebrowsing.Requests = 0
|
||||
d.checkMatch(t, "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
|
||||
for _, host := range yandex {
|
||||
res, err := d.CheckHost(host, dns.TypeA, &setts)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, res.IsFiltered)
|
||||
if assert.Len(t, res.Rules, 1) {
|
||||
assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56")
|
||||
result, err := d.CheckHost(host, dns.TypeA, &setts)
|
||||
if err != nil {
|
||||
t.Errorf("SafeSearch doesn't work for yandex domain `%s` cause %s", host, err)
|
||||
}
|
||||
|
||||
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
|
||||
for _, host := range googleDomains {
|
||||
res, err := d.CheckHost(host, dns.TypeA, &setts)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, res.IsFiltered)
|
||||
if assert.Len(t, res.Rules, 1) {
|
||||
assert.NotEqual(t, res.Rules[0].IP.String(), "0.0.0.0")
|
||||
result, err := d.CheckHost(host, dns.TypeA, &setts)
|
||||
if err != nil {
|
||||
t.Errorf("SafeSearch doesn't work for %s cause %s", host, err)
|
||||
}
|
||||
|
||||
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()
|
||||
domain := "yandex.ru"
|
||||
|
||||
// Check host with disabled safesearch.
|
||||
res, err := d.CheckHost(domain, dns.TypeA, &setts)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, res.IsFiltered)
|
||||
assert.Len(t, res.Rules, 0)
|
||||
var result Result
|
||||
var err error
|
||||
|
||||
// Check host with disabled safesearch
|
||||
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)
|
||||
defer d.Close()
|
||||
|
||||
res, err = d.CheckHost(domain, dns.TypeA, &setts)
|
||||
result, err = d.CheckHost(domain, dns.TypeA, &setts)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
|
||||
}
|
||||
|
||||
// For yandex we already know valid ip.
|
||||
if assert.Len(t, res.Rules, 1) {
|
||||
assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56")
|
||||
// Fir yandex we already know valid ip
|
||||
if result.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)
|
||||
assert.True(t, isFound)
|
||||
if assert.Len(t, cachedValue.Rules, 1) {
|
||||
assert.Equal(t, cachedValue.Rules[0].IP.String(), "213.180.193.56")
|
||||
|
||||
if !isFound {
|
||||
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)
|
||||
defer d.Close()
|
||||
domain := "www.google.ru"
|
||||
res, err := d.CheckHost(domain, dns.TypeA, &setts)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, res.IsFiltered)
|
||||
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!")
|
||||
}
|
||||
|
||||
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
defer d.Close()
|
||||
@@ -322,17 +313,25 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
res, err = d.CheckHost(domain, dns.TypeA, &setts)
|
||||
assert.Nil(t, err)
|
||||
if assert.Len(t, res.Rules, 1) {
|
||||
assert.True(t, res.Rules[0].IP.Equal(ip))
|
||||
result, err = d.CheckHost(domain, dns.TypeA, &setts)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
assert.True(t, isFound)
|
||||
if assert.Len(t, cachedValue.Rules, 1) {
|
||||
assert.True(t, cachedValue.Rules[0].IP.Equal(ip))
|
||||
|
||||
if !isFound {
|
||||
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 (
|
||||
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
|
||||
regexRules = `/example\.org/` + nl + `@@||test.example.org^` + nl
|
||||
maskRules = `test*.example.org^` + nl + `exam*.com` + nl
|
||||
@@ -380,49 +379,49 @@ var tests = []struct {
|
||||
reason Reason
|
||||
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^", "doubleclick.net.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, "test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"blocking", blockingRules, "example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"blocking", blockingRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
|
||||
{"allowlist", allowlistRules, "example.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"allowlist", allowlistRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||
{"allowlist", allowlistRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||
{"allowlist", allowlistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"allowlist", allowlistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"whitelist", whitelistRules, "example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
|
||||
{"important", importantRules, "example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||
{"important", importantRules, "test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"important", importantRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"important", importantRules, "example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"important", importantRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"important", importantRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"important", importantRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
|
||||
{"regex", regexRules, "example.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"regex", regexRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||
{"regex", regexRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||
{"regex", regexRules, "testexample.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"regex", regexRules, "onemoreexample.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"regex", regexRules, "example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"regex", regexRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"regex", regexRules, "testexample.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"regex", regexRules, "onemoreexample.org", true, FilteredBlackList, dns.TypeA},
|
||||
|
||||
{"mask", maskRules, "test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"mask", maskRules, "test2.example.org", true, FilteredBlockList, dns.TypeA},
|
||||
{"mask", maskRules, "example.com", true, FilteredBlockList, dns.TypeA},
|
||||
{"mask", maskRules, "exampleeee.com", true, FilteredBlockList, dns.TypeA},
|
||||
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlockList, dns.TypeA},
|
||||
{"mask", maskRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "test2.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "example.com", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "exampleeee.com", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "example.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"mask", maskRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"mask", maskRules, "example.co.uk", false, NotFilteredNotFound, dns.TypeA},
|
||||
|
||||
{"dnstype", dnstypeRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"dnstype", dnstypeRules, "example.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"dnstype", dnstypeRules, "example.org", true, FilteredBlockList, dns.TypeAAAA},
|
||||
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeAAAA},
|
||||
{"dnstype", dnstypeRules, "example.org", true, FilteredBlackList, dns.TypeAAAA},
|
||||
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeAAAA},
|
||||
}
|
||||
|
||||
func TestMatching(t *testing.T) {
|
||||
@@ -434,15 +433,15 @@ func TestMatching(t *testing.T) {
|
||||
d := NewForTest(nil, filters)
|
||||
defer d.Close()
|
||||
|
||||
res, err := d.CheckHost(test.hostname, test.dnsType, &setts)
|
||||
ret, err := d.CheckHost(test.hostname, test.dnsType, &setts)
|
||||
if err != nil {
|
||||
t.Errorf("Error while matching host %s: %s", test.hostname, err)
|
||||
}
|
||||
if res.IsFiltered != test.isFiltered {
|
||||
t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, res.IsFiltered, test.isFiltered)
|
||||
if ret.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 {
|
||||
t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, res.Reason.String(), test.reason.String())
|
||||
if ret.Reason != test.reason {
|
||||
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()
|
||||
|
||||
// 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, !res.IsFiltered && res.Reason == NotFilteredAllowList)
|
||||
if assert.Len(t, res.Rules, 1) {
|
||||
assert.True(t, res.Rules[0].Text == "||host1^")
|
||||
}
|
||||
assert.True(t, !ret.IsFiltered && ret.Reason == NotFilteredWhiteList)
|
||||
assert.True(t, ret.Rule == "||host1^")
|
||||
|
||||
// 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, res.IsFiltered && res.Reason == FilteredBlockList)
|
||||
if assert.Len(t, res.Rules, 1) {
|
||||
assert.True(t, res.Rules[0].Text == "||host2^")
|
||||
}
|
||||
assert.True(t, ret.IsFiltered && ret.Reason == FilteredBlackList)
|
||||
assert.True(t, ret.Rule == "||host2^")
|
||||
}
|
||||
|
||||
// CLIENT SETTINGS
|
||||
@@ -511,8 +506,8 @@ func TestClientSettings(t *testing.T) {
|
||||
|
||||
// blocked by filters
|
||||
r, _ = d.CheckHost("example.org", dns.TypeA, &setts)
|
||||
if !r.IsFiltered || r.Reason != FilteredBlockList {
|
||||
t.Fatalf("CheckHost FilteredBlockList")
|
||||
if !r.IsFiltered || r.Reason != FilteredBlackList {
|
||||
t.Fatalf("CheckHost FilteredBlackList")
|
||||
}
|
||||
|
||||
// blocked by parental
|
||||
@@ -564,11 +559,11 @@ func BenchmarkSafeBrowsing(b *testing.B) {
|
||||
defer d.Close()
|
||||
for n := 0; n < b.N; n++ {
|
||||
hostname := "wmconvirus.narod.ru"
|
||||
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||
if err != nil {
|
||||
b.Errorf("Error while matching host %s: %s", hostname, err)
|
||||
}
|
||||
if !res.IsFiltered {
|
||||
if !ret.IsFiltered {
|
||||
b.Errorf("Expected hostname %s to match", hostname)
|
||||
}
|
||||
}
|
||||
@@ -580,11 +575,11 @@ func BenchmarkSafeBrowsingParallel(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
hostname := "wmconvirus.narod.ru"
|
||||
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||
if err != nil {
|
||||
b.Errorf("Error while matching host %s: %s", hostname, err)
|
||||
}
|
||||
if !res.IsFiltered {
|
||||
if !ret.IsFiltered {
|
||||
b.Errorf("Expected hostname %s to match", hostname)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func (r *RewriteEntry) prepare() {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSFilter) prepareRewrites() {
|
||||
func (d *Dnsfilter) prepareRewrites() {
|
||||
for i := range d.Rewrites {
|
||||
d.Rewrites[i].prepare()
|
||||
}
|
||||
@@ -148,7 +148,7 @@ type rewriteEntryJSON struct {
|
||||
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{}
|
||||
|
||||
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{}
|
||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||
if err != nil {
|
||||
@@ -191,7 +191,7 @@ func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
||||
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{}
|
||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||
if err != nil {
|
||||
@@ -218,7 +218,7 @@ func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request)
|
||||
d.Config.ConfigModified()
|
||||
}
|
||||
|
||||
func (d *DNSFilter) registerRewritesHandlers() {
|
||||
func (d *Dnsfilter) registerRewritesHandlers() {
|
||||
d.Config.HTTPRegister("GET", "/control/rewrite/list", d.handleRewriteList)
|
||||
d.Config.HTTPRegister("POST", "/control/rewrite/add", d.handleRewriteAdd)
|
||||
d.Config.HTTPRegister("POST", "/control/rewrite/delete", d.handleRewriteDelete)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestRewrites(t *testing.T) {
|
||||
d := DNSFilter{}
|
||||
d := Dnsfilter{}
|
||||
// CNAME, A, AAAA
|
||||
d.Rewrites = []RewriteEntry{
|
||||
{"somecname", "somehost.com", 0, nil},
|
||||
@@ -104,7 +104,7 @@ func TestRewrites(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRewritesLevels(t *testing.T) {
|
||||
d := DNSFilter{}
|
||||
d := Dnsfilter{}
|
||||
// exact host, wildcard L2, wildcard L3
|
||||
d.Rewrites = []RewriteEntry{
|
||||
{"host.com", "1.1.1.1", 0, nil},
|
||||
@@ -133,7 +133,7 @@ func TestRewritesLevels(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRewritesExceptionCNAME(t *testing.T) {
|
||||
d := DNSFilter{}
|
||||
d := Dnsfilter{}
|
||||
// wildcard; exception for a sub-domain
|
||||
d.Rewrites = []RewriteEntry{
|
||||
{"*.host.com", "2.2.2.2", 0, nil},
|
||||
@@ -153,7 +153,7 @@ func TestRewritesExceptionCNAME(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRewritesExceptionWC(t *testing.T) {
|
||||
d := DNSFilter{}
|
||||
d := Dnsfilter{}
|
||||
// wildcard; exception for a sub-wildcard
|
||||
d.Rewrites = []RewriteEntry{
|
||||
{"*.host.com", "2.2.2.2", 0, nil},
|
||||
@@ -173,7 +173,7 @@ func TestRewritesExceptionWC(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRewritesExceptionIP(t *testing.T) {
|
||||
d := DNSFilter{}
|
||||
d := Dnsfilter{}
|
||||
// exception for AAAA record
|
||||
d.Rewrites = []RewriteEntry{
|
||||
{"host.com", "1.2.3.4", 0, nil},
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
expire byte[4]
|
||||
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
|
||||
|
||||
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
|
||||
func (d *DNSFilter) SafeSearchDomain(host string) (string, bool) {
|
||||
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
|
||||
val, ok := safeSearchDomains[host]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (d *DNSFilter) checkSafeSearch(host string) (Result, error) {
|
||||
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
||||
@@ -87,52 +87,49 @@ func (d *DNSFilter) checkSafeSearch(host string) (Result, error) {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
res := Result{
|
||||
IsFiltered: true,
|
||||
Reason: FilteredSafeSearch,
|
||||
Rules: []*ResultRule{{}},
|
||||
}
|
||||
|
||||
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
|
||||
if ip := net.ParseIP(safeHost); ip != nil {
|
||||
res.Rules[0].IP = ip
|
||||
res.IP = ip
|
||||
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
res.Rules[0].IP = ipv4
|
||||
|
||||
l := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, l)
|
||||
|
||||
return res, nil
|
||||
for _, i := range addrs {
|
||||
if ipv4 := i.To4(); ipv4 != nil {
|
||||
res.IP = ipv4
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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.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.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{}{
|
||||
"enabled": d.Config.SafeSearchEnabled,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Safe Browsing, Parental Control
|
||||
|
||||
package dnsfilter
|
||||
|
||||
import (
|
||||
@@ -20,8 +22,6 @@ import (
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// Safe browsing and parental control methods.
|
||||
|
||||
const (
|
||||
dnsTimeout = 3 * time.Second
|
||||
defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
|
||||
@@ -30,7 +30,7 @@ const (
|
||||
pcTXTSuffix = `pc.dns.adguard.com.`
|
||||
)
|
||||
|
||||
func (d *DNSFilter) initSecurityServices() error {
|
||||
func (d *Dnsfilter) initSecurityServices() error {
|
||||
var err error
|
||||
d.safeBrowsingServer = defaultSafebrowsingServer
|
||||
d.parentalServer = defaultParentalServer
|
||||
@@ -287,7 +287,7 @@ func check(c *sbCtx, r Result, u upstream.Upstream) (Result, error) {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
func (d *DNSFilter) checkSafeBrowsing(host string) (Result, error) {
|
||||
func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
|
||||
@@ -301,14 +301,12 @@ func (d *DNSFilter) checkSafeBrowsing(host string) (Result, error) {
|
||||
res := Result{
|
||||
IsFiltered: true,
|
||||
Reason: FilteredSafeBrowsing,
|
||||
Rules: []*ResultRule{{
|
||||
Text: "adguard-malware-shavar",
|
||||
}},
|
||||
Rule: "adguard-malware-shavar",
|
||||
}
|
||||
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 {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("Parental lookup for %s", host)
|
||||
@@ -322,9 +320,7 @@ func (d *DNSFilter) checkParental(host string) (Result, error) {
|
||||
res := Result{
|
||||
IsFiltered: true,
|
||||
Reason: FilteredParental,
|
||||
Rules: []*ResultRule{{
|
||||
Text: "parental CATEGORY_BLACKLISTED",
|
||||
}},
|
||||
Rule: "parental CATEGORY_BLACKLISTED",
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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.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.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{}{
|
||||
"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.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.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{}{
|
||||
"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/disable", d.handleSafeBrowsingDisable)
|
||||
d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus)
|
||||
@@ -366,9 +366,7 @@ func processFilteringAfterResponse(ctx *dnsContext) int {
|
||||
var err error
|
||||
|
||||
switch res.Reason {
|
||||
case dnsfilter.ReasonRewrite,
|
||||
dnsfilter.DNSRewriteRule:
|
||||
|
||||
case dnsfilter.ReasonRewrite:
|
||||
if len(ctx.origQuestion.Name) == 0 {
|
||||
// origQuestion is set in case we get only CNAME without IP from rewrites table
|
||||
break
|
||||
@@ -379,12 +377,12 @@ func processFilteringAfterResponse(ctx *dnsContext) int {
|
||||
|
||||
if len(d.Res.Answer) != 0 {
|
||||
answer := []dns.RR{}
|
||||
answer = append(answer, s.genAnswerCNAME(d.Req, res.CanonName))
|
||||
answer = append(answer, d.Res.Answer...)
|
||||
answer = append(answer, s.genCNAMEAnswer(d.Req, res.CanonName))
|
||||
answer = append(answer, d.Res.Answer...) // host -> IP
|
||||
d.Res.Answer = answer
|
||||
}
|
||||
|
||||
case dnsfilter.NotFilteredAllowList:
|
||||
case dnsfilter.NotFilteredWhiteList:
|
||||
// nothing
|
||||
|
||||
default:
|
||||
|
||||
@@ -48,7 +48,7 @@ var webRegistered bool
|
||||
// The zero Server is empty and ready for use.
|
||||
type Server struct {
|
||||
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)
|
||||
queryLog querylog.QueryLog // Query log instance
|
||||
stats stats.Stats
|
||||
@@ -74,7 +74,7 @@ type Server struct {
|
||||
|
||||
// DNSCreateParams - parameters for NewServer()
|
||||
type DNSCreateParams struct {
|
||||
DNSFilter *dnsfilter.DNSFilter
|
||||
DNSFilter *dnsfilter.Dnsfilter
|
||||
Stats stats.Stats
|
||||
QueryLog querylog.QueryLog
|
||||
DHCPServer dhcpd.ServerInterface
|
||||
|
||||
@@ -296,7 +296,7 @@ func TestBlockedRequest(t *testing.T) {
|
||||
|
||||
func TestServerCustomClientUpstream(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
s.conf.GetCustomUpstreamByClient = func(_ string) *proxy.UpstreamConfig {
|
||||
s.conf.GetCustomUpstreamByClient = func(clientAddr string) *proxy.UpstreamConfig {
|
||||
uc := &proxy.UpstreamConfig{}
|
||||
u := &testUpstream{}
|
||||
u.ipv4 = map[string][]net.IP{}
|
||||
@@ -473,7 +473,7 @@ func TestBlockCNAME(t *testing.T) {
|
||||
func TestClientRulesForCNAMEMatching(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
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
|
||||
}
|
||||
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) {
|
||||
t.Helper()
|
||||
|
||||
req := createTestMessage(host)
|
||||
reply, _, err := client.Exchange(req, addr.String())
|
||||
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) {
|
||||
t.Helper()
|
||||
|
||||
if len(reply.Answer) != 1 {
|
||||
t.Fatalf("DNS server returned reply with wrong number of answers - %d", len(reply.Answer))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -42,8 +42,7 @@ func (s *Server) getClientRequestFilteringSettings(d *proxy.DNSContext) *dnsfilt
|
||||
return &setts
|
||||
}
|
||||
|
||||
// filterDNSRequest applies the dnsFilter and sets d.Res if the request
|
||||
// was filtered.
|
||||
// filterDNSRequest applies the dnsFilter and sets d.Res if the request was filtered
|
||||
func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||
d := ctx.proxyCtx
|
||||
req := d.Req
|
||||
@@ -53,17 +52,13 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||
// Return immediately if there's an error
|
||||
return nil, fmt.Errorf("dnsfilter failed to check host %q: %w", host, err)
|
||||
} 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)
|
||||
} else if res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.DNSRewriteRule) &&
|
||||
res.CanonName != "" &&
|
||||
len(res.IPList) == 0 {
|
||||
// Resolve the new canonical name, not the original host
|
||||
// name. The original question is readded in
|
||||
// processFilteringAfterResponse.
|
||||
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
|
||||
ctx.origQuestion = d.Req.Question[0]
|
||||
// resolve canonical name, not the original host name
|
||||
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)
|
||||
for _, h := range res.ReverseHosts {
|
||||
hdr := dns.RR_Header{
|
||||
@@ -82,33 +77,28 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
name := host
|
||||
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
|
||||
}
|
||||
|
||||
for _, ip := range res.IPList {
|
||||
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)
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
} else if req.Question[0].Qtype == dns.TypeAAAA {
|
||||
a := s.genAnswerAAAA(req, ip)
|
||||
a := s.genAAAAAnswer(req, ip)
|
||||
a.Hdr.Name = dns.Fqdn(name)
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
}
|
||||
}
|
||||
|
||||
d.Res = resp
|
||||
} else if res.Reason == dnsfilter.DNSRewriteRule {
|
||||
err = s.filterDNSRewrite(req, res, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &res, err
|
||||
|
||||
@@ -167,12 +167,11 @@ func (req *dnsConfig) checkCacheTTL() bool {
|
||||
if req.CacheMinTTL == nil && req.CacheMaxTTL == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
var min, max uint32
|
||||
if req.CacheMinTTL != nil {
|
||||
min = *req.CacheMinTTL
|
||||
}
|
||||
if req.CacheMaxTTL != nil {
|
||||
if req.CacheMaxTTL == nil {
|
||||
max = *req.CacheMaxTTL
|
||||
}
|
||||
|
||||
|
||||
@@ -7,22 +7,16 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// Create a DNS response by DNS request and set necessary flags
|
||||
func (s *Server) makeResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||
resp = &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
RecursionAvailable: true,
|
||||
},
|
||||
Compress: true,
|
||||
}
|
||||
|
||||
func (s *Server) makeResponse(req *dns.Msg) *dns.Msg {
|
||||
resp := dns.Msg{}
|
||||
resp.SetReply(req)
|
||||
|
||||
return resp
|
||||
resp.RecursionAvailable = true
|
||||
resp.Compress = true
|
||||
return &resp
|
||||
}
|
||||
|
||||
// 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
|
||||
// the IP address that must be used in response.
|
||||
// In this case regardless of the filtering method, we should return it
|
||||
if result.Reason == dnsfilter.FilteredSafeSearch &&
|
||||
len(result.Rules) > 0 &&
|
||||
result.Rules[0].IP != nil {
|
||||
return s.genResponseWithIP(m, result.Rules[0].IP)
|
||||
if result.Reason == dnsfilter.FilteredSafeSearch && result.IP != nil {
|
||||
return s.genResponseWithIP(m, result.IP)
|
||||
}
|
||||
|
||||
if s.conf.BlockingMode == "null_ip" {
|
||||
@@ -76,8 +68,8 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
|
||||
// Default blocking mode
|
||||
// If there's an IP specified in the rule, return it
|
||||
// For host-type rules, return null IP
|
||||
if len(result.Rules) > 0 && result.Rules[0].IP != nil {
|
||||
return s.genResponseWithIP(m, result.Rules[0].IP)
|
||||
if result.IP != nil {
|
||||
return s.genResponseWithIP(m, result.IP)
|
||||
}
|
||||
|
||||
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 {
|
||||
resp := s.makeResponse(request)
|
||||
resp.Answer = append(resp.Answer, s.genAnswerA(request, ip))
|
||||
resp.Answer = append(resp.Answer, s.genAAnswer(request, ip))
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Server) genAAAARecord(request *dns.Msg, ip net.IP) *dns.Msg {
|
||||
resp := s.makeResponse(request)
|
||||
resp.Answer = append(resp.Answer, s.genAnswerAAAA(request, ip))
|
||||
resp.Answer = append(resp.Answer, s.genAAAAAnswer(request, ip))
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Server) hdr(req *dns.Msg, rrType rules.RRType) (h dns.RR_Header) {
|
||||
return dns.RR_Header{
|
||||
func (s *Server) genAAnswer(req *dns.Msg, ip net.IP) *dns.A {
|
||||
answer := new(dns.A)
|
||||
answer.Hdr = dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: rrType,
|
||||
Rrtype: dns.TypeA,
|
||||
Ttl: s.conf.BlockedResponseTTL,
|
||||
Class: dns.ClassINET,
|
||||
}
|
||||
answer.A = ip
|
||||
return answer
|
||||
}
|
||||
|
||||
func (s *Server) genAnswerA(req *dns.Msg, ip net.IP) (ans *dns.A) {
|
||||
return &dns.A{
|
||||
Hdr: s.hdr(req, dns.TypeA),
|
||||
A: ip,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
func (s *Server) genAAAAAnswer(req *dns.Msg, ip net.IP) *dns.AAAA {
|
||||
answer := new(dns.AAAA)
|
||||
answer.Hdr = dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypeAAAA,
|
||||
Ttl: s.conf.BlockedResponseTTL,
|
||||
Class: dns.ClassINET,
|
||||
}
|
||||
answer.AAAA = ip
|
||||
return answer
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
func (s *Server) makeResponseREFUSED(request *dns.Msg) *dns.Msg {
|
||||
resp := dns.Msg{}
|
||||
|
||||
@@ -91,7 +91,7 @@ func (s *Server) updateStats(d *proxy.DNSContext, elapsed time.Duration, res dns
|
||||
case dnsfilter.FilteredSafeSearch:
|
||||
e.Result = stats.RSafeSearch
|
||||
|
||||
case dnsfilter.FilteredBlockList:
|
||||
case dnsfilter.FilteredBlackList:
|
||||
fallthrough
|
||||
case dnsfilter.FilteredInvalid:
|
||||
fallthrough
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -59,10 +59,10 @@ func (s *session) deserialize(data []byte) bool {
|
||||
// Auth - global object
|
||||
type Auth struct {
|
||||
db *bbolt.DB
|
||||
sessions map[string]*session
|
||||
users []User
|
||||
sessions map[string]*session // session name -> session data
|
||||
lock sync.Mutex
|
||||
sessionTTL uint32
|
||||
users []User
|
||||
sessionTTL uint32 // in seconds
|
||||
}
|
||||
|
||||
// User object
|
||||
@@ -223,35 +223,24 @@ func (a *Auth) removeSession(sess []byte) {
|
||||
log.Debug("Auth: removed session from DB")
|
||||
}
|
||||
|
||||
// checkSessionResult is the result of checking a session.
|
||||
type checkSessionResult 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) {
|
||||
// CheckSession - check if session is valid
|
||||
// Return 0 if OK; -1 if session doesn't exist; 1 if session has expired
|
||||
func (a *Auth) CheckSession(sess string) int {
|
||||
now := uint32(time.Now().UTC().Unix())
|
||||
update := false
|
||||
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
s, ok := a.sessions[sess]
|
||||
if !ok {
|
||||
return checkSessionNotFound
|
||||
a.lock.Unlock()
|
||||
return -1
|
||||
}
|
||||
|
||||
if s.expire <= now {
|
||||
delete(a.sessions, sess)
|
||||
key, _ := hex.DecodeString(sess)
|
||||
a.removeSession(key)
|
||||
|
||||
return checkSessionExpired
|
||||
a.lock.Unlock()
|
||||
return 1
|
||||
}
|
||||
|
||||
newExpire := now + a.sessionTTL
|
||||
@@ -261,6 +250,8 @@ func (a *Auth) checkSession(sess string) (res checkSessionResult) {
|
||||
s.expire = newExpire
|
||||
}
|
||||
|
||||
a.lock.Unlock()
|
||||
|
||||
if update {
|
||||
key, _ := hex.DecodeString(sess)
|
||||
if a.storeSession(key, s) {
|
||||
@@ -268,7 +259,7 @@ func (a *Auth) checkSession(sess string) (res checkSessionResult) {
|
||||
}
|
||||
}
|
||||
|
||||
return checkSessionOK
|
||||
return 0
|
||||
}
|
||||
|
||||
// RemoveSession - remove session
|
||||
@@ -401,8 +392,8 @@ func optionalAuthThird(w http.ResponseWriter, r *http.Request) (authFirst bool)
|
||||
ok = true
|
||||
|
||||
} else if err == nil {
|
||||
r := Context.auth.checkSession(cookie.Value)
|
||||
if r == checkSessionOK {
|
||||
r := Context.auth.CheckSession(cookie.Value)
|
||||
if r == 0 {
|
||||
ok = true
|
||||
} else if r < 0 {
|
||||
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()
|
||||
cookie, err := r.Cookie(sessionCookieName)
|
||||
if authRequired && err == nil {
|
||||
r := Context.auth.checkSession(cookie.Value)
|
||||
if r == checkSessionOK {
|
||||
r := Context.auth.CheckSession(cookie.Value)
|
||||
if r == 0 {
|
||||
w.Header().Set("Location", "/")
|
||||
w.WriteHeader(http.StatusFound)
|
||||
|
||||
return
|
||||
} else if r == checkSessionNotFound {
|
||||
} else if r < 0 {
|
||||
log.Debug("Auth: invalid cookie value: %s", cookie)
|
||||
}
|
||||
}
|
||||
@@ -513,34 +503,32 @@ func (a *Auth) UserFind(login, password string) User {
|
||||
return User{}
|
||||
}
|
||||
|
||||
// getCurrentUser returns the current user. It returns an empty User if the
|
||||
// user is not found.
|
||||
func (a *Auth) getCurrentUser(r *http.Request) User {
|
||||
// GetCurrentUser - get the current user
|
||||
func (a *Auth) GetCurrentUser(r *http.Request) User {
|
||||
cookie, err := r.Cookie(sessionCookieName)
|
||||
if err != nil {
|
||||
// There's no Cookie, check Basic authentication.
|
||||
// there's no Cookie, check Basic authentication
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if ok {
|
||||
return Context.auth.UserFind(user, pass)
|
||||
u := Context.auth.UserFind(user, pass)
|
||||
return u
|
||||
}
|
||||
|
||||
return User{}
|
||||
}
|
||||
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
s, ok := a.sessions[cookie.Value]
|
||||
if !ok {
|
||||
a.lock.Unlock()
|
||||
return User{}
|
||||
}
|
||||
|
||||
for _, u := range a.users {
|
||||
if u.Name == s.userName {
|
||||
a.lock.Unlock()
|
||||
return u
|
||||
}
|
||||
}
|
||||
|
||||
a.lock.Unlock()
|
||||
return User{}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestAuth(t *testing.T) {
|
||||
user := User{Name: "name"}
|
||||
a.UserAdd(&user, "password")
|
||||
|
||||
assert.Equal(t, checkSessionNotFound, a.checkSession("notfound"))
|
||||
assert.True(t, a.CheckSession("notfound") == -1)
|
||||
a.RemoveSession("notfound")
|
||||
|
||||
sess, err := getSession(&users[0])
|
||||
@@ -49,13 +49,13 @@ func TestAuth(t *testing.T) {
|
||||
// check expiration
|
||||
s.expire = uint32(now)
|
||||
a.addSession(sess, &s)
|
||||
assert.Equal(t, checkSessionExpired, a.checkSession(sessStr))
|
||||
assert.True(t, a.CheckSession(sessStr) == 1)
|
||||
|
||||
// add session with TTL = 2 sec
|
||||
s = session{}
|
||||
s.expire = uint32(time.Now().UTC().Unix() + 2)
|
||||
a.addSession(sess, &s)
|
||||
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
|
||||
assert.True(t, a.CheckSession(sessStr) == 0)
|
||||
|
||||
a.Close()
|
||||
|
||||
@@ -63,8 +63,8 @@ func TestAuth(t *testing.T) {
|
||||
a = InitAuth(fn, users, 60)
|
||||
|
||||
// the session is still alive
|
||||
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
|
||||
// reset our expiration time because checkSession() has just updated it
|
||||
assert.True(t, a.CheckSession(sessStr) == 0)
|
||||
// reset our expiration time because CheckSession() has just updated it
|
||||
s.expire = uint32(time.Now().UTC().Unix() + 2)
|
||||
a.storeSession(sess, &s)
|
||||
a.Close()
|
||||
@@ -76,7 +76,7 @@ func TestAuth(t *testing.T) {
|
||||
|
||||
// load and remove expired sessions
|
||||
a = InitAuth(fn, users, 60)
|
||||
assert.Equal(t, checkSessionNotFound, a.checkSession(sessStr))
|
||||
assert.True(t, a.CheckSession(sessStr) == -1)
|
||||
|
||||
a.Close()
|
||||
os.Remove(fn)
|
||||
@@ -111,7 +111,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||
Context.auth = InitAuth(fn, users, 60)
|
||||
|
||||
handlerCalled := false
|
||||
handler := func(_ http.ResponseWriter, _ *http.Request) {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
handlerCalled = true
|
||||
}
|
||||
handler2 := optionalAuth(handler)
|
||||
|
||||
@@ -89,7 +89,7 @@ type profileJSON struct {
|
||||
|
||||
func handleGetProfile(w http.ResponseWriter, r *http.Request) {
|
||||
pj := profileJSON{}
|
||||
u := Context.auth.getCurrentUser(r)
|
||||
u := Context.auth.GetCurrentUser(r)
|
||||
pj.Name = u.Name
|
||||
|
||||
data, err := json.Marshal(pj)
|
||||
|
||||
@@ -346,25 +346,10 @@ func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request
|
||||
enableFilters(true)
|
||||
}
|
||||
|
||||
type checkHostRespRule struct {
|
||||
FilterListID int64 `json:"filter_list_id"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type checkHostResp struct {
|
||||
Reason string `json:"reason"`
|
||||
|
||||
// FilterID is the ID of the rule's filter list.
|
||||
//
|
||||
// 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"`
|
||||
Reason string `json:"reason"`
|
||||
FilterID int64 `json:"filter_id"`
|
||||
Rule string `json:"rule"`
|
||||
|
||||
// for FilteredBlockedService:
|
||||
SvcName string `json:"service_name"`
|
||||
@@ -389,23 +374,11 @@ func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
resp := checkHostResp{}
|
||||
resp.Reason = result.Reason.String()
|
||||
resp.FilterID = result.FilterID
|
||||
resp.Rule = result.Rule
|
||||
resp.SvcName = result.ServiceName
|
||||
resp.CanonName = result.CanonName
|
||||
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)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
|
||||
|
||||
@@ -58,7 +58,7 @@ type homeContext struct {
|
||||
dnsServer *dnsforward.Server // DNS module
|
||||
rdns *RDNS // rDNS module
|
||||
whois *Whois // WHOIS module
|
||||
dnsFilter *dnsfilter.DNSFilter // DNS filtering module
|
||||
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
|
||||
dhcpServer *dhcpd.Server // DHCP module
|
||||
auth *Auth // HTTP authentication module
|
||||
filters Filtering // DNS filtering module
|
||||
|
||||
@@ -4,13 +4,11 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -87,6 +85,54 @@ var logEntryHandlers = map[string]logEntryHandler{
|
||||
ent.OrigAnswer, err = base64.StdEncoding.DecodeString(v)
|
||||
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 {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
@@ -107,411 +153,42 @@ var logEntryHandlers = map[string]logEntryHandler{
|
||||
ent.Elapsed = time.Duration(i)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
"Result": func(json.Token, *logEntry) error {
|
||||
return nil
|
||||
},
|
||||
"Rule": func(t json.Token, ent *logEntry) error {
|
||||
s, ok := t.(string)
|
||||
"Question": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
l := len(ent.Result.Rules)
|
||||
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()
|
||||
var qstr []byte
|
||||
qstr, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l := len(ent.Result.Rules)
|
||||
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()
|
||||
q := new(dns.Msg)
|
||||
err = q.Unpack(qstr)
|
||||
if err != nil {
|
||||
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
|
||||
},
|
||||
"ServiceName": func(t json.Token, ent *logEntry) error {
|
||||
s, ok := t.(string)
|
||||
"Time": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
ent.Result.ServiceName = s
|
||||
|
||||
return nil
|
||||
var err error
|
||||
ent.Time, err = time.Parse(time.RFC3339, v)
|
||||
return err
|
||||
},
|
||||
"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) {
|
||||
@@ -523,27 +200,18 @@ func decodeLogEntry(ent *logEntry, str string) {
|
||||
if err != io.EOF {
|
||||
log.Debug("decodeLogEntry err: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := keyToken.(json.Delim); ok {
|
||||
continue
|
||||
}
|
||||
|
||||
key, ok := keyToken.(string)
|
||||
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
|
||||
}
|
||||
|
||||
if key == "Result" {
|
||||
decodeResult(dec, ent)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
handler, ok := logEntryHandlers[key]
|
||||
if !ok {
|
||||
continue
|
||||
@@ -555,8 +223,7 @@ func decodeLogEntry(ent *logEntry, str string) {
|
||||
}
|
||||
|
||||
if err = handler(val, ent); err != nil {
|
||||
log.Debug("decodeLogEntry handler err: %s", err)
|
||||
|
||||
log.Debug("decodeLogEntry err: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,181 +2,107 @@ package querylog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecodeLogEntry(t *testing.T) {
|
||||
func TestDecode_decodeQueryLog(t *testing.T) {
|
||||
logOutput := &bytes.Buffer{}
|
||||
|
||||
testutil.ReplaceLogWriter(t, logOutput)
|
||||
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 {
|
||||
name string
|
||||
log string
|
||||
want string
|
||||
}{{
|
||||
name: "all_right_old_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":"||an.yandex.","FilterID":1,"ReverseHosts":["example.com"],"IPList":["127.0.0.1"]},"Elapsed":837429}`,
|
||||
want: "",
|
||||
name: "back_compatibility_all_right",
|
||||
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: "default",
|
||||
}, {
|
||||
name: "bad_filter_id_old_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,"FilterID":1.5},"Elapsed":837429}`,
|
||||
want: "decodeResult handler err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n",
|
||||
name: "back_compatibility_bad_msg",
|
||||
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: "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",
|
||||
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}`,
|
||||
want: "decodeLogEntry err: invalid character 'o' in literal true (expecting 'u')\n",
|
||||
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: "default",
|
||||
}, {
|
||||
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}`,
|
||||
want: "",
|
||||
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: "default",
|
||||
}, {
|
||||
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}`,
|
||||
want: "",
|
||||
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: "default",
|
||||
}, {
|
||||
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}`,
|
||||
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",
|
||||
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 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",
|
||||
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}`,
|
||||
want: "",
|
||||
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: "default",
|
||||
}, {
|
||||
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}`,
|
||||
want: "",
|
||||
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: "default",
|
||||
}, {
|
||||
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}`,
|
||||
want: "",
|
||||
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: "default",
|
||||
}, {
|
||||
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}`,
|
||||
want: "",
|
||||
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: "default",
|
||||
}, {
|
||||
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}`,
|
||||
want: "decodeLogEntry handler err: invalid client proto: \"dog\"\n",
|
||||
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 err: invalid client proto: \"dog\"\n",
|
||||
}, {
|
||||
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}`,
|
||||
want: "",
|
||||
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: "default",
|
||||
}, {
|
||||
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}`,
|
||||
want: "decodeLogEntry handler err: illegal base64 data at input byte 61\n",
|
||||
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 err: illegal base64 data at input byte 61\n",
|
||||
}, {
|
||||
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}`,
|
||||
want: "",
|
||||
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: "default",
|
||||
}, {
|
||||
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}`,
|
||||
want: "",
|
||||
}, {
|
||||
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",
|
||||
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: "default",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := logOutput.Write([]byte("default"))
|
||||
assert.Nil(t, err)
|
||||
|
||||
l := &logEntry{}
|
||||
decodeLogEntry(l, tc.log)
|
||||
|
||||
s := logOutput.String()
|
||||
if tc.want == "" {
|
||||
assert.Equal(t, "", s)
|
||||
} else {
|
||||
assert.True(t, strings.HasSuffix(s, tc.want),
|
||||
"got %q", s)
|
||||
}
|
||||
assert.True(t, strings.HasSuffix(logOutput.String(), tc.want), logOutput.String())
|
||||
|
||||
logOutput.Reset()
|
||||
})
|
||||
|
||||
@@ -6,13 +6,10 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Use a proper structured approach here.
|
||||
|
||||
// Get Client IP address
|
||||
func (l *queryLog) getClientIP(clientIP string) string {
|
||||
if l.conf.AnonymizeClientIP {
|
||||
@@ -32,12 +29,10 @@ func (l *queryLog) getClientIP(clientIP string) string {
|
||||
return clientIP
|
||||
}
|
||||
|
||||
// jobject is a JSON object alias.
|
||||
type jobject = map[string]interface{}
|
||||
|
||||
// entriesToJSON converts query log entries to JSON.
|
||||
func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) (res jobject) {
|
||||
data := []jobject{}
|
||||
// entriesToJSON - converts log entries to JSON
|
||||
func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[string]interface{} {
|
||||
// init the response object
|
||||
data := []map[string]interface{}{}
|
||||
|
||||
// the elements order is already reversed (from newer to older)
|
||||
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)
|
||||
}
|
||||
|
||||
res = jobject{
|
||||
"data": data,
|
||||
"oldest": "",
|
||||
}
|
||||
result := map[string]interface{}{}
|
||||
result["oldest"] = ""
|
||||
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
|
||||
|
||||
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(),
|
||||
"elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64),
|
||||
"time": entry.Time.Format(time.RFC3339Nano),
|
||||
"client": l.getClientIP(entry.IP),
|
||||
"client_proto": entry.ClientProto,
|
||||
"upstream": entry.Upstream,
|
||||
"question": jobject{
|
||||
"host": entry.QHost,
|
||||
"type": entry.QType,
|
||||
"class": entry.QClass,
|
||||
},
|
||||
}
|
||||
jsonEntry["question"] = map[string]interface{}{
|
||||
"host": entry.QHost,
|
||||
"type": entry.QType,
|
||||
"class": entry.QClass,
|
||||
}
|
||||
|
||||
if msg != nil {
|
||||
@@ -90,15 +83,12 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) (jsonEntry jobject) {
|
||||
if opt != nil {
|
||||
dnssecOk = opt.Do()
|
||||
}
|
||||
|
||||
jsonEntry["answer_dnssec"] = dnssecOk
|
||||
}
|
||||
|
||||
jsonEntry["rules"] = resultRulesToJSONRules(entry.Result.Rules)
|
||||
|
||||
if len(entry.Result.Rules) > 0 && len(entry.Result.Rules[0].Text) > 0 {
|
||||
jsonEntry["rule"] = entry.Result.Rules[0].Text
|
||||
jsonEntry["filterId"] = entry.Result.Rules[0].FilterListID
|
||||
if len(entry.Result.Rule) > 0 {
|
||||
jsonEntry["rule"] = entry.Result.Rule
|
||||
jsonEntry["filterId"] = entry.Result.FilterID
|
||||
}
|
||||
|
||||
if len(entry.Result.ServiceName) != 0 {
|
||||
@@ -123,30 +113,20 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) (jsonEntry jobject) {
|
||||
}
|
||||
}
|
||||
|
||||
jsonEntry["upstream"] = entry.Upstream
|
||||
|
||||
return jsonEntry
|
||||
}
|
||||
|
||||
func resultRulesToJSONRules(rules []*dnsfilter.ResultRule) (jsonRules []jobject) {
|
||||
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) {
|
||||
func answerToMap(a *dns.Msg) []map[string]interface{} {
|
||||
if a == nil || len(a.Answer) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
answers = []jobject{}
|
||||
answers := []map[string]interface{}{}
|
||||
for _, k := range a.Answer {
|
||||
header := k.Header()
|
||||
answer := jobject{
|
||||
answer := map[string]interface{}{
|
||||
"type": dns.TypeToString[header.Rrtype],
|
||||
"ttl": header.Ttl,
|
||||
}
|
||||
|
||||
@@ -236,12 +236,10 @@ func addEntry(l *queryLog, host, answerStr, client string) {
|
||||
a.Answer = append(a.Answer, answer)
|
||||
res := dnsfilter.Result{
|
||||
IsFiltered: true,
|
||||
Rule: "SomeRule",
|
||||
Reason: dnsfilter.ReasonRewrite,
|
||||
ServiceName: "SomeService",
|
||||
Rules: []*dnsfilter.ResultRule{{
|
||||
FilterListID: 1,
|
||||
Text: "SomeRule",
|
||||
}},
|
||||
FilterID: 1,
|
||||
}
|
||||
params := AddParams{
|
||||
Question: &q,
|
||||
|
||||
@@ -115,14 +115,14 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool {
|
||||
case filteringStatusFiltered:
|
||||
return res.IsFiltered ||
|
||||
res.Reason.In(
|
||||
dnsfilter.NotFilteredAllowList,
|
||||
dnsfilter.NotFilteredWhiteList,
|
||||
dnsfilter.ReasonRewrite,
|
||||
dnsfilter.RewriteAutoHosts,
|
||||
dnsfilter.RewriteEtcHosts,
|
||||
)
|
||||
|
||||
case filteringStatusBlocked:
|
||||
return res.IsFiltered &&
|
||||
res.Reason.In(dnsfilter.FilteredBlockList, dnsfilter.FilteredBlockedService)
|
||||
res.Reason.In(dnsfilter.FilteredBlackList, dnsfilter.FilteredBlockedService)
|
||||
|
||||
case filteringStatusBlockedService:
|
||||
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
|
||||
|
||||
case filteringStatusWhitelisted:
|
||||
return res.Reason == dnsfilter.NotFilteredAllowList
|
||||
return res.Reason == dnsfilter.NotFilteredWhiteList
|
||||
|
||||
case filteringStatusRewritten:
|
||||
return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteAutoHosts)
|
||||
return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteEtcHosts)
|
||||
|
||||
case filteringStatusSafeSearch:
|
||||
return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeSearch
|
||||
|
||||
case filteringStatusProcessed:
|
||||
return !res.Reason.In(
|
||||
dnsfilter.FilteredBlockList,
|
||||
dnsfilter.FilteredBlackList,
|
||||
dnsfilter.FilteredBlockedService,
|
||||
dnsfilter.NotFilteredAllowList,
|
||||
dnsfilter.NotFilteredWhiteList,
|
||||
)
|
||||
|
||||
default:
|
||||
|
||||
@@ -18,7 +18,7 @@ require (
|
||||
golang.org/x/mod v0.4.0 // indirect
|
||||
golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7
|
||||
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/unparam v0.0.0-20200501210554-b37ab49443f7
|
||||
)
|
||||
|
||||
@@ -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-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-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-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
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=
|
||||
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.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/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0=
|
||||
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY=
|
||||
|
||||
@@ -1,67 +1,5 @@
|
||||
# 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
|
||||
|
||||
### API: replace settings in GET /control/dns_info & POST /control/dns_config
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
'info':
|
||||
'title': 'AdGuard Home'
|
||||
'description': >
|
||||
AdGuard Home REST-ish API. Our admin web interface is built on top of this
|
||||
REST-ish API.
|
||||
'version': '0.105'
|
||||
AdGuard Home REST API. Our admin web interface is built on top of this REST
|
||||
API.
|
||||
'version': '0.104'
|
||||
'contact':
|
||||
'name': 'AdGuard Home'
|
||||
'url': 'https://github.com/AdguardTeam/AdGuardHome'
|
||||
@@ -523,7 +523,7 @@
|
||||
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.
|
||||
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
|
||||
last-resort attempt to make filter lists fresh. If you ever find
|
||||
yourself using `force` to make something work that otherwise wont, this
|
||||
@@ -1246,7 +1246,7 @@
|
||||
'properties':
|
||||
'reason':
|
||||
'type': 'string'
|
||||
'description': 'Request filtering status.'
|
||||
'description': 'DNS filter status'
|
||||
'enum':
|
||||
- 'NotFilteredNotFound'
|
||||
- 'NotFilteredWhiteList'
|
||||
@@ -1257,41 +1257,24 @@
|
||||
- 'FilteredInvalid'
|
||||
- 'FilteredSafeSearch'
|
||||
- 'FilteredBlockedService'
|
||||
- 'Rewrite'
|
||||
- 'RewriteEtcHosts'
|
||||
- 'DNSRewriteRule'
|
||||
- 'ReasonRewrite'
|
||||
'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'
|
||||
'rule':
|
||||
'deprecated': true
|
||||
'type': 'string'
|
||||
'example': '||example.org^'
|
||||
'description': >
|
||||
Filtering rule applied to the request (if any).
|
||||
|
||||
Deprecated: use `rules[*].text` instead.
|
||||
'rules':
|
||||
'description': 'Applied rules.'
|
||||
'type': 'array'
|
||||
'items':
|
||||
'$ref': '#/components/schemas/ResultRule'
|
||||
'description': 'Filtering rule applied to the request (if any)'
|
||||
'service_name':
|
||||
'type': 'string'
|
||||
'description': 'Set if reason=FilteredBlockedService'
|
||||
'cname':
|
||||
'type': 'string'
|
||||
'description': 'Set if reason=Rewrite'
|
||||
'description': 'Set if reason=ReasonRewrite'
|
||||
'ip_addrs':
|
||||
'type': 'array'
|
||||
'items':
|
||||
'type': 'string'
|
||||
'description': 'Set if reason=Rewrite'
|
||||
'description': 'Set if reason=ReasonRewrite'
|
||||
'FilterRefreshResponse':
|
||||
'type': 'object'
|
||||
'description': '/filtering/refresh response data'
|
||||
@@ -1627,30 +1610,18 @@
|
||||
'question':
|
||||
'$ref': '#/components/schemas/DnsQuestion'
|
||||
'filterId':
|
||||
'deprecated': true
|
||||
'type': 'integer'
|
||||
'example': 123123
|
||||
'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.
|
||||
the filter that rule belongs to.
|
||||
'rule':
|
||||
'deprecated': true
|
||||
'type': 'string'
|
||||
'example': '||example.org^'
|
||||
'description': >
|
||||
Filtering rule applied to the request (if any).
|
||||
|
||||
Deprecated: use `rules[*].text` instead.
|
||||
'rules':
|
||||
'description': 'Applied rules.'
|
||||
'type': 'array'
|
||||
'items':
|
||||
'$ref': '#/components/schemas/ResultRule'
|
||||
'description': 'Filtering rule applied to the request (if any)'
|
||||
'reason':
|
||||
'type': 'string'
|
||||
'description': 'Request filtering status.'
|
||||
'description': 'DNS filter status'
|
||||
'enum':
|
||||
- 'NotFilteredNotFound'
|
||||
- 'NotFilteredWhiteList'
|
||||
@@ -1661,9 +1632,7 @@
|
||||
- 'FilteredInvalid'
|
||||
- 'FilteredSafeSearch'
|
||||
- 'FilteredBlockedService'
|
||||
- 'Rewrite'
|
||||
- 'RewriteEtcHosts'
|
||||
- 'DNSRewriteRule'
|
||||
- 'ReasonRewrite'
|
||||
'service_name':
|
||||
'type': 'string'
|
||||
'description': 'Set if reason=FilteredBlockedService'
|
||||
@@ -1699,22 +1668,6 @@
|
||||
'anonymize_client_ip':
|
||||
'type': 'boolean'
|
||||
'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':
|
||||
'type': 'object'
|
||||
'description': 'TLS configuration settings and status'
|
||||
|
||||
@@ -95,7 +95,7 @@ ineffassign .
|
||||
|
||||
unparam ./...
|
||||
|
||||
git ls-files -- '*.go' '*.md' '*.yaml' '*.yml' | xargs misspell --error
|
||||
misspell --error ./...
|
||||
|
||||
looppointer ./...
|
||||
|
||||
@@ -112,4 +112,4 @@ exit_on_output sh -c '
|
||||
{ grep -e "defer" -e "_test\.go:" -v || exit 0; }
|
||||
'
|
||||
|
||||
staticcheck ./...
|
||||
staticcheck --checks='all' ./...
|
||||
|
||||
@@ -190,7 +190,6 @@ main() {
|
||||
SCRIPT_URL="https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh"
|
||||
URL="https://static.adguard.com/adguardhome/${CHANNEL}/${PKG_NAME}"
|
||||
OUT_DIR=/opt
|
||||
AGH_DIR="${OUT_DIR}/AdGuardHome"
|
||||
|
||||
# Root check
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
@@ -209,22 +208,22 @@ main() {
|
||||
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"
|
||||
|
||||
unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package"
|
||||
|
||||
# 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}"
|
||||
|
||||
log_info "AdGuard Home is now installed and running."
|
||||
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 "$@"
|
||||
@@ -2,6 +2,11 @@ const fs = require('fs');
|
||||
const readline = require('readline');
|
||||
const dnsPacket = require('dns-packet')
|
||||
|
||||
const decodeBase64 = (data) => {
|
||||
let buff = new Buffer(data, 'base64');
|
||||
return buff.toString('ascii');
|
||||
}
|
||||
|
||||
const processLineByLine = async (source, callback) => {
|
||||
const fileStream = fs.createReadStream(source);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## Twosky integration script
|
||||
## Twosky intergration script
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
@@ -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 = []
|
||||
Reference in New Issue
Block a user