Compare commits
82 Commits
release-v0
...
v0.108.0-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89d9b03dfe | ||
|
|
f1d05a49f0 | ||
|
|
9a764b9b82 | ||
|
|
e0b557eda2 | ||
|
|
ea6e033dae | ||
|
|
3afe7c3daf | ||
|
|
afbc7a72e3 | ||
|
|
ff1e108bfe | ||
|
|
773b80a969 | ||
|
|
f131067278 | ||
|
|
b43aa86cae | ||
|
|
6824eec308 | ||
|
|
18079ca1bb | ||
|
|
a1f29c31b9 | ||
|
|
0ef8344178 | ||
|
|
f53f48cc33 | ||
|
|
2a5b5f1927 | ||
|
|
b290eddc70 | ||
|
|
6d0a43aad6 | ||
|
|
1bc2186c2d | ||
|
|
6584c300b8 | ||
|
|
dc480ae70f | ||
|
|
e783564084 | ||
|
|
0ee34534c6 | ||
|
|
9146df5493 | ||
|
|
76fa60498e | ||
|
|
8455940b59 | ||
|
|
2d46aa7121 | ||
|
|
bf9b35b9c6 | ||
|
|
f9aa5ae86a | ||
|
|
642d68c482 | ||
|
|
5ff7cdbac8 | ||
|
|
504c54ab0e | ||
|
|
90c17c79de | ||
|
|
0b72bcc5a1 | ||
|
|
dc14f89c9f | ||
|
|
2263adbbe0 | ||
|
|
e29261516f | ||
|
|
f12eaf29a2 | ||
|
|
3e2ab87293 | ||
|
|
41e8db4221 | ||
|
|
3f5605c42e | ||
|
|
f7ff02f3b1 | ||
|
|
5ec4a4dab8 | ||
|
|
13871977f9 | ||
|
|
2fdda8a22c | ||
|
|
d82b290251 | ||
|
|
eb15304ff4 | ||
|
|
1a3bf5ebda | ||
|
|
15956f4511 | ||
|
|
09d0ce4578 | ||
|
|
9735a35123 | ||
|
|
813a06d09a | ||
|
|
061136508e | ||
|
|
008f58c863 | ||
|
|
0e4ffd339f | ||
|
|
1458600c37 | ||
|
|
34c95f99f8 | ||
|
|
e9c59b098e | ||
|
|
a0bb5ce8a4 | ||
|
|
01947bedb4 | ||
|
|
a6ca824064 | ||
|
|
380cff07f2 | ||
|
|
d2ce06e1ca | ||
|
|
2ed1f939b5 | ||
|
|
dea8a585f8 | ||
|
|
313555b10c | ||
|
|
661f4ece48 | ||
|
|
52f36f201e | ||
|
|
46cd974e2a | ||
|
|
201ef10de6 | ||
|
|
d9df7c13be | ||
|
|
64e751e579 | ||
|
|
e6e5958595 | ||
|
|
ff3df0ec33 | ||
|
|
ebe86ce00e | ||
|
|
d317e19291 | ||
|
|
39c4999d2d | ||
|
|
7f55bd8461 | ||
|
|
2968a65f14 | ||
|
|
779fbe79b8 | ||
|
|
da0d1cb754 |
201
CHANGELOG.md
201
CHANGELOG.md
@@ -12,20 +12,11 @@ and this project adheres to
|
||||
## [Unreleased]
|
||||
|
||||
<!--
|
||||
## [v0.108.0] - 2022-07-01 (APPROX.)
|
||||
## [v0.108.0] - 2022-06-01 (APPROX.)
|
||||
-->
|
||||
|
||||
### Security
|
||||
|
||||
- Enforced password strength policy ([#3503]).
|
||||
- Weaker cipher suites that use the CBC (cipher block chaining) mode of
|
||||
operation have been disabled ([#2993]).
|
||||
|
||||
### Added
|
||||
|
||||
- The ability to customize the set of networks that are considered private
|
||||
through the new `dns.private_networks` property in the configuration file
|
||||
([#3142]).
|
||||
- EDNS Client-Subnet information in the request details section of a query log
|
||||
record ([#3978]).
|
||||
- Support for hostnames for plain UDP upstream servers using the `udp://` scheme
|
||||
@@ -36,14 +27,6 @@ and this project adheres to
|
||||
|
||||
### Changed
|
||||
|
||||
- The default DNS-over-QUIC port number is now `853` instead of `754` in
|
||||
accordance with the latest [RFC draft][doq-draft-10] ([#4276]).
|
||||
- Reverse DNS now has a greater priority as the source of runtime clients'
|
||||
information than ARP neighborhood.
|
||||
- Improved detection of runtime clients through more resilient ARP processing
|
||||
([#3597]).
|
||||
- The TTL of responses served from the optimistic cache is now lowered to 10
|
||||
seconds.
|
||||
- Domain-specific private reverse DNS upstream servers are now validated to
|
||||
allow only `*.in-addr.arpa` and `*.ip6.arpa` domains pointing to
|
||||
locally-served networks ([#3381]). **Note:** If you already have invalid
|
||||
@@ -56,16 +39,16 @@ and this project adheres to
|
||||
of the commit from which the binary was built ([#4221]). This should simplify
|
||||
reproducible builds for package maintainers and those who compile their own
|
||||
AdGuard Home.
|
||||
- The property `local_domain_name` is now in the `dhcp` object in the
|
||||
- The setting `local_domain_name` is now in the `dhcp` block in the
|
||||
configuration file to avoid confusion ([#3367]).
|
||||
- The `dns.bogus_nxdomain` property in the configuration file now supports CIDR
|
||||
- The `dns.bogus_nxdomain` configuration file parameter now supports CIDR
|
||||
notation alongside IP addresses ([#1730]).
|
||||
|
||||
#### Configuration Changes
|
||||
|
||||
In this release, the schema version has changed from 12 to 13.
|
||||
|
||||
- Property `local_domain_name`, which in schema versions 12 and earlier used to
|
||||
- Parameter `local_domain_name`, which in schema versions 12 and earlier used to
|
||||
be a part of the `dns` object, is now a part of the `dhcp` object:
|
||||
|
||||
```yaml
|
||||
@@ -80,119 +63,54 @@ In this release, the schema version has changed from 12 to 13.
|
||||
'local_domain_name': 'lan'
|
||||
```
|
||||
|
||||
To rollback this change, move the property back into the `dns` object and
|
||||
change the `schema_version` back to `12`.
|
||||
To rollback this change, move the parameter back into `dns` and change the
|
||||
`schema_version` back to `12`.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Go 1.17 support. v0.109.0 will require at least Go 1.18 to build.
|
||||
|
||||
### Removed
|
||||
|
||||
- Go 1.16 support.
|
||||
|
||||
### Security
|
||||
|
||||
- Enforced password strength policy ([#3503]).
|
||||
- Weaker cipher suites that use the CBC (cipher block chaining) mode of
|
||||
operation have been disabled ([#2993]).
|
||||
|
||||
[#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730
|
||||
[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
|
||||
[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
|
||||
[#3142]: https://github.com/AdguardTeam/AdGuardHome/issues/3142
|
||||
[#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
|
||||
[#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381
|
||||
[#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
|
||||
[#3597]: https://github.com/AdguardTeam/AdGuardHome/issues/3597
|
||||
[#3978]: https://github.com/AdguardTeam/AdGuardHome/issues/3978
|
||||
[#4166]: https://github.com/AdguardTeam/AdGuardHome/issues/4166
|
||||
[#4213]: https://github.com/AdguardTeam/AdGuardHome/issues/4213
|
||||
[#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
|
||||
[#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
|
||||
[#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
|
||||
[#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
|
||||
|
||||
[repr]: https://reproducible-builds.org/docs/source-date-epoch/
|
||||
[doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
|
||||
[repr]: https://reproducible-builds.org/docs/source-date-epoch/
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
## [v0.107.7] - 2022-05-18 (APPROX.)
|
||||
## [v0.107.5] - 2022-04-04 (APPROX.)
|
||||
|
||||
See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
|
||||
See also the [v0.107.5 GitHub milestone][ms-v0.107.5].
|
||||
|
||||
[ms-v0.107.7]: https://github.com/AdguardTeam/AdGuardHome/milestone/43?closed=1
|
||||
[ms-v0.107.5]: https://github.com/AdguardTeam/AdGuardHome/milestone/42?closed=1
|
||||
-->
|
||||
|
||||
|
||||
|
||||
## [v0.107.6] - 2022-04-13
|
||||
|
||||
See also the [v0.107.6 GitHub milestone][ms-v0.107.6].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] vulnerabilities.
|
||||
|
||||
### Added
|
||||
|
||||
- Support for SVCB/HTTPS parameter `dohpath` in filtering rules with
|
||||
the `dnsrewrite` modifier according to the [RFC draft][dns-draft-02]
|
||||
([#4463]).
|
||||
|
||||
### Changed
|
||||
|
||||
- Filtering rules with the `dnsrewrite` modifier that create SVCB or HTTPS
|
||||
responses should use `ech` instead of `echconfig` to conform with the [latest
|
||||
drafts][svcb-draft-08].
|
||||
|
||||
### Deprecated
|
||||
|
||||
- SVCB/HTTPS parameter name `echconfig` in filtering rules with the `dnsrewrite`
|
||||
modifier. Use `ech` instead. v0.109.0 will remove support for the outdated
|
||||
name `echconfig`.
|
||||
- Obsolete `--no-mem-optimization` option ([#4437]). v0.109.0 will remove the
|
||||
flag completely.
|
||||
|
||||
### Fixed
|
||||
|
||||
- I/O timeout errors when checking the presence of another DHCP server.
|
||||
- Network interfaces being incorrectly labeled as down during installation.
|
||||
- Rules for blocking the QQ service ([#3171]).
|
||||
|
||||
### Removed
|
||||
|
||||
- Go 1.16 support, since that branch of the Go compiler has reached end of life
|
||||
and doesn't receive security updates anymore.
|
||||
|
||||
[#3171]: https://github.com/AdguardTeam/AdGuardHome/issues/3171
|
||||
[#4437]: https://github.com/AdguardTeam/AdGuardHome/issues/4437
|
||||
[#4463]: https://github.com/AdguardTeam/AdGuardHome/issues/4463
|
||||
|
||||
[CVE-2022-24675]: https://www.cvedetails.com/cve/CVE-2022-24675
|
||||
[CVE-2022-27536]: https://www.cvedetails.com/cve/CVE-2022-27536
|
||||
[CVE-2022-28327]: https://www.cvedetails.com/cve/CVE-2022-28327
|
||||
[dns-draft-02]: https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02#section-5.1
|
||||
[ms-v0.107.6]: https://github.com/AdguardTeam/AdGuardHome/milestone/42?closed=1
|
||||
[svcb-draft-08]: https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-08.html
|
||||
|
||||
|
||||
|
||||
## [v0.107.5] - 2022-03-04
|
||||
|
||||
This is a security update. There is no GitHub milestone, since no GitHub issues
|
||||
were resolved.
|
||||
|
||||
### Security
|
||||
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-24921] vulnerability.
|
||||
|
||||
[CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921
|
||||
|
||||
|
||||
|
||||
## [v0.107.4] - 2022-03-01
|
||||
|
||||
See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] vulnerabilities.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Optimistic cache now responds with expired items even if those can't be
|
||||
@@ -200,6 +118,11 @@ See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
|
||||
- Unnecessarily complex hosts-related logic leading to infinite recursion in
|
||||
some cases ([#4216]).
|
||||
|
||||
### Security
|
||||
|
||||
- Go version was updated to prevent the possibility of exploiting
|
||||
[CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773].
|
||||
|
||||
[#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
|
||||
[#4254]: https://github.com/AdguardTeam/AdGuardHome/issues/4254
|
||||
|
||||
@@ -216,7 +139,7 @@ See also the [v0.107.3 GitHub milestone][ms-v0.107.3].
|
||||
|
||||
### Added
|
||||
|
||||
- Support for a `dnsrewrite` modifier with an empty `NOERROR` response
|
||||
- Support for a `$dnsrewrite` modifier with an empty `NOERROR` response
|
||||
([#4133]).
|
||||
|
||||
### Fixed
|
||||
@@ -297,15 +220,15 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
|
||||
through the new `fastest_timeout` field in the configuration file ([#1992]).
|
||||
- Static IP address detection on FreeBSD ([#3289]).
|
||||
- Optimistic cache ([#2145]).
|
||||
- New possible value of `6h` for `querylog_interval` property ([#2504]).
|
||||
- New possible value of `6h` for `querylog_interval` setting ([#2504]).
|
||||
- Blocking access using ClientIDs ([#2624], [#3162]).
|
||||
- `source` directives support in `/etc/network/interfaces` on Linux ([#3257]).
|
||||
- [RFC 9000][rfc-9000] support in QUIC.
|
||||
- RFC 9000 support in DNS-over-QUIC.
|
||||
- Completely disabling statistics by setting the statistics interval to zero
|
||||
([#2141]).
|
||||
- The ability to completely purge DHCP leases ([#1691]).
|
||||
- Settable timeouts for querying the upstream servers ([#2280]).
|
||||
- Configuration file properties to change group and user ID on startup on Unix
|
||||
- Configuration file parameters to change group and user ID on startup on Unix
|
||||
([#2763]).
|
||||
- Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439], [#3225],
|
||||
[#3226]).
|
||||
@@ -332,7 +255,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
|
||||
- Better error message for ED25519 private keys, which are not widely supported
|
||||
([#3737]).
|
||||
- Cache now follows RFC more closely for negative answers ([#3707]).
|
||||
- `dnsrewrite` rules and other DNS rewrites will now be applied even when the
|
||||
- `$dnsrewrite` rules and other DNS rewrites will now be applied even when the
|
||||
protection is disabled ([#1558]).
|
||||
- DHCP gateway address, subnet mask, IP address range, and leases validations
|
||||
([#3529]).
|
||||
@@ -348,22 +271,22 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
|
||||
proxy ([#2799]).
|
||||
- Clients who are blocked by access settings now receive a `REFUSED` response
|
||||
when a protocol other than DNS-over-UDP and DNSCrypt is used.
|
||||
- `dns.querylog_interval` property is now formatted in hours.
|
||||
- `querylog_interval` setting is now formatted in hours.
|
||||
- Query log search now supports internationalized domains ([#3012]).
|
||||
- Internationalized domains are now shown decoded in the query log with the
|
||||
original encoded version shown in request details ([#3013]).
|
||||
- When /etc/hosts-type rules have several IPs for one host, all IPs are now
|
||||
returned instead of only the first one ([#1381]).
|
||||
- Property `rlimit_nofile` is now in the `os` object of the configuration
|
||||
file, together with the new `group` and `user` properties ([#2763]).
|
||||
- The setting `rlimit_nofile` is now in the `os` block of the configuration
|
||||
file, together with the new `group` and `user` settings ([#2763]).
|
||||
- Permissions on filter files are now `0o644` instead of `0o600` ([#3198]).
|
||||
|
||||
#### Configuration Changes
|
||||
|
||||
In this release, the schema version has changed from 10 to 12.
|
||||
|
||||
- Property `dns.querylog_interval`, which in schema versions 11 and earlier used
|
||||
to be an integer number of days, is now a string with a human-readable
|
||||
- Parameter `dns.querylog_interval`, which in schema versions 11 and earlier
|
||||
used to be an integer number of days, is now a string with a human-readable
|
||||
duration:
|
||||
|
||||
```yaml
|
||||
@@ -378,10 +301,10 @@ In this release, the schema version has changed from 10 to 12.
|
||||
'querylog_interval': '2160h'
|
||||
```
|
||||
|
||||
To rollback this change, convert the property back into days and change the
|
||||
To rollback this change, convert the parameter back into days and change the
|
||||
`schema_version` back to `11`.
|
||||
|
||||
- Property `rlimit_nofile`, which in schema versions 10 and earlier used to be
|
||||
- Parameter `rlimit_nofile`, which in schema versions 10 and earlier used to be
|
||||
on the top level, is now moved to the new `os` object:
|
||||
|
||||
```yaml
|
||||
@@ -395,7 +318,7 @@ In this release, the schema version has changed from 10 to 12.
|
||||
'user': ''
|
||||
```
|
||||
|
||||
To rollback this change, move the property on the top level and change the
|
||||
To rollback this change, move the parameter on the top level and change the
|
||||
`schema_version` back to `10`.
|
||||
|
||||
### Deprecated
|
||||
@@ -405,7 +328,7 @@ In this release, the schema version has changed from 10 to 12.
|
||||
### Fixed
|
||||
|
||||
- EDNS0 TCP keepalive option handling ([#3778]).
|
||||
- Rules with the `denyallow` modifier applying to IP addresses when they
|
||||
- Rules with the `$denyallow` modifier applying to IP addresses when they
|
||||
shouldn't ([#3175]).
|
||||
- The length of the EDNS0 client subnet option appearing too long for some
|
||||
upstream servers ([#3887]).
|
||||
@@ -413,8 +336,8 @@ In this release, the schema version has changed from 10 to 12.
|
||||
settings ([#3558]).
|
||||
- Incomplete propagation of the client's IP anonymization setting to the
|
||||
statistics ([#3890]).
|
||||
- Incorrect results with the `dnsrewrite` modifier for entries from the
|
||||
operating system's hosts file ([#3815]).
|
||||
- Incorrect `$dnsrewrite` results for entries from the operating system's hosts
|
||||
file ([#3815]).
|
||||
- Matching against rules with `|` at the end of the domain name ([#3371]).
|
||||
- Incorrect assignment of explicitly configured DHCP options ([#3744]).
|
||||
- Occasional panic during shutdown ([#3655]).
|
||||
@@ -441,8 +364,8 @@ In this release, the schema version has changed from 10 to 12.
|
||||
- Letter case mismatches in `CNAME` filtering ([#3335]).
|
||||
- Occasional breakages on network errors with DNS-over-HTTP upstreams ([#3217]).
|
||||
- Errors when setting static IP on Linux ([#3257]).
|
||||
- Treatment of domain names and FQDNs in custom rules with the `dnsrewrite`
|
||||
modifier that use the `PTR` type ([#3256]).
|
||||
- Treatment of domain names and FQDNs in custom rules with `$dnsrewrite` that
|
||||
use the `PTR` type ([#3256]).
|
||||
- Redundant hostname generating while loading static leases with empty hostname
|
||||
([#3166]).
|
||||
- Domain name case in responses ([#3194]).
|
||||
@@ -525,7 +448,6 @@ In this release, the schema version has changed from 10 to 12.
|
||||
[#3933]: https://github.com/AdguardTeam/AdGuardHome/pull/3933
|
||||
|
||||
[ms-v0.107.0]: https://github.com/AdguardTeam/AdGuardHome/milestone/23?closed=1
|
||||
[rfc-9000]: https://datatracker.ietf.org/doc/html/rfc9000
|
||||
|
||||
|
||||
|
||||
@@ -606,7 +528,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
|
||||
|
||||
- The ability to block user for login after configurable number of unsuccessful
|
||||
attempts for configurable time ([#2826]).
|
||||
- `denyallow` modifier for filters ([#2923]).
|
||||
- `$denyallow` modifier for filters ([#2923]).
|
||||
- Hostname uniqueness validation in the DHCP server ([#2952]).
|
||||
- Hostname generating for DHCP clients which don't provide their own ([#2723]).
|
||||
- New flag `--no-etc-hosts` to disable client domain name lookups in the
|
||||
@@ -621,8 +543,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
|
||||
network ([#2393], [#2961]).
|
||||
- The ability to serve DNS queries on multiple hosts and interfaces ([#1401]).
|
||||
- `ips` and `text` DHCP server options ([#2385]).
|
||||
- `SRV` records support in filtering rules with the `dnsrewrite` modifier
|
||||
([#2533]).
|
||||
- `SRV` records support in `$dnsrewrite` filters ([#2533]).
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -636,8 +557,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
|
||||
([#2704]).
|
||||
- Stricter validation of the IP addresses of static leases in the DHCP server
|
||||
with regards to the netmask ([#2838]).
|
||||
- Stricter validation of `dnsrewrite` filtering rule modifier parameters
|
||||
([#2498]).
|
||||
- Stricter validation of `$dnsrewrite` filter modifier parameters ([#2498]).
|
||||
- New, more correct versioning scheme ([#2412]).
|
||||
|
||||
### Deprecated
|
||||
@@ -646,7 +566,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Multiple answers for a `dnsrewrite` rule matching requests with repeating
|
||||
- Multiple answers for `$dnsrewrite` rule matching requests with repeating
|
||||
patterns in it ([#2981]).
|
||||
- Root server resolving when custom upstreams for hosts are specified ([#2994]).
|
||||
- Inconsistent resolving of DHCP clients when the DHCP server is disabled
|
||||
@@ -698,10 +618,6 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
|
||||
|
||||
## [v0.105.2] - 2021-03-10
|
||||
|
||||
### Security
|
||||
|
||||
- Session token doesn't contain user's information anymore ([#2470]).
|
||||
|
||||
See also the [v0.105.2 GitHub milestone][ms-v0.105.2].
|
||||
|
||||
### Fixed
|
||||
@@ -715,6 +631,10 @@ See also the [v0.105.2 GitHub milestone][ms-v0.105.2].
|
||||
- Incomplete DNS upstreams validation ([#2674]).
|
||||
- Wrong parsing of DHCP options of the `ip` type ([#2688]).
|
||||
|
||||
### Security
|
||||
|
||||
- Session token doesn't contain user's information anymore ([#2470]).
|
||||
|
||||
[#2470]: https://github.com/AdguardTeam/AdGuardHome/issues/2470
|
||||
[#2582]: https://github.com/AdguardTeam/AdGuardHome/issues/2582
|
||||
[#2600]: https://github.com/AdguardTeam/AdGuardHome/issues/2600
|
||||
@@ -750,8 +670,8 @@ See also the [v0.105.1 GitHub milestone][ms-v0.105.1].
|
||||
- Occasional crashes during startup.
|
||||
- The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response
|
||||
is now correctly named again ([#2678]).
|
||||
- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` properties aren't reset
|
||||
to `false` on update anymore ([#2653]).
|
||||
- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` settings aren't reset to
|
||||
`false` on update anymore ([#2653]).
|
||||
- The `Vary` header is now added along with `Access-Control-Allow-Origin` to
|
||||
prevent cache-related and other issues in browsers ([#2658]).
|
||||
- The request body size limit is now set for HTTPS requests as well.
|
||||
@@ -785,7 +705,7 @@ See also the [v0.105.0 GitHub milestone][ms-v0.105.0].
|
||||
- `ipset` subdomain matching, just like `dnsmasq` does ([#2179]).
|
||||
- ClientID support for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS
|
||||
([#1383]).
|
||||
- The new `dnsrewrite` modifier for filters ([#2102]).
|
||||
- `$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
|
||||
@@ -793,7 +713,7 @@ See also the [v0.105.0 GitHub milestone][ms-v0.105.0].
|
||||
- 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]).
|
||||
- `$dnstype` modifier for filters ([#2337]).
|
||||
- HTTP API request body size limit ([#2305]).
|
||||
|
||||
### Changed
|
||||
@@ -926,14 +846,13 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
||||
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...HEAD
|
||||
[v0.107.7]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...v0.107.7
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...HEAD
|
||||
[v0.107.5]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...HEAD
|
||||
[v0.107.6]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...v0.107.6
|
||||
[v0.107.5]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...HEAD
|
||||
[v0.107.4]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.3...v0.107.4
|
||||
[v0.107.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.2...v0.107.3
|
||||
[v0.107.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.1...v0.107.2
|
||||
|
||||
2
Makefile
2
Makefile
@@ -17,7 +17,6 @@ DIST_DIR = dist
|
||||
# See https://unix.stackexchange.com/q/646255/105635.
|
||||
GO.MACRO = $${GO:-go}
|
||||
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
|
||||
GOSUMDB = sum.golang.google.cn
|
||||
GPG_KEY = devteam@adguard.com
|
||||
GPG_KEY_PASSPHRASE = not-a-real-password
|
||||
NPM = npm
|
||||
@@ -57,7 +56,6 @@ ENV = env\
|
||||
DIST_DIR='$(DIST_DIR)'\
|
||||
GO="$(GO.MACRO)"\
|
||||
GOPROXY='$(GOPROXY)'\
|
||||
GOSUMDB='$(GOSUMDB)'\
|
||||
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
|
||||
RACE='$(RACE)'\
|
||||
SIGN='$(SIGN)'\
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.2'
|
||||
|
||||
'stages':
|
||||
- 'Make release':
|
||||
@@ -183,27 +183,8 @@
|
||||
|
||||
cd ./dist/
|
||||
|
||||
channel="${bamboo.channel}"
|
||||
readonly channel
|
||||
case "$channel"
|
||||
in
|
||||
('release')
|
||||
snapchannel='candidate'
|
||||
;;
|
||||
('beta')
|
||||
snapchannel='beta'
|
||||
;;
|
||||
('edge')
|
||||
snapchannel='edge'
|
||||
;;
|
||||
(*)
|
||||
echo "invalid channel '$channel'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
env\
|
||||
SNAPCRAFT_CHANNEL="$snapchannel"\
|
||||
SNAPCRAFT_CHANNEL=edge\
|
||||
SNAPCRAFT_EMAIL="${bamboo.snapcraftEmail}"\
|
||||
SNAPCRAFT_MACAROON="${bamboo.snapcraftMacaroonPassword}"\
|
||||
SNAPCRAFT_UBUNTU_DISCHARGE="${bamboo.snapcraftUbuntuDischargePassword}"\
|
||||
@@ -285,7 +266,7 @@
|
||||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.2'
|
||||
# release-vX.Y.Z branches are the branches from which the actual final release
|
||||
# is built.
|
||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||
@@ -300,4 +281,4 @@
|
||||
# are the ones that actually get released.
|
||||
'variables':
|
||||
'channel': 'release'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.2'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'key': 'AHBRTSPECS'
|
||||
'name': 'AdGuard Home - Build and run tests'
|
||||
'variables':
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.2'
|
||||
|
||||
'stages':
|
||||
- 'Tests':
|
||||
|
||||
114
client/package-lock.json
generated
vendored
114
client/package-lock.json
generated
vendored
@@ -1700,6 +1700,12 @@
|
||||
"v8-to-istanbul": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||
@@ -1720,6 +1726,12 @@
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"char-regex": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
||||
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
|
||||
"dev": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -1747,6 +1759,25 @@
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"string-length": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
||||
"integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"char-regex": "^1.0.2",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
|
||||
@@ -3959,10 +3990,9 @@
|
||||
"integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
|
||||
},
|
||||
"char-regex": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
||||
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
|
||||
"dev": true
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.0.tgz",
|
||||
"integrity": "sha512-oGu2QekBMXgyQNWPDRQ001bjvDnZe4/zBTz37TMbiKz1NbNiyiH5hRkobe7npRN6GfbGbxMYFck/vQ1r9c1VMA=="
|
||||
},
|
||||
"character-entities": {
|
||||
"version": "1.2.4",
|
||||
@@ -10037,6 +10067,12 @@
|
||||
"string-length": "^4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||
@@ -10057,6 +10093,12 @@
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"char-regex": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
||||
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
|
||||
"dev": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -10078,6 +10120,25 @@
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"string-length": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
||||
"integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"char-regex": "^1.0.2",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
|
||||
@@ -14193,38 +14254,26 @@
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"string-length": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz",
|
||||
"integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==",
|
||||
"dev": true,
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz",
|
||||
"integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==",
|
||||
"requires": {
|
||||
"char-regex": "^1.0.2",
|
||||
"strip-ansi": "^6.0.0"
|
||||
"char-regex": "^2.0.0",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"dev": true
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||
"dev": true,
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
|
||||
"integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.0"
|
||||
"ansi-regex": "^6.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14297,6 +14346,15 @@
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"stringify-entities": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz",
|
||||
|
||||
1
client/package.json
vendored
1
client/package.json
vendored
@@ -42,6 +42,7 @@
|
||||
"redux-actions": "^2.6.5",
|
||||
"redux-form": "^8.3.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"string-length": "^5.0.1",
|
||||
"url-polyfill": "^1.1.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -35,24 +35,24 @@
|
||||
"dhcp_config_saved": "DHCP configuration successfully saved",
|
||||
"dhcp_ipv4_settings": "DHCP IPv4 Settings",
|
||||
"dhcp_ipv6_settings": "DHCP IPv6 Settings",
|
||||
"form_error_required": "Required field.",
|
||||
"form_error_ip4_format": "Invalid IPv4 address.",
|
||||
"form_error_ip4_range_start_format": "Invalid IPv4 address of the range start.",
|
||||
"form_error_ip4_range_end_format": "Invalid IPv4 address of the range end.",
|
||||
"form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway.",
|
||||
"form_error_ip6_format": "Invalid IPv6 address.",
|
||||
"form_error_ip_format": "Invalid IP address.",
|
||||
"form_error_mac_format": "Invalid MAC address.",
|
||||
"form_error_client_id_format": "ClientID must contain only numbers, lowercase letters, and hyphens.",
|
||||
"form_error_server_name": "Invalid server name.",
|
||||
"form_error_subnet": "Subnet \"{{cidr}}\" does not contain the IP address \"{{ip}}\".",
|
||||
"form_error_positive": "Must be greater than 0.",
|
||||
"out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\".",
|
||||
"lower_range_start_error": "Must be lower than range start.",
|
||||
"greater_range_start_error": "Must be greater than range start.",
|
||||
"greater_range_end_error": "Must be greater than range end.",
|
||||
"subnet_error": "Addresses must be in one subnet.",
|
||||
"gateway_or_subnet_invalid": "Subnet mask invalid.",
|
||||
"form_error_required": "Required field",
|
||||
"form_error_ip4_format": "Invalid IPv4 address",
|
||||
"form_error_ip4_range_start_format": "Invalid IPv4 address of the range start",
|
||||
"form_error_ip4_range_end_format": "Invalid IPv4 address of the range end",
|
||||
"form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway",
|
||||
"form_error_ip6_format": "Invalid IPv6 address",
|
||||
"form_error_ip_format": "Invalid IP address",
|
||||
"form_error_mac_format": "Invalid MAC address",
|
||||
"form_error_client_id_format": "ClientID must contain only numbers, lowercase letters, and hyphens",
|
||||
"form_error_server_name": "Invalid server name",
|
||||
"form_error_subnet": "Subnet \"{{cidr}}\" does not contain the IP address \"{{ip}}\"",
|
||||
"form_error_positive": "Must be greater than 0",
|
||||
"out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Must be lower than range start",
|
||||
"greater_range_start_error": "Must be greater than range start",
|
||||
"greater_range_end_error": "Must be greater than range end",
|
||||
"subnet_error": "Addresses must be in one subnet",
|
||||
"gateway_or_subnet_invalid": "Subnet mask invalid",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
"dhcp_form_subnet_input": "Subnet mask",
|
||||
"dhcp_form_range_title": "Range of IP addresses",
|
||||
@@ -67,7 +67,7 @@
|
||||
"dhcp_table_hostname": "Hostname",
|
||||
"dhcp_table_expires": "Expires",
|
||||
"dhcp_warning": "If you want to enable DHCP server anyway, make sure that there is no other active DHCP server in your network, as this may break the Internet connectivity for devices on the network!",
|
||||
"dhcp_error": "AdGuard Home could not determine if there is another active DHCP server on the network.",
|
||||
"dhcp_error": "AdGuard Home could not determine if there is another active DHCP server on the network",
|
||||
"dhcp_static_ip_error": "In order to use DHCP server a static IP address must be set. AdGuard Home failed to determine if this network interface is configured using a static IP address. Please set a static IP address manually.",
|
||||
"dhcp_dynamic_ip_found": "Your system uses dynamic IP address configuration for interface <0>{{interfaceName}}</0>. In order to use DHCP server, a static IP address must be set. Your current IP address is <0>{{ipAddress}}</0>. AdGuard Home will automatically set this IP address as static if you press the \"Enable DHCP server\" button.",
|
||||
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
|
||||
@@ -196,8 +196,8 @@
|
||||
"choose_allowlist": "Choose allowlists",
|
||||
"enter_valid_blocklist": "Enter a valid URL to the blocklist.",
|
||||
"enter_valid_allowlist": "Enter a valid URL to the allowlist.",
|
||||
"form_error_url_format": "Invalid URL format.",
|
||||
"form_error_url_or_path_format": "Invalid URL or absolute path of the list.",
|
||||
"form_error_url_format": "Invalid URL format",
|
||||
"form_error_url_or_path_format": "Invalid URL or absolute path of the list",
|
||||
"custom_filter_rules": "Custom filtering rules",
|
||||
"custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.",
|
||||
"system_host_files": "System hosts files",
|
||||
@@ -210,11 +210,13 @@
|
||||
"example_comment_hash": "# Also a comment.",
|
||||
"example_regex_meaning": "block access to domains matching the specified regular expression.",
|
||||
"example_upstream_regular": "regular DNS (over UDP);",
|
||||
"example_upstream_udp": "regular DNS (over UDP, hostname);",
|
||||
"example_upstream_dot": "encrypted <0>DNS-over-TLS</0>;",
|
||||
"example_upstream_doh": "encrypted <0>DNS-over-HTTPS</0>;",
|
||||
"example_upstream_doq": "encrypted <0>DNS-over-QUIC</0> (experimental);",
|
||||
"example_upstream_sdns": "<0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers;",
|
||||
"example_upstream_tcp": "regular DNS (over TCP);",
|
||||
"example_upstream_tcp_hostname": "regular DNS (over TCP, hostname);",
|
||||
"all_lists_up_to_date_toast": "All lists are already up-to-date",
|
||||
"updated_upstream_dns_toast": "Upstream servers successfully saved",
|
||||
"dns_test_ok_toast": "Specified DNS servers are working correctly",
|
||||
@@ -259,10 +261,10 @@
|
||||
"query_log_strict_search": "Use double quotes for strict search",
|
||||
"query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost",
|
||||
"anonymize_client_ip": "Anonymize client IP",
|
||||
"anonymize_client_ip_desc": "Don't save the client's full IP address to logs or statistics.",
|
||||
"anonymize_client_ip_desc": "Don't save the client's full IP address to logs or statistics",
|
||||
"dns_config": "DNS server configuration",
|
||||
"dns_cache_config": "DNS cache configuration",
|
||||
"dns_cache_config_desc": "Here you can configure DNS cache.",
|
||||
"dns_cache_config_desc": "Here you can configure DNS cache",
|
||||
"blocking_mode": "Blocking mode",
|
||||
"default": "Default",
|
||||
"nxdomain": "NXDOMAIN",
|
||||
@@ -285,7 +287,7 @@
|
||||
"form_enter_rate_limit": "Enter rate limit",
|
||||
"rate_limit": "Rate limit",
|
||||
"edns_enable": "Enable EDNS client subnet",
|
||||
"edns_cs_desc": "Send clients' subnets to the DNS servers.",
|
||||
"edns_cs_desc": "Add the EDNS Client Subnet option (ECS) to upstream requests and log the values sent by the clients in the query log.",
|
||||
"rate_limit_desc": "The number of requests per second allowed per client. Setting it to 0 means no limit.",
|
||||
"blocking_ipv4_desc": "IP address to be returned for a blocked A request",
|
||||
"blocking_ipv6_desc": "IP address to be returned for a blocked AAAA request",
|
||||
@@ -309,7 +311,7 @@
|
||||
"install_settings_listen": "Listen interface",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Your AdGuard Home admin web interface will be available on the following addresses:",
|
||||
"form_error_port": "Enter valid port number.",
|
||||
"form_error_port": "Enter valid port number",
|
||||
"install_settings_dns": "DNS server",
|
||||
"install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server on the following addresses:",
|
||||
"install_settings_all_interfaces": "All interfaces",
|
||||
@@ -356,7 +358,7 @@
|
||||
"open_dashboard": "Open Dashboard",
|
||||
"install_saved": "Saved successfully",
|
||||
"encryption_title": "Encryption",
|
||||
"encryption_desc": "Encryption (HTTPS/TLS) support for both DNS and admin web interface.",
|
||||
"encryption_desc": "Encryption (HTTPS/QUIC/TLS) support for both DNS and admin web interface",
|
||||
"encryption_config_saved": "Encryption configuration saved",
|
||||
"encryption_server": "Server name",
|
||||
"encryption_server_enter": "Enter your domain name",
|
||||
@@ -378,26 +380,26 @@
|
||||
"encryption_key_input": "Copy/paste your PEM-encoded private key for your certificate here.",
|
||||
"encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)",
|
||||
"encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Certificate chain is valid.",
|
||||
"encryption_chain_invalid": "Certificate chain is invalid.",
|
||||
"encryption_key_valid": "This is a valid {{type}} private key.",
|
||||
"encryption_key_invalid": "This is an invalid {{type}} private key.",
|
||||
"encryption_chain_valid": "Certificate chain is valid",
|
||||
"encryption_chain_invalid": "Certificate chain is invalid",
|
||||
"encryption_key_valid": "This is a valid {{type}} private key",
|
||||
"encryption_key_invalid": "This is an invalid {{type}} private key",
|
||||
"encryption_subject": "Subject",
|
||||
"encryption_issuer": "Issuer",
|
||||
"encryption_hostnames": "Hostnames",
|
||||
"encryption_reset": "Are you sure you want to reset encryption settings?",
|
||||
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings</0>.",
|
||||
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings</0>.",
|
||||
"form_error_port_range": "Enter port number in the range of 80-65535.",
|
||||
"form_error_port_unsafe": "This is an unsafe port.",
|
||||
"form_error_equal": "Must not be equal.",
|
||||
"form_error_password": "Password mismatched.",
|
||||
"form_error_port_range": "Enter port number in the range of 80-65535",
|
||||
"form_error_port_unsafe": "Unsafe port",
|
||||
"form_error_equal": "Must not be equal",
|
||||
"form_error_password": "Password mismatch",
|
||||
"reset_settings": "Reset settings",
|
||||
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here</0> for more info.",
|
||||
"setup_guide": "Setup Guide",
|
||||
"dns_addresses": "DNS addresses",
|
||||
"dns_start": "DNS server is starting up",
|
||||
"dns_status_error": "Error checking the DNS server status.",
|
||||
"dns_status_error": "Error checking the DNS server status",
|
||||
"down": "Down",
|
||||
"fix": "Fix",
|
||||
"dns_providers": "Here is a <0>list of known DNS providers</0> to choose from.",
|
||||
@@ -406,7 +408,7 @@
|
||||
"manual_update": "Please <a>follow these steps</a> to update manually.",
|
||||
"processing_update": "Please wait, AdGuard Home is being updated",
|
||||
"clients_title": "Persistent clients",
|
||||
"clients_desc": "Configure persistent client records for devices connected to AdGuard Home.",
|
||||
"clients_desc": "Configure persistent client records for devices connected to AdGuard Home",
|
||||
"settings_global": "Global",
|
||||
"settings_custom": "Custom",
|
||||
"table_client": "Client",
|
||||
@@ -433,9 +435,9 @@
|
||||
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
|
||||
"list_confirm_delete": "Are you sure you want to delete this list?",
|
||||
"auto_clients_title": "Runtime clients",
|
||||
"auto_clients_desc": "Devices not on the list of Persistent clients that may still use AdGuard Home.",
|
||||
"auto_clients_desc": "Devices not on the list of Persistent clients that may still use AdGuard Home",
|
||||
"access_title": "Access settings",
|
||||
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
|
||||
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server",
|
||||
"access_allowed_title": "Allowed clients",
|
||||
"access_allowed_desc": "A list of CIDRs, IP addresses, or <a>ClientIDs</a>. If this list has entries, AdGuard Home will accept requests only from these clients.",
|
||||
"access_disallowed_title": "Disallowed clients",
|
||||
@@ -475,8 +477,8 @@
|
||||
"dns_rewrites": "DNS rewrites",
|
||||
"form_domain": "Enter domain name or wildcard",
|
||||
"form_answer": "Enter IP address or domain name",
|
||||
"form_error_domain_format": "Invalid domain format.",
|
||||
"form_error_answer_format": "Invalid answer format.",
|
||||
"form_error_domain_format": "Invalid domain format",
|
||||
"form_error_answer_format": "Invalid answer format",
|
||||
"configure": "Configure",
|
||||
"main_settings": "Main settings",
|
||||
"block_services": "Block specific services",
|
||||
@@ -500,6 +502,7 @@
|
||||
"interval_days": "{{count}} day",
|
||||
"interval_days_plural": "{{count}} days",
|
||||
"domain": "Domain",
|
||||
"ecs": "ECS",
|
||||
"punycode": "Punycode",
|
||||
"answer": "Answer",
|
||||
"filter_added_successfully": "The list has been successfully added",
|
||||
@@ -507,7 +510,7 @@
|
||||
"filter_updated": "The list has been successfully updated",
|
||||
"statistics_configuration": "Statistics configuration",
|
||||
"statistics_retention": "Statistics retention",
|
||||
"statistics_retention_desc": "If you decrease the interval value, some data will be lost.",
|
||||
"statistics_retention_desc": "If you decrease the interval value, some data will be lost",
|
||||
"statistics_clear": "Clear statistics",
|
||||
"statistics_clear_confirm": "Are you sure you want to clear statistics?",
|
||||
"statistics_retention_confirm": "Are you sure you want to change statistics retention? If you decrease the interval value, some data will be lost",
|
||||
@@ -517,7 +520,7 @@
|
||||
"interval_hours_plural": "{{count}} hours",
|
||||
"filters_configuration": "Filters configuration",
|
||||
"filters_enable": "Enable filters",
|
||||
"filters_interval": "Filters update interval",
|
||||
"filters_interval": "Filter update interval",
|
||||
"disabled": "Disabled",
|
||||
"username_label": "Username",
|
||||
"username_placeholder": "Enter username",
|
||||
@@ -606,7 +609,7 @@
|
||||
"enter_cache_ttl_max_override": "Enter maximum TTL (seconds)",
|
||||
"cache_ttl_min_override_desc": "Extend short time-to-live values (seconds) received from the upstream server when caching DNS responses.",
|
||||
"cache_ttl_max_override_desc": "Set a maximum time-to-live value (seconds) for entries in the DNS cache.",
|
||||
"ttl_cache_validation": "Minimum cache TTL override must be less than or equal to the maximum.",
|
||||
"ttl_cache_validation": "Minimum cache TTL override must be less than or equal to the maximum",
|
||||
"cache_optimistic": "Optimistic caching",
|
||||
"cache_optimistic_desc": "Make AdGuard Home respond from the cache even when the entries are expired and also try to refresh them.",
|
||||
"filter_category_general": "General",
|
||||
@@ -628,5 +631,5 @@
|
||||
"parental_control": "Parental Control",
|
||||
"safe_browsing": "Safe Browsing",
|
||||
"served_from_cache": "{{value}} <i>(served from cache)</i>",
|
||||
"form_error_password_length": "Password must be at least {{value}} characters long."
|
||||
"form_error_password_length": "Password must be at least {{value}} characters long"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ const DomainCell = ({
|
||||
time,
|
||||
tracker,
|
||||
type,
|
||||
ecs,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dnssec_enabled = useSelector((state) => state.dnsConfig.dnssec_enabled);
|
||||
@@ -56,6 +57,13 @@ const DomainCell = ({
|
||||
};
|
||||
}
|
||||
|
||||
if (ecs) {
|
||||
requestDetailsObj = {
|
||||
...requestDetailsObj,
|
||||
ecs,
|
||||
};
|
||||
}
|
||||
|
||||
requestDetailsObj = {
|
||||
...requestDetailsObj,
|
||||
type_table_header: type,
|
||||
@@ -168,6 +176,7 @@ DomainCell.propTypes = {
|
||||
time: propTypes.string.isRequired,
|
||||
type: propTypes.string.isRequired,
|
||||
tracker: propTypes.object,
|
||||
ecs: propTypes.string,
|
||||
};
|
||||
|
||||
export default DomainCell;
|
||||
|
||||
@@ -238,6 +238,7 @@ Row.propTypes = {
|
||||
type: propTypes.string.isRequired,
|
||||
client_proto: propTypes.string.isRequired,
|
||||
client_id: propTypes.string,
|
||||
ecs: propTypes.string,
|
||||
client_info: propTypes.shape({
|
||||
name: propTypes.string.isRequired,
|
||||
whois: propTypes.shape({
|
||||
|
||||
@@ -10,6 +10,15 @@ const Examples = (props) => (
|
||||
<li>
|
||||
<code>94.140.14.140</code>: {props.t('example_upstream_regular')}
|
||||
</li>
|
||||
<li>
|
||||
<code>udp://dns-unfiltered.adguard.com</code>: <Trans>example_upstream_udp</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://94.140.14.140</code>: <Trans>example_upstream_tcp</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://dns-unfiltered.adguard.com</code>: <Trans>example_upstream_tcp_hostname</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>tls://dns-unfiltered.adguard.com</code>:
|
||||
<span>
|
||||
@@ -67,9 +76,6 @@ const Examples = (props) => (
|
||||
</Trans>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://94.140.14.140</code>: <Trans>example_upstream_tcp</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>sdns://...</code>:
|
||||
<span>
|
||||
|
||||
@@ -25,9 +25,7 @@ class Encryption extends Component {
|
||||
|
||||
handleFormChange = debounce((values) => {
|
||||
const submitValues = this.getSubmitValues(values);
|
||||
if (submitValues.enabled) {
|
||||
this.props.validateTlsConfig(submitValues);
|
||||
}
|
||||
this.props.validateTlsConfig(submitValues);
|
||||
}, DEBOUNCE_TIMEOUT);
|
||||
|
||||
getInitialValues = (data) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.icons {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon--24 {
|
||||
|
||||
@@ -26,6 +26,8 @@ export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)
|
||||
|
||||
export const R_CLIENT_ID = /^[a-z0-9-]{1,63}$/;
|
||||
|
||||
export const MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
export const HTML_PAGES = {
|
||||
INSTALL: '/install.html',
|
||||
LOGIN: '/login.html',
|
||||
|
||||
@@ -76,6 +76,7 @@ export const normalizeLogs = (logs) => logs.map((log) => {
|
||||
original_answer,
|
||||
upstream,
|
||||
cached,
|
||||
ecs,
|
||||
} = log;
|
||||
|
||||
const { name: domain, unicode_name: unicodeName, type } = question;
|
||||
@@ -118,6 +119,7 @@ export const normalizeLogs = (logs) => logs.map((log) => {
|
||||
elapsedMs,
|
||||
upstream,
|
||||
cached,
|
||||
ecs,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import i18next from 'i18next';
|
||||
import stringLength from 'string-length';
|
||||
|
||||
import {
|
||||
MAX_PORT,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
UNSAFE_PORTS,
|
||||
R_CLIENT_ID,
|
||||
R_DOMAIN,
|
||||
MIN_PASSWORD_LENGTH,
|
||||
} from './constants';
|
||||
import { ip4ToInt, isValidAbsolutePath } from './form';
|
||||
import { isIpInCidr, parseSubnetMask } from './helpers';
|
||||
@@ -320,10 +322,20 @@ export const validatePath = (value) => {
|
||||
* @param cidr {string}
|
||||
* @returns {Function}
|
||||
*/
|
||||
|
||||
export const validateIpv4InCidr = (valueIp, allValues) => {
|
||||
if (!isIpInCidr(valueIp, allValues.cidr)) {
|
||||
return i18next.t('form_error_subnet', { ip: valueIp, cidr: allValues.cidr });
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param value {string}
|
||||
* @returns {Function}
|
||||
*/
|
||||
export const validatePasswordLength = (value) => {
|
||||
if (value && stringLength(value) < MIN_PASSWORD_LENGTH) {
|
||||
return i18next.t('form_error_password_length', { value: MIN_PASSWORD_LENGTH });
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import i18n from '../../i18n';
|
||||
import Controls from './Controls';
|
||||
import { renderInputField } from '../../helpers/form';
|
||||
import { FORM_NAME } from '../../helpers/constants';
|
||||
import { validatePasswordLength } from '../../helpers/validators';
|
||||
|
||||
const required = (value) => {
|
||||
if (value || value === 0) {
|
||||
@@ -67,7 +68,7 @@ const Auth = (props) => {
|
||||
type="password"
|
||||
className="form-control"
|
||||
placeholder={ t('install_auth_password_enter') }
|
||||
validate={[required]}
|
||||
validate={[required, validatePasswordLength]}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import i18n from 'i18next';
|
||||
|
||||
import Controls from './Controls';
|
||||
import AddressList from './AddressList';
|
||||
@@ -32,10 +31,10 @@ const renderInterfaces = (interfaces) => Object.values(interfaces)
|
||||
|
||||
if (option && ip_addresses?.length > 0) {
|
||||
const ip = getInterfaceIp(option);
|
||||
const isUp = flags?.includes('up');
|
||||
const isDown = flags?.includes('down');
|
||||
|
||||
return <option value={ip} key={name} disabled={!isUp}>
|
||||
{name} - {ip} {!isUp && `(${i18n.t('down')})`}
|
||||
return <option value={ip} key={name} disabled={isDown}>
|
||||
{name} - {ip} {isDown && `(${<Trans>down</Trans>})`}
|
||||
</option>;
|
||||
}
|
||||
|
||||
|
||||
17
go.mod
17
go.mod
@@ -3,8 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.41.4
|
||||
github.com/AdguardTeam/golibs v0.10.8
|
||||
github.com/AdguardTeam/dnsproxy v0.41.3
|
||||
github.com/AdguardTeam/golibs v0.10.6
|
||||
github.com/AdguardTeam/urlfilter v0.15.2
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.3
|
||||
@@ -20,14 +20,14 @@ require (
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
|
||||
github.com/mdlayher/netlink v1.5.0
|
||||
github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b
|
||||
github.com/miekg/dns v1.1.48
|
||||
github.com/miekg/dns v1.1.45
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/ti-mo/netfilter v0.4.0
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
|
||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
howett.net/plist v1.0.0
|
||||
@@ -55,15 +55,12 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.1.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
honnef.co/go/tools v0.2.2 // indirect
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Return to the main repo once miekg/dns#1359 is merged.
|
||||
replace github.com/miekg/dns => github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8
|
||||
|
||||
27
go.sum
27
go.sum
@@ -7,14 +7,13 @@ 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.41.4 h1:zA8BJmWBkSL5kp4b8CblQRgIrLGzJ4IUGQ7tA1255Cw=
|
||||
github.com/AdguardTeam/dnsproxy v0.41.4/go.mod h1:GCdEbTw683vBqksJIccPSYzBg2yIFbRiDnXltyIinug=
|
||||
github.com/AdguardTeam/dnsproxy v0.41.3 h1:FJnIf2pHaABUjAvB0P79nIXN5sBAvsUf2368NNw50+s=
|
||||
github.com/AdguardTeam/dnsproxy v0.41.3/go.mod h1:GCdEbTw683vBqksJIccPSYzBg2yIFbRiDnXltyIinug=
|
||||
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
|
||||
github.com/AdguardTeam/golibs v0.10.6 h1:6UG6LxWFnG7TfjNzeApw+T68Kqqov0fcDYk9RjhTdhc=
|
||||
github.com/AdguardTeam/golibs v0.10.6/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
|
||||
github.com/AdguardTeam/golibs v0.10.8 h1:diU9gP9qG1qeLbAkzIwfUerpHSqzR6zaBgzvRMR/m6Q=
|
||||
github.com/AdguardTeam/golibs v0.10.8/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
|
||||
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
|
||||
github.com/AdguardTeam/urlfilter v0.15.2 h1:LZGgrm4l4Ys9eAqB+UUmZfiC6vHlDlYFhx0WXqo6LtQ=
|
||||
github.com/AdguardTeam/urlfilter v0.15.2/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
|
||||
@@ -29,8 +28,6 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||
github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8 h1:Hp2waLwK989ui3bDkFpedlIHfyWdZ77gynvd+GPEqXY=
|
||||
github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.3 h1:X9UP5AHtwp46Ji+sGFfF/1Is6OPI/SjxLqhKpx0P5UI=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.3/go.mod h1:xJB9cE1/GF+NB6EEQqRlkoa4bjcV2w7VYn1G+zVq7Bs=
|
||||
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
@@ -199,8 +196,8 @@ github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00v
|
||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/miekg/dns v1.1.44/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
|
||||
github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk=
|
||||
github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
@@ -303,7 +300,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -314,9 +310,8 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -357,9 +352,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
|
||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
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=
|
||||
@@ -429,11 +423,9 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
|
||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -458,9 +450,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -156,7 +156,7 @@ func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, n
|
||||
b := make([]byte, 1500)
|
||||
n, _, err := c.ReadFrom(b)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
if isTimeout(err) {
|
||||
log.Debug("dhcpv4: didn't receive dhcp response")
|
||||
|
||||
return false, false, nil
|
||||
@@ -176,21 +176,20 @@ func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, n
|
||||
|
||||
log.Debug("dhcpv4: received message from server: %s", response.Summary())
|
||||
|
||||
switch {
|
||||
case
|
||||
response.OpCode != dhcpv4.OpcodeBootReply,
|
||||
response.HWType != iana.HWTypeEthernet,
|
||||
!bytes.Equal(response.ClientHWAddr, iface.HardwareAddr),
|
||||
response.TransactionID != req.TransactionID,
|
||||
!response.Options.Has(dhcpv4.OptionDHCPMessageType):
|
||||
log.Debug("dhcpv4: received response doesn't match the request")
|
||||
if !(response.OpCode == dhcpv4.OpcodeBootReply &&
|
||||
response.HWType == iana.HWTypeEthernet &&
|
||||
bytes.Equal(response.ClientHWAddr, iface.HardwareAddr) &&
|
||||
bytes.Equal(response.TransactionID[:], req.TransactionID[:]) &&
|
||||
response.Options.Has(dhcpv4.OptionDHCPMessageType)) {
|
||||
|
||||
log.Debug("dhcpv4: received message from server doesn't match our request")
|
||||
|
||||
return false, true, nil
|
||||
default:
|
||||
log.Tracef("dhcpv4: the packet is from an active dhcp server")
|
||||
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
log.Tracef("dhcpv4: the packet is from an active dhcp server")
|
||||
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
// checkOtherDHCPv6 sends a DHCP request to the specified network interface, and
|
||||
@@ -276,7 +275,7 @@ func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error)
|
||||
|
||||
n, _, err := c.ReadFrom(b)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
if isTimeout(err) {
|
||||
log.Debug("dhcpv6: didn't receive dhcp response")
|
||||
|
||||
return false, false, nil
|
||||
@@ -319,3 +318,15 @@ func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error)
|
||||
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
// isTimeout returns true if err is an operation timeout error from net package.
|
||||
//
|
||||
// TODO(e.burkov): Consider moving into netutil.
|
||||
func isTimeout(err error) (ok bool) {
|
||||
var operr *net.OpError
|
||||
if errors.As(err, &operr) {
|
||||
return operr.Timeout()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -6,16 +6,18 @@ import (
|
||||
|
||||
// SubnetDetector describes IP address properties.
|
||||
type SubnetDetector struct {
|
||||
// spNets is the slice of special-purpose address registries as defined
|
||||
// by RFC-6890 (https://tools.ietf.org/html/rfc6890).
|
||||
// spNets is the collection of special-purpose address registries as defined
|
||||
// by RFC 6890.
|
||||
spNets []*net.IPNet
|
||||
|
||||
// locServedNets is the slice of locally-served networks as defined by
|
||||
// RFC-6303 (https://tools.ietf.org/html/rfc6303).
|
||||
// locServedNets is the collection of locally-served networks as defined by
|
||||
// RFC 6303.
|
||||
locServedNets []*net.IPNet
|
||||
}
|
||||
|
||||
// NewSubnetDetector returns a new IP detector.
|
||||
//
|
||||
// TODO(a.garipov): Decide whether an error is actually needed.
|
||||
func NewSubnetDetector() (snd *SubnetDetector, err error) {
|
||||
spNets := []string{
|
||||
// "This" network.
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// TestUpstream is a mock of real upstream.
|
||||
type TestUpstream struct {
|
||||
// Upstream is a mock implementation of upstream.Upstream.
|
||||
type Upstream struct {
|
||||
// CName is a map of hostname to canonical name.
|
||||
CName map[string]string
|
||||
CName map[string][]string
|
||||
// IPv4 is a map of hostname to IPv4.
|
||||
IPv4 map[string][]net.IP
|
||||
// IPv6 is a map of hostname to IPv6.
|
||||
@@ -25,78 +25,45 @@ type TestUpstream struct {
|
||||
Addr string
|
||||
}
|
||||
|
||||
// Exchange implements upstream.Upstream interface for *TestUpstream.
|
||||
// Exchange implements the upstream.Upstream interface for *Upstream.
|
||||
//
|
||||
// TODO(a.garipov): Split further into handlers.
|
||||
func (u *TestUpstream) Exchange(m *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = &dns.Msg{}
|
||||
resp.SetReply(m)
|
||||
func (u *Upstream) Exchange(m *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = new(dns.Msg).SetReply(m)
|
||||
|
||||
if len(m.Question) == 0 {
|
||||
return nil, fmt.Errorf("question should not be empty")
|
||||
}
|
||||
|
||||
name := m.Question[0].Name
|
||||
|
||||
if cname, ok := u.CName[name]; ok {
|
||||
ans := &dns.CNAME{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: name,
|
||||
Rrtype: dns.TypeCNAME,
|
||||
},
|
||||
q := m.Question[0]
|
||||
name := q.Name
|
||||
for _, cname := range u.CName[name] {
|
||||
resp.Answer = append(resp.Answer, &dns.CNAME{
|
||||
Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME},
|
||||
Target: cname,
|
||||
}
|
||||
|
||||
resp.Answer = append(resp.Answer, ans)
|
||||
})
|
||||
}
|
||||
|
||||
rrType := m.Question[0].Qtype
|
||||
qtype := q.Qtype
|
||||
hdr := dns.RR_Header{
|
||||
Name: name,
|
||||
Rrtype: rrType,
|
||||
Rrtype: qtype,
|
||||
}
|
||||
|
||||
var names []string
|
||||
var ips []net.IP
|
||||
switch m.Question[0].Qtype {
|
||||
switch qtype {
|
||||
case dns.TypeA:
|
||||
ips = u.IPv4[name]
|
||||
for _, ip := range u.IPv4[name] {
|
||||
resp.Answer = append(resp.Answer, &dns.A{Hdr: hdr, A: ip})
|
||||
}
|
||||
case dns.TypeAAAA:
|
||||
ips = u.IPv6[name]
|
||||
for _, ip := range u.IPv6[name] {
|
||||
resp.Answer = append(resp.Answer, &dns.AAAA{Hdr: hdr, AAAA: ip})
|
||||
}
|
||||
case dns.TypePTR:
|
||||
names = u.Reverse[name]
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
var ans dns.RR
|
||||
if rrType == dns.TypeA {
|
||||
ans = &dns.A{
|
||||
Hdr: hdr,
|
||||
A: ip,
|
||||
}
|
||||
|
||||
resp.Answer = append(resp.Answer, ans)
|
||||
|
||||
continue
|
||||
for _, name := range u.Reverse[name] {
|
||||
resp.Answer = append(resp.Answer, &dns.PTR{Hdr: hdr, Ptr: name})
|
||||
}
|
||||
|
||||
ans = &dns.AAAA{
|
||||
Hdr: hdr,
|
||||
AAAA: ip,
|
||||
}
|
||||
|
||||
resp.Answer = append(resp.Answer, ans)
|
||||
}
|
||||
|
||||
for _, n := range names {
|
||||
ans := &dns.PTR{
|
||||
Hdr: hdr,
|
||||
Ptr: n,
|
||||
}
|
||||
|
||||
resp.Answer = append(resp.Answer, ans)
|
||||
}
|
||||
|
||||
if len(resp.Answer) == 0 {
|
||||
resp.SetRcode(m, dns.RcodeNameError)
|
||||
}
|
||||
@@ -104,8 +71,8 @@ func (u *TestUpstream) Exchange(m *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Address implements upstream.Upstream interface for *TestUpstream.
|
||||
func (u *TestUpstream) Address() string {
|
||||
// Address implements upstream.Upstream interface for *Upstream.
|
||||
func (u *Upstream) Address() string {
|
||||
return u.Addr
|
||||
}
|
||||
|
||||
|
||||
30
internal/aghtls/aghtls.go
Normal file
30
internal/aghtls/aghtls.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Package aghtls contains utilities for work with TLS.
|
||||
package aghtls
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// SaferCipherSuites returns a set of default cipher suites with vulnerable and
|
||||
// weak cipher suites removed.
|
||||
func SaferCipherSuites() (safe []uint16) {
|
||||
for _, s := range tls.CipherSuites() {
|
||||
switch s.ID {
|
||||
case
|
||||
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
|
||||
// Less safe 3DES and CBC suites, go on.
|
||||
default:
|
||||
safe = append(safe, s.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return safe
|
||||
}
|
||||
@@ -119,23 +119,28 @@ func (l *Lease) UnmarshalJSON(data []byte) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerConfig - DHCP server configuration
|
||||
// field ordering is important -- yaml fields will mirror ordering from here
|
||||
// ServerConfig is the configuration for the DHCP server. The order of YAML
|
||||
// fields is important, since the YAML configuration file follows it.
|
||||
type ServerConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
InterfaceName string `yaml:"interface_name"`
|
||||
|
||||
Conf4 V4ServerConf `yaml:"dhcpv4"`
|
||||
Conf6 V6ServerConf `yaml:"dhcpv6"`
|
||||
|
||||
WorkDir string `yaml:"-"`
|
||||
DBFilePath string `yaml:"-"` // path to DB file
|
||||
|
||||
// Called when the configuration is changed by HTTP request
|
||||
ConfigModified func() `yaml:"-"`
|
||||
|
||||
// Register an HTTP handler
|
||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
|
||||
|
||||
Enabled bool `yaml:"enabled"`
|
||||
InterfaceName string `yaml:"interface_name"`
|
||||
|
||||
// LocalDomainName is the domain name used for DHCP hosts. For example,
|
||||
// a DHCP client with the hostname "myhost" can be addressed as "myhost.lan"
|
||||
// when LocalDomainName is "lan".
|
||||
LocalDomainName string `yaml:"local_domain_name"`
|
||||
|
||||
Conf4 V4ServerConf `yaml:"dhcpv4"`
|
||||
Conf6 V6ServerConf `yaml:"dhcpv6"`
|
||||
|
||||
WorkDir string `yaml:"-"`
|
||||
DBFilePath string `yaml:"-"`
|
||||
}
|
||||
|
||||
// OnLeaseChangedT is a callback for lease changes.
|
||||
@@ -156,7 +161,9 @@ type Server struct {
|
||||
srv4 DHCPServer
|
||||
srv6 DHCPServer
|
||||
|
||||
conf ServerConfig
|
||||
// TODO(a.garipov): Either create a separate type for the internal config or
|
||||
// just put the config values into Server.
|
||||
conf *ServerConfig
|
||||
|
||||
// Called when the leases DB is modified
|
||||
onLeaseChanged []OnLeaseChangedT
|
||||
@@ -181,14 +188,21 @@ type ServerInterface interface {
|
||||
}
|
||||
|
||||
// Create - create object
|
||||
func Create(conf ServerConfig) (s *Server, err error) {
|
||||
s = &Server{}
|
||||
func Create(conf *ServerConfig) (s *Server, err error) {
|
||||
s = &Server{
|
||||
conf: &ServerConfig{
|
||||
ConfigModified: conf.ConfigModified,
|
||||
|
||||
s.conf.Enabled = conf.Enabled
|
||||
s.conf.InterfaceName = conf.InterfaceName
|
||||
s.conf.HTTPRegister = conf.HTTPRegister
|
||||
s.conf.ConfigModified = conf.ConfigModified
|
||||
s.conf.DBFilePath = filepath.Join(conf.WorkDir, dbFilename)
|
||||
HTTPRegister: conf.HTTPRegister,
|
||||
|
||||
Enabled: conf.Enabled,
|
||||
InterfaceName: conf.InterfaceName,
|
||||
|
||||
LocalDomainName: conf.LocalDomainName,
|
||||
|
||||
DBFilePath: filepath.Join(conf.WorkDir, dbFilename),
|
||||
},
|
||||
}
|
||||
|
||||
if !webHandlersRegistered && s.conf.HTTPRegister != nil {
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -305,6 +319,7 @@ func (s *Server) notify(flags int) {
|
||||
func (s *Server) WriteDiskConfig(c *ServerConfig) {
|
||||
c.Enabled = s.conf.Enabled
|
||||
c.InterfaceName = s.conf.InterfaceName
|
||||
c.LocalDomainName = s.conf.LocalDomainName
|
||||
s.srv4.WriteDiskConfig4(&c.Conf4)
|
||||
s.srv6.WriteDiskConfig6(&c.Conf6)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func testNotify(flags uint32) {
|
||||
func TestDB(t *testing.T) {
|
||||
var err error
|
||||
s := Server{
|
||||
conf: ServerConfig{
|
||||
conf: &ServerConfig{
|
||||
DBFilePath: dbFilename,
|
||||
},
|
||||
}
|
||||
@@ -140,27 +140,27 @@ func TestNormalizeLeases(t *testing.T) {
|
||||
func TestV4Server_badRange(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErrMsg string
|
||||
gatewayIP net.IP
|
||||
subnetMask net.IP
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "gateway_in_range",
|
||||
gatewayIP: net.IP{192, 168, 10, 120},
|
||||
subnetMask: net.IP{255, 255, 255, 0},
|
||||
name: "gateway_in_range",
|
||||
wantErrMsg: "dhcpv4: gateway ip 192.168.10.120 in the ip range: " +
|
||||
"192.168.10.20-192.168.10.200",
|
||||
gatewayIP: net.IP{192, 168, 10, 120},
|
||||
subnetMask: net.IP{255, 255, 255, 0},
|
||||
}, {
|
||||
name: "outside_range_start",
|
||||
gatewayIP: net.IP{192, 168, 10, 1},
|
||||
subnetMask: net.IP{255, 255, 255, 240},
|
||||
name: "outside_range_start",
|
||||
wantErrMsg: "dhcpv4: range start 192.168.10.20 is outside network " +
|
||||
"192.168.10.1/28",
|
||||
}, {
|
||||
name: "outside_range_end",
|
||||
gatewayIP: net.IP{192, 168, 10, 1},
|
||||
subnetMask: net.IP{255, 255, 255, 224},
|
||||
subnetMask: net.IP{255, 255, 255, 240},
|
||||
}, {
|
||||
name: "outside_range_end",
|
||||
wantErrMsg: "dhcpv4: range end 192.168.10.200 is outside network " +
|
||||
"192.168.10.1/27",
|
||||
gatewayIP: net.IP{192, 168, 10, 1},
|
||||
subnetMask: net.IP{255, 255, 255, 224},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -575,12 +575,15 @@ func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) {
|
||||
log.Error("dhcp: removing db: %s", err)
|
||||
}
|
||||
|
||||
oldconf := s.conf
|
||||
s.conf = ServerConfig{
|
||||
WorkDir: oldconf.WorkDir,
|
||||
HTTPRegister: oldconf.HTTPRegister,
|
||||
ConfigModified: oldconf.ConfigModified,
|
||||
DBFilePath: oldconf.DBFilePath,
|
||||
s.conf = &ServerConfig{
|
||||
ConfigModified: s.conf.ConfigModified,
|
||||
|
||||
HTTPRegister: s.conf.HTTPRegister,
|
||||
|
||||
LocalDomainName: s.conf.LocalDomainName,
|
||||
|
||||
WorkDir: s.conf.WorkDir,
|
||||
DBFilePath: s.conf.DBFilePath,
|
||||
}
|
||||
|
||||
v4conf := V4ServerConf{
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
@@ -243,18 +244,15 @@ func (s *Server) createProxyConfig() (proxy.Config, error) {
|
||||
proxyConfig.FastestPingTimeout = s.conf.FastestTimeout.Duration
|
||||
}
|
||||
|
||||
if len(s.conf.BogusNXDomain) > 0 {
|
||||
for _, s := range s.conf.BogusNXDomain {
|
||||
ip := net.ParseIP(s)
|
||||
if ip == nil {
|
||||
log.Error("Invalid bogus IP: %s", s)
|
||||
} else {
|
||||
proxyConfig.BogusNXDomain = append(
|
||||
proxyConfig.BogusNXDomain,
|
||||
netutil.SingleIPSubnet(ip),
|
||||
)
|
||||
}
|
||||
for i, s := range s.conf.BogusNXDomain {
|
||||
subnet, err := netutil.ParseSubnet(s)
|
||||
if err != nil {
|
||||
log.Error("subnet at index %d: %s", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
proxyConfig.BogusNXDomain = append(proxyConfig.BogusNXDomain, subnet)
|
||||
}
|
||||
|
||||
// TLS settings
|
||||
@@ -429,6 +427,7 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
|
||||
|
||||
proxyConfig.TLSConfig = &tls.Config{
|
||||
GetCertificate: s.onGetCertificate,
|
||||
CipherSuites: aghtls.SaferCipherSuites(),
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
|
||||
@@ -215,9 +215,8 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||
ipToHost = netutil.NewIPMap(len(ll))
|
||||
|
||||
for _, l := range ll {
|
||||
// TODO(a.garipov): Remove this after we're finished
|
||||
// with the client hostname validations in the DHCP
|
||||
// server code.
|
||||
// TODO(a.garipov): Remove this after we're finished with the client
|
||||
// hostname validations in the DHCP server code.
|
||||
err = netutil.ValidateDomainName(l.Hostname)
|
||||
if err != nil {
|
||||
log.Debug(
|
||||
@@ -301,6 +300,8 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
|
||||
}
|
||||
|
||||
reqHost := strings.ToLower(q.Name)
|
||||
// TODO(a.garipov): Move everything related to DHCP local domain to the DHCP
|
||||
// server.
|
||||
host := strings.TrimSuffix(reqHost, s.localDomainSuffix)
|
||||
if host == reqHost {
|
||||
return resultCodeSuccess
|
||||
@@ -612,9 +613,9 @@ func (s *Server) processFilteringAfterResponse(ctx *dnsContext) (rc resultCode)
|
||||
d.Res.Answer = answer
|
||||
}
|
||||
default:
|
||||
// Check the response only if the it's from an upstream. Don't check
|
||||
// the response if the protection is disabled since dnsrewrite rules
|
||||
// aren't applied to it anyway.
|
||||
// Check the response only if it's from an upstream. Don't check the
|
||||
// response if the protection is disabled since dnsrewrite rules aren't
|
||||
// applied to it anyway.
|
||||
if !ctx.protectionEnabled || !ctx.responseFromUpstream || s.dnsFilter == nil {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ func TestServer_ProcessInternalHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_ProcessRestrictLocal(t *testing.T) {
|
||||
ups := &aghtest.TestUpstream{
|
||||
ups := &aghtest.Upstream{
|
||||
Reverse: map[string][]string{
|
||||
"251.252.253.254.in-addr.arpa.": {"host1.example.net."},
|
||||
"1.1.168.192.in-addr.arpa.": {"some.local-client."},
|
||||
@@ -339,7 +339,7 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
}, &aghtest.TestUpstream{
|
||||
}, &aghtest.Upstream{
|
||||
Reverse: map[string][]string{
|
||||
reqAddr: {locDomain},
|
||||
},
|
||||
|
||||
@@ -89,7 +89,7 @@ func createTestServer(
|
||||
defer s.serverLock.Unlock()
|
||||
|
||||
if localUps != nil {
|
||||
s.localResolvers.Config.UpstreamConfig.Upstreams = []upstream.Upstream{localUps}
|
||||
s.localResolvers.UpstreamConfig.Upstreams = []upstream.Upstream{localUps}
|
||||
s.conf.UsePrivateRDNS = true
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ func TestServer(t *testing.T) {
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
}, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.TestUpstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
@@ -316,7 +316,7 @@ func TestServerWithProtectionDisabled(t *testing.T) {
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
}, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.TestUpstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
@@ -339,7 +339,7 @@ func TestDoTServer(t *testing.T) {
|
||||
TLSListenAddrs: []*net.TCPAddr{{}},
|
||||
})
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.TestUpstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
@@ -369,7 +369,7 @@ func TestDoQServer(t *testing.T) {
|
||||
QUICListenAddrs: []*net.UDPAddr{{IP: net.IP{127, 0, 0, 1}}},
|
||||
})
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.TestUpstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
@@ -413,7 +413,7 @@ func TestServerRace(t *testing.T) {
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.TestUpstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
@@ -552,7 +552,7 @@ func TestServerCustomClientUpstream(t *testing.T) {
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
|
||||
s.conf.GetCustomUpstreamByClient = func(_ string) (conf *proxy.UpstreamConfig, err error) {
|
||||
ups := &aghtest.TestUpstream{
|
||||
ups := &aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"host.": {{192, 168, 0, 1}},
|
||||
},
|
||||
@@ -580,9 +580,9 @@ func TestServerCustomClientUpstream(t *testing.T) {
|
||||
}
|
||||
|
||||
// testCNAMEs is a map of names and CNAMEs necessary for the TestUpstream work.
|
||||
var testCNAMEs = map[string]string{
|
||||
"badhost.": "NULL.example.org.",
|
||||
"whitelist.example.org.": "NULL.example.org.",
|
||||
var testCNAMEs = map[string][]string{
|
||||
"badhost.": {"NULL.example.org."},
|
||||
"whitelist.example.org.": {"NULL.example.org."},
|
||||
}
|
||||
|
||||
// testIPv4 is a map of names and IPv4s necessary for the TestUpstream work.
|
||||
@@ -596,7 +596,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
}, nil)
|
||||
testUpstm := &aghtest.TestUpstream{
|
||||
testUpstm := &aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
IPv4: testIPv4,
|
||||
IPv6: nil,
|
||||
@@ -630,7 +630,7 @@ func TestBlockCNAME(t *testing.T) {
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.TestUpstream{
|
||||
&aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
IPv4: testIPv4,
|
||||
},
|
||||
@@ -640,14 +640,17 @@ func TestBlockCNAME(t *testing.T) {
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP).String()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
want bool
|
||||
}{{
|
||||
name: "block_request",
|
||||
host: "badhost.",
|
||||
// 'badhost' has a canonical name 'NULL.example.org' which is
|
||||
// blocked by filters: response is blocked.
|
||||
want: true,
|
||||
}, {
|
||||
name: "allowed",
|
||||
host: "whitelist.example.org.",
|
||||
// 'whitelist.example.org' has a canonical name
|
||||
// 'NULL.example.org' which is blocked by filters
|
||||
@@ -655,6 +658,7 @@ func TestBlockCNAME(t *testing.T) {
|
||||
// response isn't blocked.
|
||||
want: false,
|
||||
}, {
|
||||
name: "block_response",
|
||||
host: "example.org.",
|
||||
// 'example.org' has a canonical name 'cname1' with IP
|
||||
// 127.0.0.255 which is blocked by filters: response is blocked.
|
||||
@@ -662,9 +666,9 @@ func TestBlockCNAME(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("block_cname_"+tc.host, func(t *testing.T) {
|
||||
req := createTestMessage(tc.host)
|
||||
req := createTestMessage(tc.host)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
reply, err := dns.Exchange(req, addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -674,7 +678,7 @@ func TestBlockCNAME(t *testing.T) {
|
||||
|
||||
ans := reply.Answer[0]
|
||||
a, ok := ans.(*dns.A)
|
||||
require.Truef(t, ok, "got %T", ans)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.True(t, a.A.IsUnspecified())
|
||||
}
|
||||
@@ -695,7 +699,7 @@ func TestClientRulesForCNAMEMatching(t *testing.T) {
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.TestUpstream{
|
||||
&aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
IPv4: testIPv4,
|
||||
},
|
||||
@@ -931,9 +935,9 @@ func TestRewrite(t *testing.T) {
|
||||
}))
|
||||
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.TestUpstream{
|
||||
CName: map[string]string{
|
||||
"example.org": "somename",
|
||||
&aghtest.Upstream{
|
||||
CName: map[string][]string{
|
||||
"example.org": {"somename"},
|
||||
},
|
||||
IPv4: map[string][]net.IP{
|
||||
"example.org.": {{4, 3, 2, 1}},
|
||||
@@ -1193,12 +1197,12 @@ func TestNewServer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_Exchange(t *testing.T) {
|
||||
extUpstream := &aghtest.TestUpstream{
|
||||
extUpstream := &aghtest.Upstream{
|
||||
Reverse: map[string][]string{
|
||||
"1.1.1.1.in-addr.arpa.": {"one.one.one.one"},
|
||||
},
|
||||
}
|
||||
locUpstream := &aghtest.TestUpstream{
|
||||
locUpstream := &aghtest.Upstream{
|
||||
Reverse: map[string][]string{
|
||||
"1.1.168.192.in-addr.arpa.": {"local.domain"},
|
||||
"2.1.168.192.in-addr.arpa.": {},
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
||||
Preference: 32,
|
||||
}
|
||||
svcbVal := &rules.DNSSVCB{
|
||||
Params: map[string]string{"alpn": "h3", "dohpath": "/dns-query"},
|
||||
Params: map[string]string{"alpn": "h3"},
|
||||
Target: dns.Fqdn(domain),
|
||||
Priority: 32,
|
||||
}
|
||||
@@ -164,20 +164,10 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
||||
|
||||
require.Len(t, d.Res.Answer, 1)
|
||||
ans, ok := d.Res.Answer[0].(*dns.SVCB)
|
||||
|
||||
require.True(t, ok)
|
||||
require.Len(t, ans.Value, 2)
|
||||
|
||||
assert.ElementsMatch(
|
||||
t,
|
||||
[]dns.SVCBKey{dns.SVCB_ALPN, dns.SVCB_DOHPATH},
|
||||
[]dns.SVCBKey{ans.Value[0].Key(), ans.Value[1].Key()},
|
||||
)
|
||||
assert.ElementsMatch(
|
||||
t,
|
||||
[]string{svcbVal.Params["alpn"], svcbVal.Params["dohpath"]},
|
||||
[]string{ans.Value[0].String(), ans.Value[1].String()},
|
||||
)
|
||||
assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key())
|
||||
assert.Equal(t, svcbVal.Params["alpn"], ans.Value[0].String())
|
||||
assert.Equal(t, svcbVal.Target, ans.Target)
|
||||
assert.Equal(t, svcbVal.Priority, ans.Priority)
|
||||
})
|
||||
@@ -196,18 +186,8 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
||||
ans, ok := d.Res.Answer[0].(*dns.HTTPS)
|
||||
|
||||
require.True(t, ok)
|
||||
require.Len(t, ans.Value, 2)
|
||||
|
||||
assert.ElementsMatch(
|
||||
t,
|
||||
[]dns.SVCBKey{dns.SVCB_ALPN, dns.SVCB_DOHPATH},
|
||||
[]dns.SVCBKey{ans.Value[0].Key(), ans.Value[1].Key()},
|
||||
)
|
||||
assert.ElementsMatch(
|
||||
t,
|
||||
[]string{svcbVal.Params["alpn"], svcbVal.Params["dohpath"]},
|
||||
[]string{ans.Value[0].String(), ans.Value[1].String()},
|
||||
)
|
||||
assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key())
|
||||
assert.Equal(t, svcbVal.Params["alpn"], ans.Value[0].String())
|
||||
assert.Equal(t, svcbVal.Target, ans.Target)
|
||||
assert.Equal(t, svcbVal.Priority, ans.Priority)
|
||||
})
|
||||
|
||||
@@ -116,7 +116,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*filtering.Result, error) {
|
||||
|
||||
// checkHostRules checks the host against filters. It is safe for concurrent
|
||||
// use.
|
||||
func (s *Server) checkHostRules(host string, qtype uint16, setts *filtering.Settings) (
|
||||
func (s *Server) checkHostRules(host string, rrtype uint16, setts *filtering.Settings) (
|
||||
r *filtering.Result,
|
||||
err error,
|
||||
) {
|
||||
@@ -128,7 +128,7 @@ func (s *Server) checkHostRules(host string, qtype uint16, setts *filtering.Sett
|
||||
}
|
||||
|
||||
var res filtering.Result
|
||||
res, err = s.dnsFilter.CheckHostRules(host, qtype, setts)
|
||||
res, err = s.dnsFilter.CheckHostRules(host, rrtype, setts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -136,33 +136,36 @@ func (s *Server) checkHostRules(host string, qtype uint16, setts *filtering.Sett
|
||||
return &res, err
|
||||
}
|
||||
|
||||
// If response contains CNAME, A or AAAA records, we apply filtering to each
|
||||
// canonical host name or IP address. If this is a match, we set a new response
|
||||
// in d.Res and return.
|
||||
func (s *Server) filterDNSResponse(ctx *dnsContext) (*filtering.Result, error) {
|
||||
// filterDNSResponse checks each resource record of the response's answer
|
||||
// section from ctx and returns a non-nil res if at least one of canonnical
|
||||
// names or IP addresses in it matches the filtering rules.
|
||||
func (s *Server) filterDNSResponse(ctx *dnsContext) (res *filtering.Result, err error) {
|
||||
d := ctx.proxyCtx
|
||||
setts := ctx.setts
|
||||
if !setts.FilteringEnabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, a := range d.Res.Answer {
|
||||
host := ""
|
||||
|
||||
switch v := a.(type) {
|
||||
var rrtype uint16
|
||||
switch a := a.(type) {
|
||||
case *dns.CNAME:
|
||||
log.Debug("DNSFwd: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
|
||||
host = strings.TrimSuffix(v.Target, ".")
|
||||
|
||||
host = strings.TrimSuffix(a.Target, ".")
|
||||
rrtype = dns.TypeCNAME
|
||||
case *dns.A:
|
||||
host = v.A.String()
|
||||
log.Debug("DNSFwd: Checking record A (%s) for %s", host, v.Hdr.Name)
|
||||
|
||||
host = a.A.String()
|
||||
rrtype = dns.TypeA
|
||||
case *dns.AAAA:
|
||||
host = v.AAAA.String()
|
||||
log.Debug("DNSFwd: Checking record AAAA (%s) for %s", host, v.Hdr.Name)
|
||||
|
||||
host = a.AAAA.String()
|
||||
rrtype = dns.TypeAAAA
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
res, err := s.checkHostRules(host, d.Req.Question[0].Qtype, ctx.setts)
|
||||
log.Debug("dnsforward: checking %s %s for %s", dns.Type(rrtype), host, a.Header().Name)
|
||||
|
||||
res, err = s.checkHostRules(host, rrtype, setts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
|
||||
159
internal/dnsforward/filter_test.go
Normal file
159
internal/dnsforward/filter_test.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
|
||||
rules := `
|
||||
||blocked.domain^
|
||||
@@||allowed.domain^
|
||||
||cname.specific^$dnstype=~CNAME
|
||||
||0.0.0.1^$dnstype=~A
|
||||
||::1^$dnstype=~AAAA
|
||||
`
|
||||
|
||||
forwardConf := ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
FilteringConfig: FilteringConfig{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: BlockingModeDefault,
|
||||
},
|
||||
}
|
||||
filters := []filtering.Filter{{
|
||||
ID: 0, Data: []byte(rules),
|
||||
}}
|
||||
|
||||
f := filtering.New(&filtering.Config{}, filters)
|
||||
f.SetEnabled(true)
|
||||
|
||||
snd, err := aghnet.NewSubnetDetector()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, snd)
|
||||
|
||||
s, err := NewServer(DNSCreateParams{
|
||||
DHCPServer: &testDHCP{},
|
||||
DNSFilter: f,
|
||||
SubnetDetector: snd,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
s.conf = forwardConf
|
||||
err = s.Prepare(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.Upstream{
|
||||
CName: map[string][]string{
|
||||
"cname.exception.": {"cname.specific."},
|
||||
"should.block.": {"blocked.domain."},
|
||||
"allowed.first.": {"allowed.domain.", "blocked.domain."},
|
||||
"blocked.first.": {"blocked.domain.", "allowed.domain."},
|
||||
},
|
||||
IPv4: map[string][]net.IP{
|
||||
"a.exception.": {{0, 0, 0, 1}},
|
||||
},
|
||||
IPv6: map[string][]net.IP{
|
||||
"aaaa.exception.": {net.ParseIP("::1")},
|
||||
},
|
||||
},
|
||||
}
|
||||
startDeferStop(t, s)
|
||||
|
||||
testCases := []struct {
|
||||
req *dns.Msg
|
||||
name string
|
||||
wantAns []dns.RR
|
||||
}{{
|
||||
req: createTestMessage("cname.exception."),
|
||||
name: "cname_exception",
|
||||
wantAns: []dns.RR{&dns.CNAME{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "cname.exception.",
|
||||
Rrtype: dns.TypeCNAME,
|
||||
},
|
||||
Target: "cname.specific.",
|
||||
}},
|
||||
}, {
|
||||
req: createTestMessage("should.block."),
|
||||
name: "blocked_by_cname",
|
||||
wantAns: []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "should.block.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
A: netutil.IPv4Zero(),
|
||||
}},
|
||||
}, {
|
||||
req: createTestMessage("a.exception."),
|
||||
name: "a_exception",
|
||||
wantAns: []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "a.exception.",
|
||||
Rrtype: dns.TypeA,
|
||||
},
|
||||
A: net.IP{0, 0, 0, 1},
|
||||
}},
|
||||
}, {
|
||||
req: createTestMessageWithType("aaaa.exception.", dns.TypeAAAA),
|
||||
name: "aaaa_exception",
|
||||
wantAns: []dns.RR{&dns.AAAA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "aaaa.exception.",
|
||||
Rrtype: dns.TypeAAAA,
|
||||
},
|
||||
AAAA: net.ParseIP("::1"),
|
||||
}},
|
||||
}, {
|
||||
req: createTestMessage("allowed.first."),
|
||||
name: "allowed_first",
|
||||
wantAns: []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "allowed.first.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
A: netutil.IPv4Zero(),
|
||||
}},
|
||||
}, {
|
||||
req: createTestMessage("blocked.first."),
|
||||
name: "blocked_first",
|
||||
wantAns: []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "blocked.first.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
A: netutil.IPv4Zero(),
|
||||
}},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
dctx := &proxy.DNSContext{
|
||||
Proto: proxy.ProtoUDP,
|
||||
Req: tc.req,
|
||||
Addr: &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 1},
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err = s.handleDNSRequest(nil, dctx)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dctx.Res)
|
||||
|
||||
assert.Equal(t, tc.wantAns, dctx.Res.Answer)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
@@ -41,7 +43,7 @@ type dnsConfig struct {
|
||||
LocalPTRUpstreams *[]string `json:"local_ptr_upstreams"`
|
||||
}
|
||||
|
||||
func (s *Server) getDNSConfig() dnsConfig {
|
||||
func (s *Server) getDNSConfig() (c *dnsConfig) {
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
@@ -70,7 +72,7 @@ func (s *Server) getDNSConfig() dnsConfig {
|
||||
upstreamMode = "parallel"
|
||||
}
|
||||
|
||||
return dnsConfig{
|
||||
return &dnsConfig{
|
||||
Upstreams: &upstreams,
|
||||
UpstreamsFile: &upstreamFile,
|
||||
Bootstraps: &bootstraps,
|
||||
@@ -106,7 +108,7 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
// since there is no need to omit it while decoding from JSON.
|
||||
DefautLocalPTRUpstreams []string `json:"default_local_ptr_upstreams,omitempty"`
|
||||
}{
|
||||
dnsConfig: s.getDNSConfig(),
|
||||
dnsConfig: *s.getDNSConfig(),
|
||||
DefautLocalPTRUpstreams: defLocalPTRUps,
|
||||
}
|
||||
|
||||
@@ -138,39 +140,63 @@ func (req *dnsConfig) checkBlockingMode() bool {
|
||||
}
|
||||
|
||||
func (req *dnsConfig) checkUpstreamsMode() bool {
|
||||
if req.UpstreamMode == nil {
|
||||
return true
|
||||
}
|
||||
valid := []string{"", "fastest_addr", "parallel"}
|
||||
|
||||
for _, valid := range []string{
|
||||
"",
|
||||
"fastest_addr",
|
||||
"parallel",
|
||||
} {
|
||||
if *req.UpstreamMode == valid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return req.UpstreamMode == nil || stringutil.InSlice(valid, *req.UpstreamMode)
|
||||
}
|
||||
|
||||
func (req *dnsConfig) checkBootstrap() (string, error) {
|
||||
func (req *dnsConfig) checkBootstrap() (err error) {
|
||||
if req.Bootstraps == nil {
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, boot := range *req.Bootstraps {
|
||||
if boot == "" {
|
||||
return boot, fmt.Errorf("invalid bootstrap server address: empty")
|
||||
var b string
|
||||
defer func() { err = errors.Annotate(err, "checking bootstrap %s: invalid address: %w", b) }()
|
||||
|
||||
for _, b = range *req.Bootstraps {
|
||||
if b == "" {
|
||||
return errors.Error("empty")
|
||||
}
|
||||
|
||||
if _, err := upstream.NewResolver(boot, nil); err != nil {
|
||||
return boot, fmt.Errorf("invalid bootstrap server address: %w", err)
|
||||
if _, err = upstream.NewResolver(b, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// validate returns an error if any field of req is invalid.
|
||||
func (req *dnsConfig) validate(snd *aghnet.SubnetDetector) (err error) {
|
||||
if req.Upstreams != nil {
|
||||
err = ValidateUpstreams(*req.Upstreams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating upstream servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if req.LocalPTRUpstreams != nil {
|
||||
err = ValidateUpstreamsPrivate(*req.LocalPTRUpstreams, snd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating private upstream servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = req.checkBootstrap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case !req.checkBlockingMode():
|
||||
return errors.Error("blocking_mode: incorrect value")
|
||||
case !req.checkUpstreamsMode():
|
||||
return errors.Error("upstream_mode: incorrect value")
|
||||
case !req.checkCacheTTL():
|
||||
return errors.Error("cache_ttl_min must be less or equal than cache_ttl_max")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (req *dnsConfig) checkCacheTTL() bool {
|
||||
@@ -190,69 +216,33 @@ func (req *dnsConfig) checkCacheTTL() bool {
|
||||
}
|
||||
|
||||
func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req := dnsConfig{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
req := &dnsConfig{}
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "json Encode: %s", err)
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "decoding request: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if req.Upstreams != nil {
|
||||
if err = ValidateUpstreams(*req.Upstreams); err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var errBoot string
|
||||
if errBoot, err = req.checkBootstrap(); err != nil {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"%s can not be used as bootstrap dns cause: %s",
|
||||
errBoot,
|
||||
err,
|
||||
)
|
||||
err = req.validate(s.subnetDetector)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case !req.checkBlockingMode():
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "blocking_mode: incorrect value")
|
||||
|
||||
return
|
||||
case !req.checkUpstreamsMode():
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "upstream_mode: incorrect value")
|
||||
|
||||
return
|
||||
case !req.checkCacheTTL():
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"cache_ttl_min must be less or equal than cache_ttl_max",
|
||||
)
|
||||
|
||||
return
|
||||
default:
|
||||
// Go on.
|
||||
}
|
||||
|
||||
restart := s.setConfig(req)
|
||||
s.conf.ConfigModified()
|
||||
|
||||
if restart {
|
||||
if err = s.Reconfigure(nil); err != nil {
|
||||
err = s.Reconfigure(nil)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) setConfigRestartable(dc dnsConfig) (restart bool) {
|
||||
func (s *Server) setConfigRestartable(dc *dnsConfig) (restart bool) {
|
||||
if dc.Upstreams != nil {
|
||||
s.conf.UpstreamDNS = *dc.Upstreams
|
||||
restart = true
|
||||
@@ -273,9 +263,9 @@ func (s *Server) setConfigRestartable(dc dnsConfig) (restart bool) {
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.RateLimit != nil {
|
||||
restart = restart || s.conf.Ratelimit != *dc.RateLimit
|
||||
if dc.RateLimit != nil && s.conf.Ratelimit != *dc.RateLimit {
|
||||
s.conf.Ratelimit = *dc.RateLimit
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.EDNSCSEnabled != nil {
|
||||
@@ -306,7 +296,7 @@ func (s *Server) setConfigRestartable(dc dnsConfig) (restart bool) {
|
||||
return restart
|
||||
}
|
||||
|
||||
func (s *Server) setConfig(dc dnsConfig) (restart bool) {
|
||||
func (s *Server) setConfig(dc *dnsConfig) (restart bool) {
|
||||
s.serverLock.Lock()
|
||||
defer s.serverLock.Unlock()
|
||||
|
||||
@@ -353,58 +343,123 @@ type upstreamJSON struct {
|
||||
PrivateUpstreams []string `json:"private_upstream"`
|
||||
}
|
||||
|
||||
// IsCommentOrEmpty returns true of the string starts with a "#" character or is
|
||||
// an empty string. This function is useful for filtering out non-upstream
|
||||
// lines from upstream configs.
|
||||
// IsCommentOrEmpty returns true if s starts with a "#" character or is empty.
|
||||
// This function is useful for filtering out non-upstream lines from upstream
|
||||
// configs.
|
||||
func IsCommentOrEmpty(s string) (ok bool) {
|
||||
return len(s) == 0 || s[0] == '#'
|
||||
}
|
||||
|
||||
// LocalNetChecker is used to check if the IP address belongs to a local
|
||||
// network.
|
||||
type LocalNetChecker interface {
|
||||
// IsLocallyServedNetwork returns true if ip is contained in any of address
|
||||
// registries defined by RFC 6303.
|
||||
IsLocallyServedNetwork(ip net.IP) (ok bool)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ LocalNetChecker = (*aghnet.SubnetDetector)(nil)
|
||||
|
||||
// newUpstreamConfig validates upstreams and returns an appropriate upstream
|
||||
// configuration or nil if it can't be built.
|
||||
//
|
||||
// TODO(e.burkov): Perhaps proxy.ParseUpstreamsConfig should validate upstreams
|
||||
// slice already so that this function may be considered useless.
|
||||
func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err error) {
|
||||
// No need to validate comments and empty lines.
|
||||
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
|
||||
if len(upstreams) == 0 {
|
||||
// Consider this case valid since it means the default server should be
|
||||
// used.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
conf, err = proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{Bootstrap: []string{}, Timeout: DefaultTimeout},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(conf.Upstreams) == 0 {
|
||||
return nil, errors.Error("no default upstreams specified")
|
||||
}
|
||||
|
||||
for _, u := range upstreams {
|
||||
_, err = validateUpstream(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// ValidateUpstreams validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified.
|
||||
//
|
||||
// TODO(e.burkov): Move into aghnet or even into dnsproxy.
|
||||
// TODO(e.burkov): Move into aghnet or even into dnsproxy.
|
||||
func ValidateUpstreams(upstreams []string) (err error) {
|
||||
// No need to validate comments
|
||||
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
|
||||
_, err = newUpstreamConfig(upstreams)
|
||||
|
||||
// Consider this case valid because defaultDNS will be used
|
||||
if len(upstreams) == 0 {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// stringKeysSorted returns the sorted slice of string keys of m.
|
||||
//
|
||||
// TODO(e.burkov): Use generics in Go 1.18. Move into golibs.
|
||||
func stringKeysSorted(m map[string][]upstream.Upstream) (sorted []string) {
|
||||
sorted = make([]string, 0, len(m))
|
||||
for s := range m {
|
||||
sorted = append(sorted, s)
|
||||
}
|
||||
|
||||
_, err = proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: []string{},
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
)
|
||||
sort.Strings(sorted)
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
// ValidateUpstreamsPrivate validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified. It also
|
||||
// checks each domain of domain-specific upstreams for being ARPA pointing to
|
||||
// a locally-served network. lnc must not be nil.
|
||||
func ValidateUpstreamsPrivate(upstreams []string, lnc LocalNetChecker) (err error) {
|
||||
conf, err := newUpstreamConfig(upstreams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var defaultUpstreamFound bool
|
||||
for _, u := range upstreams {
|
||||
var useDefault bool
|
||||
useDefault, err = validateUpstream(u)
|
||||
if conf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, domain := range stringKeysSorted(conf.DomainReservedUpstreams) {
|
||||
var subnet *net.IPNet
|
||||
subnet, err = netutil.SubnetFromReversedAddr(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
errs = append(errs, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !defaultUpstreamFound {
|
||||
defaultUpstreamFound = useDefault
|
||||
if !lnc.IsLocallyServedNetwork(subnet.IP) {
|
||||
errs = append(
|
||||
errs,
|
||||
fmt.Errorf("arpa domain %q should point to a locally-served network", domain),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if !defaultUpstreamFound {
|
||||
return fmt.Errorf("no default upstreams specified")
|
||||
if len(errs) > 0 {
|
||||
return errors.List("checking domain-specific upstreams", errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var protocols = []string{"tls://", "https://", "tcp://", "sdns://", "quic://"}
|
||||
var protocols = []string{"udp://", "tcp://", "tls://", "https://", "sdns://", "quic://"}
|
||||
|
||||
func validateUpstream(u string) (useDefault bool, err error) {
|
||||
// Check if the user tries to specify upstream for domain.
|
||||
|
||||
@@ -184,12 +184,11 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "upstream_dns_bad",
|
||||
wantSet: `wrong upstreams specification: bad ipport address "!!!": address !!!: ` +
|
||||
`missing port in address`,
|
||||
wantSet: `validating upstream servers: bad ipport address "!!!": ` +
|
||||
`address !!!: missing port in address`,
|
||||
}, {
|
||||
name: "bootstraps_bad",
|
||||
wantSet: `a can not be used as bootstrap dns cause: ` +
|
||||
`invalid bootstrap server address: ` +
|
||||
wantSet: `checking bootstrap a: invalid address: ` +
|
||||
`Resolver a is not eligible to be a bootstrap DNS server`,
|
||||
}, {
|
||||
name: "cache_bad_ttl",
|
||||
@@ -200,6 +199,10 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
||||
}, {
|
||||
name: "local_ptr_upstreams_good",
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "local_ptr_upstreams_bad",
|
||||
wantSet: `validating private upstream servers: checking domain-specific upstreams: ` +
|
||||
`bad arpa domain name "non.arpa": not a reversed ip network`,
|
||||
}, {
|
||||
name: "local_ptr_upstreams_null",
|
||||
wantSet: "",
|
||||
@@ -303,6 +306,14 @@ func TestValidateUpstream(t *testing.T) {
|
||||
name: "valid_default",
|
||||
upstream: "sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
wantErr: ``,
|
||||
}, {
|
||||
wantDef: assert.True,
|
||||
name: "default_udp_host",
|
||||
upstream: "udp://dns.google",
|
||||
}, {
|
||||
wantDef: assert.True,
|
||||
name: "default_udp_ip",
|
||||
upstream: "udp://8.8.8.8",
|
||||
}, {
|
||||
wantDef: assert.False,
|
||||
name: "valid",
|
||||
@@ -350,7 +361,7 @@ func TestValidateUpstream(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUpstreamsSet(t *testing.T) {
|
||||
func TestValidateUpstreams(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErr string
|
||||
@@ -397,3 +408,52 @@ func TestValidateUpstreamsSet(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUpstreamsPrivate(t *testing.T) {
|
||||
snd, err := aghnet.NewSubnetDetector()
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErr string
|
||||
u string
|
||||
}{{
|
||||
name: "success_address",
|
||||
wantErr: ``,
|
||||
u: "[/1.0.0.127.in-addr.arpa/]#",
|
||||
}, {
|
||||
name: "success_subnet",
|
||||
wantErr: ``,
|
||||
u: "[/127.in-addr.arpa/]#",
|
||||
}, {
|
||||
name: "not_arpa_subnet",
|
||||
wantErr: `checking domain-specific upstreams: ` +
|
||||
`bad arpa domain name "hello.world": not a reversed ip network`,
|
||||
u: "[/hello.world/]#",
|
||||
}, {
|
||||
name: "non-private_arpa_address",
|
||||
wantErr: `checking domain-specific upstreams: ` +
|
||||
`arpa domain "1.2.3.4.in-addr.arpa." should point to a locally-served network`,
|
||||
u: "[/1.2.3.4.in-addr.arpa/]#",
|
||||
}, {
|
||||
name: "non-private_arpa_subnet",
|
||||
wantErr: `checking domain-specific upstreams: ` +
|
||||
`arpa domain "128.in-addr.arpa." should point to a locally-served network`,
|
||||
u: "[/128.in-addr.arpa/]#",
|
||||
}, {
|
||||
name: "several_bad",
|
||||
wantErr: `checking domain-specific upstreams: 2 errors: ` +
|
||||
`"arpa domain \"1.2.3.4.in-addr.arpa.\" should point to a locally-served network", ` +
|
||||
`"bad arpa domain name \"non.arpa\": not a reversed ip network"`,
|
||||
u: "[/non.arpa/1.2.3.4.in-addr.arpa/127.in-addr.arpa/]#",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
set := []string{"192.168.0.1", tc.u}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err = ValidateUpstreamsPrivate(set, snd)
|
||||
testutil.AssertErrorMsg(t, tc.wantErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,55 +41,65 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
|
||||
// uninitialized while in use. This can happen after proxy server has been
|
||||
// stopped, but its workers haven't yet exited.
|
||||
if shouldLog && s.queryLog != nil {
|
||||
p := &querylog.AddParams{
|
||||
Question: msg,
|
||||
Answer: pctx.Res,
|
||||
OrigAnswer: dctx.origResp,
|
||||
Result: dctx.result,
|
||||
Elapsed: elapsed,
|
||||
ClientID: dctx.clientID,
|
||||
ClientIP: ip,
|
||||
AuthenticatedData: dctx.responseAD,
|
||||
}
|
||||
|
||||
switch pctx.Proto {
|
||||
case proxy.ProtoHTTPS:
|
||||
p.ClientProto = querylog.ClientProtoDoH
|
||||
case proxy.ProtoQUIC:
|
||||
p.ClientProto = querylog.ClientProtoDoQ
|
||||
case proxy.ProtoTLS:
|
||||
p.ClientProto = querylog.ClientProtoDoT
|
||||
case proxy.ProtoDNSCrypt:
|
||||
p.ClientProto = querylog.ClientProtoDNSCrypt
|
||||
default:
|
||||
// Consider this a plain DNS-over-UDP or DNS-over-TCP request.
|
||||
}
|
||||
|
||||
if pctx.Upstream != nil {
|
||||
p.Upstream = pctx.Upstream.Address()
|
||||
} else if cachedUps := pctx.CachedUpstreamAddr; cachedUps != "" {
|
||||
p.Upstream = pctx.CachedUpstreamAddr
|
||||
p.Cached = true
|
||||
}
|
||||
|
||||
s.queryLog.Add(p)
|
||||
s.logQuery(dctx, pctx, elapsed, ip)
|
||||
}
|
||||
|
||||
s.updateStats(dctx, elapsed, *dctx.result, ip)
|
||||
if s.stats != nil {
|
||||
s.updateStats(dctx, elapsed, *dctx.result, ip)
|
||||
}
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// logQuery pushes the request details into the query log.
|
||||
func (s *Server) logQuery(
|
||||
dctx *dnsContext,
|
||||
pctx *proxy.DNSContext,
|
||||
elapsed time.Duration,
|
||||
ip net.IP,
|
||||
) {
|
||||
p := &querylog.AddParams{
|
||||
Question: pctx.Req,
|
||||
ReqECS: pctx.ReqECS,
|
||||
Answer: pctx.Res,
|
||||
OrigAnswer: dctx.origResp,
|
||||
Result: dctx.result,
|
||||
Elapsed: elapsed,
|
||||
ClientID: dctx.clientID,
|
||||
ClientIP: ip,
|
||||
AuthenticatedData: dctx.responseAD,
|
||||
}
|
||||
|
||||
switch pctx.Proto {
|
||||
case proxy.ProtoHTTPS:
|
||||
p.ClientProto = querylog.ClientProtoDoH
|
||||
case proxy.ProtoQUIC:
|
||||
p.ClientProto = querylog.ClientProtoDoQ
|
||||
case proxy.ProtoTLS:
|
||||
p.ClientProto = querylog.ClientProtoDoT
|
||||
case proxy.ProtoDNSCrypt:
|
||||
p.ClientProto = querylog.ClientProtoDNSCrypt
|
||||
default:
|
||||
// Consider this a plain DNS-over-UDP or DNS-over-TCP request.
|
||||
}
|
||||
|
||||
if pctx.Upstream != nil {
|
||||
p.Upstream = pctx.Upstream.Address()
|
||||
} else if cachedUps := pctx.CachedUpstreamAddr; cachedUps != "" {
|
||||
p.Upstream = pctx.CachedUpstreamAddr
|
||||
p.Cached = true
|
||||
}
|
||||
|
||||
s.queryLog.Add(p)
|
||||
}
|
||||
|
||||
// updatesStats writes the request into statistics.
|
||||
func (s *Server) updateStats(
|
||||
ctx *dnsContext,
|
||||
elapsed time.Duration,
|
||||
res filtering.Result,
|
||||
clientIP net.IP,
|
||||
) {
|
||||
if s.stats == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pctx := ctx.proxyCtx
|
||||
e := stats.Entry{}
|
||||
e.Domain = strings.ToLower(pctx.Req.Question[0].Name)
|
||||
|
||||
@@ -32,16 +32,12 @@ func (s *Server) genAnswerHTTPS(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns.HTT
|
||||
// github.com/miekg/dns module.
|
||||
var strToSVCBKey = map[string]dns.SVCBKey{
|
||||
"alpn": dns.SVCB_ALPN,
|
||||
"ech": dns.SVCB_ECHCONFIG,
|
||||
"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,
|
||||
|
||||
// TODO(a.garipov): This is the previous name for the parameter that has
|
||||
// since been changed. Remove this in v0.109.0.
|
||||
"echconfig": dns.SVCB_ECHCONFIG,
|
||||
}
|
||||
|
||||
// svcbKeyHandler is a handler for one SVCB parameter key.
|
||||
@@ -55,10 +51,10 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
|
||||
}
|
||||
},
|
||||
|
||||
"ech": func(valStr string) (val dns.SVCBKeyValue) {
|
||||
"echconfig": func(valStr string) (val dns.SVCBKeyValue) {
|
||||
ech, err := base64.StdEncoding.DecodeString(valStr)
|
||||
if err != nil {
|
||||
log.Debug("can't parse svcb/https ech: %s; ignoring", err)
|
||||
log.Debug("can't parse svcb/https echconfig: %s; ignoring", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -123,32 +119,6 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
|
||||
Port: uint16(port64),
|
||||
}
|
||||
},
|
||||
|
||||
// TODO(a.garipov): This is the previous name for the parameter that has
|
||||
// since been changed. Remove this in v0.109.0.
|
||||
"echconfig": func(valStr string) (val dns.SVCBKeyValue) {
|
||||
log.Info(
|
||||
`warning: svcb/https record parameter name "echconfig" is deprecated; ` +
|
||||
`use "ech" instead`,
|
||||
)
|
||||
|
||||
ech, err := base64.StdEncoding.DecodeString(valStr)
|
||||
if err != nil {
|
||||
log.Debug("can't parse svcb/https ech: %s; ignoring", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return &dns.SVCBECHConfig{
|
||||
ECH: ech,
|
||||
}
|
||||
},
|
||||
|
||||
"dohpath": func(valStr string) (val dns.SVCBKeyValue) {
|
||||
return &dns.SVCBDoHPath{
|
||||
Template: valStr,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// genAnswerSVCB returns a properly initialized SVCB resource record.
|
||||
|
||||
@@ -87,18 +87,14 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) {
|
||||
svcb: dnssvcb("alpn", "h3"),
|
||||
want: wantsvcb(&dns.SVCBAlpn{Alpn: []string{"h3"}}),
|
||||
name: "alpn",
|
||||
}, {
|
||||
svcb: dnssvcb("ech", "AAAA"),
|
||||
want: wantsvcb(&dns.SVCBECHConfig{ECH: []byte{0, 0, 0}}),
|
||||
name: "ech",
|
||||
}, {
|
||||
svcb: dnssvcb("echconfig", "AAAA"),
|
||||
want: wantsvcb(&dns.SVCBECHConfig{ECH: []byte{0, 0, 0}}),
|
||||
name: "ech_deprecated",
|
||||
name: "echconfig",
|
||||
}, {
|
||||
svcb: dnssvcb("echconfig", "%BAD%"),
|
||||
want: wantsvcb(nil),
|
||||
name: "ech_invalid",
|
||||
name: "echconfig_invalid",
|
||||
}, {
|
||||
svcb: dnssvcb("ipv4hint", "127.0.0.1"),
|
||||
want: wantsvcb(&dns.SVCBIPv4Hint{Hint: []net.IP{ip4}}),
|
||||
@@ -127,10 +123,6 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) {
|
||||
svcb: dnssvcb("no-default-alpn", ""),
|
||||
want: wantsvcb(&dns.SVCBNoDefaultAlpn{}),
|
||||
name: "no_default_alpn",
|
||||
}, {
|
||||
svcb: dnssvcb("dohpath", "/dns-query"),
|
||||
want: wantsvcb(&dns.SVCBDoHPath{Template: "/dns-query"}),
|
||||
name: "dohpath",
|
||||
}, {
|
||||
svcb: dnssvcb("port", "8080"),
|
||||
want: wantsvcb(&dns.SVCBPort{Port: 8080}),
|
||||
|
||||
@@ -520,6 +520,43 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"local_ptr_upstreams_bad": {
|
||||
"req": {
|
||||
"local_ptr_upstreams": [
|
||||
"123.123.123.123",
|
||||
"[/non.arpa/]#"
|
||||
]
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"cache_optimistic": false,
|
||||
"resolve_clients": false,
|
||||
"use_private_ptr_resolvers": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"local_ptr_upstreams_null": {
|
||||
"req": {
|
||||
"local_ptr_upstreams": null
|
||||
|
||||
@@ -19,12 +19,9 @@ type svc struct {
|
||||
// Keep in sync with:
|
||||
// client/src/helpers/constants.js
|
||||
// client/src/components/ui/Icons.js
|
||||
var serviceRulesArray = []svc{{
|
||||
name: "whatsapp",
|
||||
rules: []string{"||whatsapp.net^", "||whatsapp.com^"},
|
||||
}, {
|
||||
name: "facebook",
|
||||
rules: []string{
|
||||
var serviceRulesArray = []svc{
|
||||
{"whatsapp", []string{"||whatsapp.net^", "||whatsapp.com^"}},
|
||||
{"facebook", []string{
|
||||
"||facebook.com^",
|
||||
"||facebook.net^",
|
||||
"||fbcdn.net^",
|
||||
@@ -36,13 +33,9 @@ var serviceRulesArray = []svc{{
|
||||
"||facebookcorewwwi.onion^",
|
||||
"||fbcdn.com^",
|
||||
"||fb.watch^",
|
||||
},
|
||||
}, {
|
||||
name: "twitter",
|
||||
rules: []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"},
|
||||
}, {
|
||||
name: "youtube",
|
||||
rules: []string{
|
||||
}},
|
||||
{"twitter", []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"}},
|
||||
{"youtube", []string{
|
||||
"||youtube.com^",
|
||||
"||ytimg.com^",
|
||||
"||youtu.be^",
|
||||
@@ -50,75 +43,35 @@ var serviceRulesArray = []svc{{
|
||||
"||youtubei.googleapis.com^",
|
||||
"||youtube-nocookie.com^",
|
||||
"||youtube",
|
||||
},
|
||||
}, {
|
||||
name: "twitch",
|
||||
rules: []string{"||twitch.tv^", "||ttvnw.net^", "||jtvnw.net^", "||twitchcdn.net^"},
|
||||
}, {
|
||||
name: "netflix",
|
||||
rules: []string{
|
||||
"||nflxext.com^",
|
||||
"||netflix.com^",
|
||||
"||nflximg.net^",
|
||||
"||nflxvideo.net^",
|
||||
"||nflxso.net^",
|
||||
},
|
||||
}, {
|
||||
name: "instagram",
|
||||
rules: []string{"||instagram.com^", "||cdninstagram.com^"},
|
||||
}, {
|
||||
name: "snapchat",
|
||||
rules: []string{
|
||||
}},
|
||||
{"twitch", []string{"||twitch.tv^", "||ttvnw.net^", "||jtvnw.net^", "||twitchcdn.net^"}},
|
||||
{"netflix", []string{"||nflxext.com^", "||netflix.com^", "||nflximg.net^", "||nflxvideo.net^", "||nflxso.net^"}},
|
||||
{"instagram", []string{"||instagram.com^", "||cdninstagram.com^"}},
|
||||
{"snapchat", []string{
|
||||
"||snapchat.com^",
|
||||
"||sc-cdn.net^",
|
||||
"||snap-dev.net^",
|
||||
"||snapkit.co",
|
||||
"||snapads.com^",
|
||||
"||impala-media-production.s3.amazonaws.com^",
|
||||
},
|
||||
}, {
|
||||
name: "discord",
|
||||
rules: []string{
|
||||
"||discord.gg^",
|
||||
"||discordapp.net^",
|
||||
"||discordapp.com^",
|
||||
"||discord.com^",
|
||||
"||discord.media^",
|
||||
},
|
||||
}, {
|
||||
name: "ok",
|
||||
rules: []string{"||ok.ru^"},
|
||||
}, {
|
||||
name: "skype",
|
||||
rules: []string{"||skype.com^", "||skypeassets.com^"},
|
||||
}, {
|
||||
name: "vk",
|
||||
rules: []string{"||vk.com^", "||userapi.com^", "||vk-cdn.net^", "||vkuservideo.net^"},
|
||||
}, {
|
||||
name: "origin",
|
||||
rules: []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"},
|
||||
}, {
|
||||
name: "steam",
|
||||
rules: []string{
|
||||
}},
|
||||
{"discord", []string{"||discord.gg^", "||discordapp.net^", "||discordapp.com^", "||discord.com^", "||discord.media^"}},
|
||||
{"ok", []string{"||ok.ru^"}},
|
||||
{"skype", []string{"||skype.com^", "||skypeassets.com^"}},
|
||||
{"vk", []string{"||vk.com^", "||userapi.com^", "||vk-cdn.net^", "||vkuservideo.net^"}},
|
||||
{"origin", []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"}},
|
||||
{"steam", []string{
|
||||
"||steam.com^",
|
||||
"||steampowered.com^",
|
||||
"||steamcommunity.com^",
|
||||
"||steamstatic.com^",
|
||||
"||steamstore-a.akamaihd.net^",
|
||||
"||steamcdn-a.akamaihd.net^",
|
||||
},
|
||||
}, {
|
||||
name: "epic_games",
|
||||
rules: []string{"||epicgames.com^", "||easyanticheat.net^", "||easy.ac^", "||eac-cdn.com^"},
|
||||
}, {
|
||||
name: "reddit",
|
||||
rules: []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"},
|
||||
}, {
|
||||
name: "mail_ru",
|
||||
rules: []string{"||mail.ru^"},
|
||||
}, {
|
||||
name: "cloudflare",
|
||||
rules: []string{
|
||||
}},
|
||||
{"epic_games", []string{"||epicgames.com^", "||easyanticheat.net^", "||easy.ac^", "||eac-cdn.com^"}},
|
||||
{"reddit", []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"}},
|
||||
{"mail_ru", []string{"||mail.ru^"}},
|
||||
{"cloudflare", []string{
|
||||
"||cloudflare.com^",
|
||||
"||cloudflare-dns.com^",
|
||||
"||cloudflare.net^",
|
||||
@@ -133,10 +86,8 @@ var serviceRulesArray = []svc{{
|
||||
"||warp.plus^",
|
||||
"||1.1.1.1^",
|
||||
"||dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion^",
|
||||
},
|
||||
}, {
|
||||
name: "amazon",
|
||||
rules: []string{
|
||||
}},
|
||||
{"amazon", []string{
|
||||
"||amazon.com^",
|
||||
"||media-amazon.com^",
|
||||
"||primevideo.com^",
|
||||
@@ -163,10 +114,8 @@ var serviceRulesArray = []svc{{
|
||||
"||amazon.co.uk^",
|
||||
"||createspace.com^",
|
||||
"||aws",
|
||||
},
|
||||
}, {
|
||||
name: "ebay",
|
||||
rules: []string{
|
||||
}},
|
||||
{"ebay", []string{
|
||||
"||ebay.com^",
|
||||
"||ebayimg.com^",
|
||||
"||ebaystatic.com^",
|
||||
@@ -192,10 +141,8 @@ var serviceRulesArray = []svc{{
|
||||
"||ebay.com.my^",
|
||||
"||ebay.com.sg^",
|
||||
"||ebay.co.uk^",
|
||||
},
|
||||
}, {
|
||||
name: "tiktok",
|
||||
rules: []string{
|
||||
}},
|
||||
{"tiktok", []string{
|
||||
"||tiktok.com^",
|
||||
"||tiktokcdn.com^",
|
||||
"||musical.ly^",
|
||||
@@ -215,55 +162,59 @@ var serviceRulesArray = []svc{{
|
||||
"||bytedance.map.fastly.net^",
|
||||
"||douyin.com^",
|
||||
"||tiktokv.com^",
|
||||
},
|
||||
}, {
|
||||
name: "vimeo",
|
||||
rules: []string{"||vimeo.com^", "||vimeocdn.com^", "*vod-adaptive.akamaized.net^"},
|
||||
}, {
|
||||
name: "pinterest",
|
||||
rules: []string{"||pinterest.*^", "||pinimg.com^"},
|
||||
}, {
|
||||
name: "imgur",
|
||||
rules: []string{"||imgur.com^"},
|
||||
}, {
|
||||
name: "dailymotion",
|
||||
rules: []string{"||dailymotion.com^", "||dm-event.net^", "||dmcdn.net^"},
|
||||
}, {
|
||||
name: "qq",
|
||||
rules: []string{
|
||||
// Block qq.com and subdomains excluding WeChat's domains.
|
||||
"||qq.com^$denyallow=wx.qq.com|weixin.qq.com",
|
||||
}},
|
||||
{"vimeo", []string{
|
||||
"||vimeo.com^",
|
||||
"||vimeocdn.com^",
|
||||
"*vod-adaptive.akamaized.net^",
|
||||
}},
|
||||
{"pinterest", []string{
|
||||
"||pinterest.*^",
|
||||
"||pinimg.com^",
|
||||
}},
|
||||
{"imgur", []string{
|
||||
"||imgur.com^",
|
||||
}},
|
||||
{"dailymotion", []string{
|
||||
"||dailymotion.com^",
|
||||
"||dm-event.net^",
|
||||
"||dmcdn.net^",
|
||||
}},
|
||||
{"qq", []string{
|
||||
// block qq.com and subdomains excluding WeChat domains
|
||||
"^(?!weixin|wx)([^.]+\\.)?qq\\.com$",
|
||||
"||qqzaixian.com^",
|
||||
},
|
||||
}, {
|
||||
name: "wechat",
|
||||
rules: []string{"||wechat.com^", "||weixin.qq.com^", "||wx.qq.com^"},
|
||||
}, {
|
||||
name: "viber",
|
||||
rules: []string{"||viber.com^"},
|
||||
}, {
|
||||
name: "weibo",
|
||||
rules: []string{"||weibo.com^"},
|
||||
}, {
|
||||
name: "9gag",
|
||||
rules: []string{"||9cache.com^", "||9gag.com^"},
|
||||
}, {
|
||||
name: "telegram",
|
||||
rules: []string{"||t.me^", "||telegram.me^", "||telegram.org^"},
|
||||
}, {
|
||||
name: "disneyplus",
|
||||
rules: []string{
|
||||
}},
|
||||
{"wechat", []string{
|
||||
"||wechat.com^",
|
||||
"||weixin.qq.com^",
|
||||
"||wx.qq.com^",
|
||||
}},
|
||||
{"viber", []string{
|
||||
"||viber.com^",
|
||||
}},
|
||||
{"weibo", []string{
|
||||
"||weibo.com^",
|
||||
}},
|
||||
{"9gag", []string{
|
||||
"||9cache.com^",
|
||||
"||9gag.com^",
|
||||
}},
|
||||
{"telegram", []string{
|
||||
"||t.me^",
|
||||
"||telegram.me^",
|
||||
"||telegram.org^",
|
||||
}},
|
||||
{"disneyplus", []string{
|
||||
"||disney-plus.net^",
|
||||
"||disneyplus.com^",
|
||||
"||disney.playback.edge.bamgrid.com^",
|
||||
"||media.dssott.com^",
|
||||
},
|
||||
}, {
|
||||
name: "hulu",
|
||||
rules: []string{"||hulu.com^"},
|
||||
}, {
|
||||
name: "spotify",
|
||||
rules: []string{
|
||||
}},
|
||||
{"hulu", []string{
|
||||
"||hulu.com^",
|
||||
}},
|
||||
{"spotify", []string{
|
||||
"/_spotify-connect._tcp.local/",
|
||||
"||spotify.com^",
|
||||
"||scdn.co^",
|
||||
@@ -275,15 +226,13 @@ var serviceRulesArray = []svc{{
|
||||
"||audio4-ak-spotify-com.akamaized.net^",
|
||||
"||heads-ak-spotify-com.akamaized.net^",
|
||||
"||heads4-ak-spotify-com.akamaized.net^",
|
||||
},
|
||||
}, {
|
||||
name: "tinder",
|
||||
rules: []string{
|
||||
}},
|
||||
{"tinder", []string{
|
||||
"||gotinder.com^",
|
||||
"||tinder.com^",
|
||||
"||tindersparks.com^",
|
||||
},
|
||||
}}
|
||||
}},
|
||||
}
|
||||
|
||||
// convert array to map
|
||||
func initBlockedServices() {
|
||||
|
||||
@@ -420,14 +420,8 @@ func (r Reason) Matched() bool {
|
||||
}
|
||||
|
||||
// CheckHostRules tries to match the host against filtering rules only.
|
||||
func (d *DNSFilter) CheckHostRules(host string, qtype uint16, setts *Settings) (Result, error) {
|
||||
if !setts.FilteringEnabled {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
host = strings.ToLower(host)
|
||||
|
||||
return d.matchHost(host, qtype, setts)
|
||||
func (d *DNSFilter) CheckHostRules(host string, rrtype uint16, setts *Settings) (Result, error) {
|
||||
return d.matchHost(strings.ToLower(host), rrtype, setts)
|
||||
}
|
||||
|
||||
// CheckHost tries to match the host against filtering rules, then safebrowsing
|
||||
@@ -726,8 +720,7 @@ func hostRulesToRules(netRules []*rules.HostRule) (res []rules.Rule) {
|
||||
return res
|
||||
}
|
||||
|
||||
// matchHostProcessAllowList processes the allowlist logic of host
|
||||
// matching.
|
||||
// matchHostProcessAllowList processes the allowlist logic of host matching.
|
||||
func (d *DNSFilter) matchHostProcessAllowList(
|
||||
host string,
|
||||
dnsres *urlfilter.DNSResult,
|
||||
@@ -798,11 +791,11 @@ func (d *DNSFilter) matchHostProcessDNSResult(
|
||||
return Result{}
|
||||
}
|
||||
|
||||
// matchHost is a low-level way to check only if hostname is filtered by rules,
|
||||
// matchHost is a low-level way to check only if host is filtered by rules,
|
||||
// skipping expensive safebrowsing and parental lookups.
|
||||
func (d *DNSFilter) matchHost(
|
||||
host string,
|
||||
qtype uint16,
|
||||
rrtype uint16,
|
||||
setts *Settings,
|
||||
) (res Result, err error) {
|
||||
if !setts.FilteringEnabled {
|
||||
@@ -815,7 +808,7 @@ func (d *DNSFilter) matchHost(
|
||||
// TODO(e.burkov): Wait for urlfilter update to pass net.IP.
|
||||
ClientIP: setts.ClientIP.String(),
|
||||
ClientName: setts.ClientName,
|
||||
DNSType: qtype,
|
||||
DNSType: rrtype,
|
||||
}
|
||||
|
||||
d.engineLock.RLock()
|
||||
@@ -855,7 +848,7 @@ func (d *DNSFilter) matchHost(
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
res = d.matchHostProcessDNSResult(qtype, dnsres)
|
||||
res = d.matchHostProcessDNSResult(rrtype, dnsres)
|
||||
for _, r := range res.Rules {
|
||||
log.Debug(
|
||||
"filtering: found rule %q for host %q, filter list id: %d",
|
||||
|
||||
@@ -3,10 +3,12 @@ package home
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -271,12 +273,18 @@ func TestClientsAddExisting(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("complicated", func(t *testing.T) {
|
||||
// TODO(a.garipov): Properly decouple the DHCP server from the client
|
||||
// storage.
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping dhcp test on windows")
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
ip := net.IP{1, 2, 3, 4}
|
||||
|
||||
// First, init a DHCP server with a single static lease.
|
||||
config := dhcpd.ServerConfig{
|
||||
config := &dhcpd.ServerConfig{
|
||||
Enabled: true,
|
||||
DBFilePath: "leases.db",
|
||||
Conf4: dhcpd.V4ServerConf{
|
||||
@@ -290,10 +298,9 @@ func TestClientsAddExisting(t *testing.T) {
|
||||
|
||||
clients.dhcpServer, err = dhcpd.Create(config)
|
||||
require.NoError(t, err)
|
||||
// TODO(e.burkov): leases.db isn't created on Windows so removing it
|
||||
// causes an error. Split the test to make it run properly on different
|
||||
// operating systems.
|
||||
t.Cleanup(func() { _ = os.Remove("leases.db") })
|
||||
testutil.CleanupAndRequireSuccess(t, func() (err error) {
|
||||
return os.Remove("leases.db")
|
||||
})
|
||||
|
||||
err = clients.dhcpServer.AddStaticLease(&dhcpd.Lease{
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
|
||||
@@ -83,7 +83,7 @@ type configuration struct {
|
||||
WhitelistFilters []filter `yaml:"whitelist_filters"`
|
||||
UserRules []string `yaml:"user_rules"`
|
||||
|
||||
DHCP dhcpd.ServerConfig `yaml:"dhcp"`
|
||||
DHCP *dhcpd.ServerConfig `yaml:"dhcp"`
|
||||
|
||||
// Clients contains the YAML representations of the persistent clients.
|
||||
// This field is only used for reading and writing persistent client data.
|
||||
@@ -123,11 +123,6 @@ type dnsConfig struct {
|
||||
// UpstreamTimeout is the timeout for querying upstream servers.
|
||||
UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"`
|
||||
|
||||
// LocalDomainName is the domain name used for known internal hosts.
|
||||
// For example, a machine called "myhost" can be addressed as
|
||||
// "myhost.lan" when LocalDomainName is "lan".
|
||||
LocalDomainName string `yaml:"local_domain_name"`
|
||||
|
||||
// ResolveClients enables and disables resolving clients with RDNS.
|
||||
ResolveClients bool `yaml:"resolve_clients"`
|
||||
|
||||
@@ -199,7 +194,6 @@ var config = &configuration{
|
||||
FilteringEnabled: true, // whether or not use filter lists
|
||||
FiltersUpdateIntervalHours: 24,
|
||||
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
|
||||
LocalDomainName: "lan",
|
||||
ResolveClients: true,
|
||||
UsePrivateRDNS: true,
|
||||
},
|
||||
@@ -208,6 +202,9 @@ var config = &configuration{
|
||||
PortDNSOverTLS: defaultPortTLS, // needs to be passed through to dnsproxy
|
||||
PortDNSOverQUIC: defaultPortQUIC,
|
||||
},
|
||||
DHCP: &dhcpd.ServerConfig{
|
||||
LocalDomainName: "lan",
|
||||
},
|
||||
logSettings: logSettings{
|
||||
LogCompress: false,
|
||||
LogLocalTime: false,
|
||||
@@ -291,20 +288,18 @@ func parseConfig() (err error) {
|
||||
uc := aghalg.UniqChecker{}
|
||||
addPorts(
|
||||
uc,
|
||||
tcpPort(config.BindPort),
|
||||
tcpPort(config.BetaBindPort),
|
||||
udpPort(config.DNS.Port),
|
||||
config.BindPort,
|
||||
config.BetaBindPort,
|
||||
config.DNS.Port,
|
||||
)
|
||||
|
||||
if config.TLS.Enabled {
|
||||
addPorts(
|
||||
uc,
|
||||
// TODO(e.burkov): Consider adding a udpPort with the same value if
|
||||
// we ever support the HTTP/3 for web admin interface.
|
||||
tcpPort(config.TLS.PortHTTPS),
|
||||
tcpPort(config.TLS.PortDNSOverTLS),
|
||||
udpPort(config.TLS.PortDNSOverQUIC),
|
||||
tcpPort(config.TLS.PortDNSCrypt),
|
||||
config.TLS.PortHTTPS,
|
||||
config.TLS.PortDNSOverTLS,
|
||||
config.TLS.PortDNSOverQUIC,
|
||||
config.TLS.PortDNSCrypt,
|
||||
)
|
||||
}
|
||||
if err = uc.Validate(aghalg.IntIsBefore); err != nil {
|
||||
@@ -322,29 +317,11 @@ func parseConfig() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// udpPort is the port number for UDP protocol.
|
||||
type udpPort int
|
||||
|
||||
// tcpPort is the port number for TCP protocol.
|
||||
type tcpPort int
|
||||
|
||||
// addPorts is a helper for ports validation. It skips zero ports. Each of
|
||||
// ports should be either a udpPort or a tcpPort.
|
||||
func addPorts(uc aghalg.UniqChecker, ports ...interface{}) {
|
||||
// addPorts is a helper for ports validation. It skips zero ports.
|
||||
func addPorts(uc aghalg.UniqChecker, ports ...int) {
|
||||
for _, p := range ports {
|
||||
// Use separate cases for tcpPort and udpPort so that the untyped
|
||||
// constant zero is converted to the appropriate type.
|
||||
switch p := p.(type) {
|
||||
case tcpPort:
|
||||
if p != 0 {
|
||||
uc.Add(p)
|
||||
}
|
||||
case udpPort:
|
||||
if p != 0 {
|
||||
uc.Add(p)
|
||||
}
|
||||
default:
|
||||
// Go on.
|
||||
if p != 0 {
|
||||
uc.Add(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,8 +386,8 @@ func (c *configuration) write() error {
|
||||
}
|
||||
|
||||
if Context.dhcpServer != nil {
|
||||
c := dhcpd.ServerConfig{}
|
||||
Context.dhcpServer.WriteDiskConfig(&c)
|
||||
c := &dhcpd.ServerConfig{}
|
||||
Context.dhcpServer.WriteDiskConfig(c)
|
||||
config.DHCP = c
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
@@ -108,7 +109,7 @@ func (req *checkConfReq) validateWeb(uc aghalg.UniqChecker) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "validating ports: %w") }()
|
||||
|
||||
port := req.Web.Port
|
||||
addPorts(uc, tcpPort(config.BetaBindPort), tcpPort(port))
|
||||
addPorts(uc, config.BetaBindPort, port)
|
||||
if err = uc.Validate(aghalg.IntIsBefore); err != nil {
|
||||
// Avoid duplicating the error into the status of DNS.
|
||||
uc[port] = 1
|
||||
@@ -134,7 +135,7 @@ func (req *checkConfReq) validateDNS(uc aghalg.UniqChecker) (canAutofix bool, er
|
||||
defer func() { err = errors.Annotate(err, "validating ports: %w") }()
|
||||
|
||||
port := req.DNS.Port
|
||||
addPorts(uc, udpPort(port))
|
||||
addPorts(uc, port)
|
||||
if err = uc.Validate(aghalg.IntIsBefore); err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -359,6 +360,9 @@ func shutdownSrv(ctx context.Context, srv *http.Server) {
|
||||
}
|
||||
}
|
||||
|
||||
// PasswordMinRunes is the minimum length of user's password in runes.
|
||||
const PasswordMinRunes = 8
|
||||
|
||||
// Apply new configuration, start DNS server, restart Web server
|
||||
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
req, restartHTTP, err := decodeApplyConfigReq(r.Body)
|
||||
@@ -368,6 +372,18 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(req.Password) < PasswordMinRunes {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusUnprocessableEntity,
|
||||
"password must be at least %d symbols long",
|
||||
PasswordMinRunes,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = aghnet.CheckPort("udp", req.DNS.IP, req.DNS.Port)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
|
||||
@@ -83,7 +83,7 @@ func initDNSServer() (err error) {
|
||||
QueryLog: Context.queryLog,
|
||||
SubnetDetector: Context.subnetDetector,
|
||||
Anonymizer: anonymizer,
|
||||
LocalDomain: config.DNS.LocalDomainName,
|
||||
LocalDomain: config.DHCP.LocalDomainName,
|
||||
}
|
||||
if Context.dhcpServer != nil {
|
||||
p.DHCPServer = Context.dhcpServer
|
||||
@@ -211,7 +211,6 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||
}
|
||||
|
||||
newConf.TLSv12Roots = Context.tlsRoots
|
||||
newConf.TLSCiphers = Context.tlsCiphers
|
||||
newConf.TLSAllowUnencryptedDoH = tlsConf.AllowUnencryptedDoH
|
||||
|
||||
newConf.FilterHandler = applyAdditionalFiltering
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
@@ -80,7 +81,6 @@ type homeContext struct {
|
||||
disableUpdate bool // If set, don't check for updates
|
||||
controlLock sync.Mutex
|
||||
tlsRoots *x509.CertPool // list of root CAs for TLSv1.2
|
||||
tlsCiphers []uint16 // list of TLS ciphers to use
|
||||
transport *http.Transport
|
||||
client *http.Client
|
||||
appSignalChannel chan os.Signal // Channel for receiving OS signals by the console app
|
||||
@@ -145,13 +145,13 @@ func setupContext(args options) {
|
||||
initConfig()
|
||||
|
||||
Context.tlsRoots = LoadSystemRootCAs()
|
||||
Context.tlsCiphers = InitTLSCiphers()
|
||||
Context.transport = &http.Transport{
|
||||
DialContext: customDialContext,
|
||||
Proxy: getHTTPProxy,
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: Context.tlsRoots,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
RootCAs: Context.tlsRoots,
|
||||
CipherSuites: aghtls.SaferCipherSuites(),
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
}
|
||||
Context.client = &http.Client{
|
||||
@@ -182,7 +182,7 @@ func setupContext(args options) {
|
||||
|
||||
// logIfUnsupported logs a formatted warning if the error is one of the
|
||||
// unsupported errors and returns nil. If err is nil, logIfUnsupported returns
|
||||
// nil. Otherise, it returns err.
|
||||
// nil. Otherwise, it returns err.
|
||||
func logIfUnsupported(msg string, err error) (outErr error) {
|
||||
if errors.As(err, new(*aghos.UnsupportedError)) {
|
||||
log.Debug(msg, err)
|
||||
@@ -299,17 +299,17 @@ func setupConfig(args options) (err error) {
|
||||
uc := aghalg.UniqChecker{}
|
||||
addPorts(
|
||||
uc,
|
||||
tcpPort(args.bindPort),
|
||||
tcpPort(config.BetaBindPort),
|
||||
udpPort(config.DNS.Port),
|
||||
args.bindPort,
|
||||
config.BetaBindPort,
|
||||
config.DNS.Port,
|
||||
)
|
||||
if config.TLS.Enabled {
|
||||
addPorts(
|
||||
uc,
|
||||
tcpPort(config.TLS.PortHTTPS),
|
||||
tcpPort(config.TLS.PortDNSOverTLS),
|
||||
udpPort(config.TLS.PortDNSOverQUIC),
|
||||
tcpPort(config.TLS.PortDNSCrypt),
|
||||
config.TLS.PortHTTPS,
|
||||
config.TLS.PortDNSOverTLS,
|
||||
config.TLS.PortDNSOverQUIC,
|
||||
config.TLS.PortDNSCrypt,
|
||||
)
|
||||
}
|
||||
if err = uc.Validate(aghalg.IntIsBefore); err != nil {
|
||||
@@ -390,6 +390,9 @@ func run(args options, clientBuildFS fs.FS) {
|
||||
// configure log level and output
|
||||
configureLogger(args)
|
||||
|
||||
// Go memory hacks
|
||||
memoryUsage(args)
|
||||
|
||||
// Print the first message after logger is configured.
|
||||
log.Println(version.Full())
|
||||
log.Debug("current working directory is %s", Context.workDir)
|
||||
|
||||
40
internal/home/memory.go
Normal file
40
internal/home/memory.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// memoryUsage implements a couple of not really beautiful hacks which purpose is to
|
||||
// make OS reclaim the memory freed by AdGuard Home as soon as possible.
|
||||
// See this for the details on the performance hits & gains:
|
||||
// https://github.com/AdguardTeam/AdGuardHome/internal/issues/2044#issuecomment-687042211
|
||||
func memoryUsage(args options) {
|
||||
if args.disableMemoryOptimization {
|
||||
log.Info("Memory optimization is disabled")
|
||||
return
|
||||
}
|
||||
|
||||
// Makes Go allocate heap at a slower pace
|
||||
// By default we keep it at 50%
|
||||
debug.SetGCPercent(50)
|
||||
|
||||
// madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED
|
||||
// instead of MADV_FREE on Linux when returning memory to the
|
||||
// kernel. This is less efficient, but causes RSS numbers to drop
|
||||
// more quickly.
|
||||
_ = os.Setenv("GODEBUG", "madvdontneed=1")
|
||||
|
||||
// periodically call "debug.FreeOSMemory" so
|
||||
// that the OS could reclaim the free memory
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
for range ticker.C {
|
||||
log.Debug("free os memory")
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// options passed from command-line arguments
|
||||
@@ -28,6 +27,10 @@ type options struct {
|
||||
// runningAsService flag is set to true when options are passed from the service runner
|
||||
runningAsService bool
|
||||
|
||||
// disableMemoryOptimization - disables memory optimization hacks
|
||||
// see memoryUsage() function for the details
|
||||
disableMemoryOptimization bool
|
||||
|
||||
glinetMode bool // Activate GL-Inet compatibility mode
|
||||
|
||||
// noEtcHosts flag should be provided when /etc/hosts file shouldn't be
|
||||
@@ -175,14 +178,10 @@ var noCheckUpdateArg = arg{
|
||||
}
|
||||
|
||||
var disableMemoryOptimizationArg = arg{
|
||||
"Deprecated. Disable memory optimization.",
|
||||
"Disable memory optimization.",
|
||||
"no-mem-optimization", "",
|
||||
nil, nil, func(_ options, _ string) (f effect, err error) {
|
||||
log.Info("warning: using --no-mem-optimization flag has no effect and is deprecated")
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
func(o options) []string { return nil },
|
||||
nil, func(o options) (options, error) { o.disableMemoryOptimization = true; return o, nil }, nil,
|
||||
func(o options) []string { return boolSliceOrNil(o.disableMemoryOptimization) },
|
||||
}
|
||||
|
||||
var verboseArg = arg{
|
||||
|
||||
@@ -101,13 +101,9 @@ func TestParseDisableUpdate(t *testing.T) {
|
||||
assert.True(t, testParseOK(t, "--no-check-update").disableUpdate, "--no-check-update is disable update")
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Remove after v0.108.0.
|
||||
func TestParseDisableMemoryOptimization(t *testing.T) {
|
||||
o, eff, err := parse("", []string{"--no-mem-optimization"})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Nil(t, eff)
|
||||
assert.Zero(t, o)
|
||||
assert.False(t, testParseOK(t).disableMemoryOptimization, "empty is not disable update")
|
||||
assert.True(t, testParseOK(t, "--no-mem-optimization").disableMemoryOptimization, "--no-mem-optimization is disable update")
|
||||
}
|
||||
|
||||
func TestParseService(t *testing.T) {
|
||||
@@ -131,6 +127,8 @@ func TestParseUnknown(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSerialize(t *testing.T) {
|
||||
const reportFmt = "expected %s but got %s"
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
opts options
|
||||
@@ -175,14 +173,19 @@ func TestSerialize(t *testing.T) {
|
||||
name: "glinet_mode",
|
||||
opts: options{glinetMode: true},
|
||||
ss: []string{"--glinet"},
|
||||
}, {
|
||||
name: "disable_mem_opt",
|
||||
opts: options{disableMemoryOptimization: true},
|
||||
ss: []string{"--no-mem-optimization"},
|
||||
}, {
|
||||
name: "multiple",
|
||||
opts: options{
|
||||
serviceControlAction: "run",
|
||||
configFilename: "config",
|
||||
workDir: "work",
|
||||
pidFile: "pid",
|
||||
disableUpdate: true,
|
||||
serviceControlAction: "run",
|
||||
configFilename: "config",
|
||||
workDir: "work",
|
||||
pidFile: "pid",
|
||||
disableUpdate: true,
|
||||
disableMemoryOptimization: true,
|
||||
},
|
||||
ss: []string{
|
||||
"-c", "config",
|
||||
@@ -190,13 +193,18 @@ func TestSerialize(t *testing.T) {
|
||||
"-s", "run",
|
||||
"--pidfile", "pid",
|
||||
"--no-check-update",
|
||||
"--no-mem-optimization",
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := serialize(tc.opts)
|
||||
assert.ElementsMatch(t, tc.ss, result)
|
||||
require.Lenf(t, result, len(tc.ss), reportFmt, tc.ss, result)
|
||||
|
||||
for i, r := range result {
|
||||
assert.Equalf(t, tc.ss[i], r, reportFmt, tc.ss, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
aghtest.ReplaceLogWriter(t, w)
|
||||
|
||||
locUpstream := &aghtest.TestUpstream{
|
||||
locUpstream := &aghtest.Upstream{
|
||||
Reverse: map[string][]string{
|
||||
"192.168.1.1": {"local.domain"},
|
||||
"2a00:1450:400c:c06::93": {"ipv6.domain"},
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -82,10 +83,15 @@ func svcStatus(s service.Service) (status service.Status, err error) {
|
||||
// On OpenWrt, the service utility may not exist. We use our service script
|
||||
// directly in this case.
|
||||
func svcAction(s service.Service, action string) (err error) {
|
||||
if runtime.GOOS == "darwin" &&
|
||||
action == "start" &&
|
||||
!strings.HasPrefix(Context.workDir, "/Applications/") {
|
||||
log.Info("warning: service must be started from within the /Applications directory")
|
||||
if runtime.GOOS == "darwin" && action == "start" {
|
||||
var exe string
|
||||
if exe, err = os.Executable(); err != nil {
|
||||
log.Error("starting service: getting executable path: %s", err)
|
||||
} else if exe, err = filepath.EvalSymlinks(exe); err != nil {
|
||||
log.Error("starting service: evaluating executable symlinks: %s", err)
|
||||
} else if !strings.HasPrefix(exe, "/Applications/") {
|
||||
log.Info("warning: service must be started from within the /Applications directory")
|
||||
}
|
||||
}
|
||||
|
||||
err = service.Control(s, action)
|
||||
@@ -579,6 +585,9 @@ status() {
|
||||
}
|
||||
`
|
||||
|
||||
// freeBSDScript is the source of the daemon script for FreeBSD. Keep as close
|
||||
// as possible to the https://github.com/kardianos/service/blob/18c957a3dc1120a2efe77beb401d476bade9e577/service_freebsd.go#L204.
|
||||
//
|
||||
// TODO(a.garipov): Don't use .WorkingDirectory here. There are currently no
|
||||
// guarantees that it will actually be the required directory.
|
||||
//
|
||||
@@ -587,14 +596,16 @@ const freeBSDScript = `#!/bin/sh
|
||||
# PROVIDE: {{.Name}}
|
||||
# REQUIRE: networking
|
||||
# KEYWORD: shutdown
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name="{{.Name}}"
|
||||
{{.Name}}_env="IS_DAEMON=1"
|
||||
{{.Name}}_user="root"
|
||||
pidfile_child="/var/run/${name}.pid"
|
||||
pidfile="/var/run/${name}_daemon.pid"
|
||||
command="/usr/sbin/daemon"
|
||||
command_args="-P ${pidfile} -p ${pidfile_child} -f -r {{.WorkingDirectory}}/{{.Name}}"
|
||||
command_args="-P ${pidfile} -p ${pidfile_child} -T ${name} -r {{.WorkingDirectory}}/{{.Name}}"
|
||||
run_rc_command "$1"
|
||||
`
|
||||
|
||||
@@ -604,6 +615,7 @@ const openBSDScript = `#!/bin/sh
|
||||
|
||||
daemon="{{.Path}}"
|
||||
daemon_flags={{ .Arguments | args }}
|
||||
daemon_logger="daemon.info"
|
||||
|
||||
. /etc/rc.d/rc.subr
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
var tlsWebHandlersRegistered = false
|
||||
@@ -254,13 +253,13 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
|
||||
uc := aghalg.UniqChecker{}
|
||||
addPorts(
|
||||
uc,
|
||||
tcpPort(config.BindPort),
|
||||
tcpPort(config.BetaBindPort),
|
||||
udpPort(config.DNS.Port),
|
||||
tcpPort(setts.PortHTTPS),
|
||||
tcpPort(setts.PortDNSOverTLS),
|
||||
udpPort(setts.PortDNSOverQUIC),
|
||||
tcpPort(setts.PortDNSCrypt),
|
||||
config.BindPort,
|
||||
config.BetaBindPort,
|
||||
config.DNS.Port,
|
||||
setts.PortHTTPS,
|
||||
setts.PortDNSOverTLS,
|
||||
setts.PortDNSOverQUIC,
|
||||
setts.PortDNSCrypt,
|
||||
)
|
||||
|
||||
err = uc.Validate(aghalg.IntIsBefore)
|
||||
@@ -347,13 +346,13 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
uc := aghalg.UniqChecker{}
|
||||
addPorts(
|
||||
uc,
|
||||
tcpPort(config.BindPort),
|
||||
tcpPort(config.BetaBindPort),
|
||||
udpPort(config.DNS.Port),
|
||||
tcpPort(data.PortHTTPS),
|
||||
tcpPort(data.PortDNSOverTLS),
|
||||
udpPort(data.PortDNSOverQUIC),
|
||||
tcpPort(data.PortDNSCrypt),
|
||||
config.BindPort,
|
||||
config.BetaBindPort,
|
||||
config.DNS.Port,
|
||||
data.PortHTTPS,
|
||||
data.PortDNSOverTLS,
|
||||
data.PortDNSOverQUIC,
|
||||
data.PortDNSCrypt,
|
||||
)
|
||||
|
||||
err = uc.Validate(aghalg.IntIsBefore)
|
||||
@@ -731,52 +730,3 @@ func LoadSystemRootCAs() (roots *x509.CertPool) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitTLSCiphers performs the same work as initDefaultCipherSuites() from
|
||||
// crypto/tls/common.go but don't uses lots of other default ciphers.
|
||||
func InitTLSCiphers() (ciphers []uint16) {
|
||||
// Check the cpu flags for each platform that has optimized GCM
|
||||
// implementations. The worst case is when all these variables are
|
||||
// false.
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES &&
|
||||
cpu.S390X.HasAESCBC &&
|
||||
cpu.S390X.HasAESCTR &&
|
||||
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
|
||||
hasGCMAsm = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
|
||||
)
|
||||
|
||||
if hasGCMAsm {
|
||||
// If AES-GCM hardware is provided then prioritize AES-GCM
|
||||
// cipher suites.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
} else {
|
||||
// Without AES-GCM hardware, we put the ChaCha20-Poly1305 cipher
|
||||
// suites first.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
}
|
||||
|
||||
return append(
|
||||
ciphers,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
// currentSchemaVersion is the current schema version.
|
||||
const currentSchemaVersion = 12
|
||||
const currentSchemaVersion = 13
|
||||
|
||||
// These aliases are provided for convenience.
|
||||
type (
|
||||
@@ -85,6 +85,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
|
||||
upgradeSchema9to10,
|
||||
upgradeSchema10to11,
|
||||
upgradeSchema11to12,
|
||||
upgradeSchema12to13,
|
||||
}
|
||||
|
||||
n := 0
|
||||
@@ -690,6 +691,52 @@ func upgradeSchema11to12(diskConf yobj) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeSchema12to13 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
// 'dns':
|
||||
// # …
|
||||
// 'local_domain_name': 'lan'
|
||||
//
|
||||
// # AFTER:
|
||||
// 'dhcp':
|
||||
// # …
|
||||
// 'local_domain_name': 'lan'
|
||||
//
|
||||
func upgradeSchema12to13(diskConf yobj) (err error) {
|
||||
log.Printf("Upgrade yaml: 12 to 13")
|
||||
diskConf["schema_version"] = 13
|
||||
|
||||
dnsVal, ok := diskConf["dns"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var dns yobj
|
||||
dns, ok = dnsVal.(yobj)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
|
||||
}
|
||||
|
||||
dhcpVal, ok := diskConf["dhcp"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var dhcp yobj
|
||||
dhcp, ok = dhcpVal.(yobj)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of dhcp: %T", dnsVal)
|
||||
}
|
||||
|
||||
const field = "local_domain_name"
|
||||
|
||||
dhcp[field] = dns[field]
|
||||
delete(dns, field)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Replace with log.Output when we port it to our logging
|
||||
// package.
|
||||
func funcName() string {
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestUpgradeSchema2to3(t *testing.T) {
|
||||
require.Len(t, v, 1)
|
||||
require.Equal(t, "8.8.8.8:53", v[0])
|
||||
default:
|
||||
t.Fatalf("wrong type for bootsrap dns: %T", v)
|
||||
t.Fatalf("wrong type for bootstrap dns: %T", v)
|
||||
}
|
||||
|
||||
excludedEntries := []string{"bootstrap_dns"}
|
||||
@@ -511,3 +511,48 @@ func TestUpgradeSchema11to12(t *testing.T) {
|
||||
assert.Equal(t, 90*24*time.Hour, ivlVal.Duration)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpgradeSchema12to13(t *testing.T) {
|
||||
t.Run("no_dns", func(t *testing.T) {
|
||||
conf := yobj{}
|
||||
|
||||
err := upgradeSchema12to13(conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, conf["schema_version"], 13)
|
||||
})
|
||||
|
||||
t.Run("no_dhcp", func(t *testing.T) {
|
||||
conf := yobj{
|
||||
"dns": yobj{},
|
||||
}
|
||||
|
||||
err := upgradeSchema12to13(conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, conf["schema_version"], 13)
|
||||
})
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
conf := yobj{
|
||||
"dns": yobj{
|
||||
"local_domain_name": "lan",
|
||||
},
|
||||
"dhcp": yobj{},
|
||||
"schema_version": 12,
|
||||
}
|
||||
|
||||
wantConf := yobj{
|
||||
"dns": yobj{},
|
||||
"dhcp": yobj{
|
||||
"local_domain_name": "lan",
|
||||
},
|
||||
"schema_version": 13,
|
||||
}
|
||||
|
||||
err := upgradeSchema12to13(conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, wantConf, conf)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
@@ -34,14 +35,13 @@ const (
|
||||
)
|
||||
|
||||
type webConfig struct {
|
||||
clientFS fs.FS
|
||||
clientBetaFS fs.FS
|
||||
|
||||
BindHost net.IP
|
||||
BindPort int
|
||||
BetaBindPort int
|
||||
PortHTTPS int
|
||||
firstRun bool
|
||||
|
||||
clientFS fs.FS
|
||||
clientBetaFS fs.FS
|
||||
|
||||
// ReadTimeout is an option to pass to http.Server for setting an
|
||||
// appropriate field.
|
||||
@@ -54,6 +54,8 @@ type webConfig struct {
|
||||
// WriteTimeout is an option to pass to http.Server for setting an
|
||||
// appropriate field.
|
||||
WriteTimeout time.Duration
|
||||
|
||||
firstRun bool
|
||||
}
|
||||
|
||||
// HTTPSServer - HTTPS Server
|
||||
@@ -263,9 +265,9 @@ func (web *Web) tlsServerLoop() {
|
||||
Addr: address,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{web.httpsServer.cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
RootCAs: Context.tlsRoots,
|
||||
CipherSuites: Context.tlsCiphers,
|
||||
CipherSuites: aghtls.SaferCipherSuites(),
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
||||
ReadTimeout: web.conf.ReadTimeout,
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type logEntryHandler (func(t json.Token, ent *logEntry) error)
|
||||
type logEntryHandler func(t json.Token, ent *logEntry) error
|
||||
|
||||
var logEntryHandlers = map[string]logEntryHandler{
|
||||
"CID": func(t json.Token, ent *logEntry) error {
|
||||
@@ -109,6 +109,16 @@ var logEntryHandlers = map[string]logEntryHandler{
|
||||
|
||||
return err
|
||||
},
|
||||
"ECS": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
ent.ReqECS = v
|
||||
|
||||
return nil
|
||||
},
|
||||
"Cached": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(bool)
|
||||
if !ok {
|
||||
|
||||
@@ -32,6 +32,7 @@ func TestDecodeLogEntry(t *testing.T) {
|
||||
`"QT":"A",` +
|
||||
`"QC":"IN",` +
|
||||
`"CP":"",` +
|
||||
`"ECS":"1.2.3.0/24",` +
|
||||
`"Answer":"` + ansStr + `",` +
|
||||
`"Cached":true,` +
|
||||
`"AD":true,` +
|
||||
@@ -58,6 +59,7 @@ func TestDecodeLogEntry(t *testing.T) {
|
||||
QClass: "IN",
|
||||
ClientID: "cli42",
|
||||
ClientProto: "",
|
||||
ReqECS: "1.2.3.0/24",
|
||||
Answer: ans,
|
||||
Cached: true,
|
||||
Result: filtering.Result{
|
||||
|
||||
@@ -78,6 +78,10 @@ func (l *queryLog) entryToJSON(entry *logEntry, anonFunc aghnet.IPMutFunc) (json
|
||||
jsonEntry["client_id"] = entry.ClientID
|
||||
}
|
||||
|
||||
if entry.ReqECS != "" {
|
||||
jsonEntry["ecs"] = entry.ReqECS
|
||||
}
|
||||
|
||||
if len(entry.Result.Rules) > 0 {
|
||||
if r := entry.Result.Rules[0]; len(r.Text) > 0 {
|
||||
jsonEntry["rule"] = r.Text
|
||||
|
||||
@@ -81,6 +81,8 @@ type logEntry struct {
|
||||
QType string `json:"QT"`
|
||||
QClass string `json:"QC"`
|
||||
|
||||
ReqECS string `json:"ECS,omitempty"`
|
||||
|
||||
ClientID string `json:"CID,omitempty"`
|
||||
ClientProto ClientProto `json:"CP"`
|
||||
|
||||
@@ -189,6 +191,10 @@ func (l *queryLog) Add(params *AddParams) {
|
||||
AuthenticatedData: params.AuthenticatedData,
|
||||
}
|
||||
|
||||
if params.ReqECS != nil {
|
||||
entry.ReqECS = params.ReqECS.String()
|
||||
}
|
||||
|
||||
if params.Answer != nil {
|
||||
var a []byte
|
||||
a, err = params.Answer.Pack()
|
||||
|
||||
@@ -77,6 +77,10 @@ type Config struct {
|
||||
type AddParams struct {
|
||||
Question *dns.Msg
|
||||
|
||||
// ReqECS is the IP network extracted from EDNS Client-Subnet option of a
|
||||
// request.
|
||||
ReqECS *net.IPNet
|
||||
|
||||
// Answer is the response which is sent to the client, if any.
|
||||
Answer *dns.Msg
|
||||
|
||||
|
||||
@@ -99,24 +99,10 @@ func (c *searchCriterion) quickMatch(line string, findClient quickMatchClientFun
|
||||
}
|
||||
|
||||
if c.strict {
|
||||
return ctDomainOrClientCaseStrict(
|
||||
c.value,
|
||||
c.asciiVal,
|
||||
clientID,
|
||||
name,
|
||||
host,
|
||||
ip,
|
||||
)
|
||||
return ctDomainOrClientCaseStrict(c.value, c.asciiVal, clientID, name, host, ip)
|
||||
}
|
||||
|
||||
return ctDomainOrClientCaseNonStrict(
|
||||
c.value,
|
||||
c.asciiVal,
|
||||
clientID,
|
||||
name,
|
||||
host,
|
||||
ip,
|
||||
)
|
||||
return ctDomainOrClientCaseNonStrict(c.value, c.asciiVal, clientID, name, host, ip)
|
||||
case ctFilteringStatus:
|
||||
// Go on, as we currently don't do quick matches against
|
||||
// filtering statuses.
|
||||
|
||||
@@ -10,8 +10,8 @@ require (
|
||||
github.com/kyoh86/looppointer v0.1.7
|
||||
github.com/securego/gosec/v2 v2.9.5
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a
|
||||
honnef.co/go/tools v0.3.0
|
||||
golang.org/x/tools v0.1.8
|
||||
honnef.co/go/tools v0.2.2
|
||||
mvdan.cc/gofumpt v0.2.1
|
||||
mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5
|
||||
)
|
||||
@@ -25,8 +25,7 @@ require (
|
||||
github.com/kyoh86/nolint v0.0.1 // indirect
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
@@ -401,7 +401,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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=
|
||||
@@ -413,10 +412,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -440,9 +436,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -478,7 +473,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@@ -545,6 +539,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -618,10 +613,10 @@ golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II=
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -742,8 +737,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.3.0 h1:2LdYUZ7CIxnYgskbUZfY7FPggmqnh6shBqfWa8Tn3XU=
|
||||
honnef.co/go/tools v0.3.0/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70=
|
||||
honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
|
||||
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
mvdan.cc/gofumpt v0.2.1 h1:7jakRGkQcLAJdT+C8Bwc9d0BANkVPSkHZkzNv07pJAs=
|
||||
mvdan.cc/gofumpt v0.2.1/go.mod h1:a/rvZPhsNaedOJBzqRD9omnwVwHZsBdJirXHa9Gh9Ig=
|
||||
mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5 h1:Jh3LAeMt1eGpxomyu3jVkmVZWW2MxZ1qIIV2TZ/nRio=
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
)
|
||||
@@ -26,11 +27,11 @@ const (
|
||||
// TODO(a.garipov): Find out if we can get GOARM and GOMIPS values the same way
|
||||
// we can GOARCH and GOOS.
|
||||
var (
|
||||
channel string = ChannelDevelopment
|
||||
goarm string
|
||||
gomips string
|
||||
version string
|
||||
buildtime string
|
||||
channel string = ChannelDevelopment
|
||||
goarm string
|
||||
gomips string
|
||||
version string
|
||||
committime string
|
||||
)
|
||||
|
||||
// Channel returns the current AdGuard Home release channel.
|
||||
@@ -106,7 +107,7 @@ const (
|
||||
vFmtVerHdr = "Version: "
|
||||
vFmtChanHdr = "Channel: "
|
||||
vFmtGoHdr = "Go version: "
|
||||
vFmtTimeHdr = "Build time: "
|
||||
vFmtTimeHdr = "Commit time: "
|
||||
vFmtRaceHdr = "Race: "
|
||||
vFmtGOOSHdr = "GOOS: " + runtime.GOOS
|
||||
vFmtGOARCHHdr = "GOARCH: " + runtime.GOARCH
|
||||
@@ -148,15 +149,23 @@ func Verbose() (v string) {
|
||||
vFmtGoHdr,
|
||||
runtime.Version(),
|
||||
)
|
||||
if buildtime != "" {
|
||||
stringutil.WriteToBuilder(b, nl, vFmtTimeHdr, buildtime)
|
||||
|
||||
if committime != "" {
|
||||
commitTimeUnix, err := strconv.ParseInt(committime, 10, 64)
|
||||
if err != nil {
|
||||
stringutil.WriteToBuilder(b, nl, vFmtTimeHdr, fmt.Sprintf("parse error: %s", err))
|
||||
} else {
|
||||
stringutil.WriteToBuilder(b, nl, vFmtTimeHdr, time.Unix(commitTimeUnix, 0).String())
|
||||
}
|
||||
}
|
||||
|
||||
stringutil.WriteToBuilder(b, nl, vFmtGOOSHdr, nl, vFmtGOARCHHdr)
|
||||
if goarm != "" {
|
||||
stringutil.WriteToBuilder(b, nl, vFmtGOARMHdr, "v", goarm)
|
||||
} else if gomips != "" {
|
||||
stringutil.WriteToBuilder(b, nl, vFmtGOMIPSHdr, gomips)
|
||||
}
|
||||
|
||||
stringutil.WriteToBuilder(b, nl, vFmtRaceHdr, strconv.FormatBool(isRace))
|
||||
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
|
||||
<!-- TODO(a.garipov): Reformat in accordance with the KeepAChangelog spec. -->
|
||||
|
||||
## v0.108.0: API changes
|
||||
|
||||
### The new optional field `"ecs"` in `QueryLogItem`
|
||||
|
||||
* The new optional field `"ecs"` in `GET /control/querylog` contains the IP
|
||||
network from an EDNS Client-Subnet option from the request message if any.
|
||||
|
||||
### The new possible status code in `/install/configure` response.
|
||||
|
||||
* The new status code `422 Unprocessable Entity` in the response for
|
||||
`POST /install/configure` which means that the specified password does not
|
||||
meet the strength requirements.
|
||||
|
||||
## v0.107.3: API changes
|
||||
|
||||
### The new field `"version"` in `AddressesInfo`
|
||||
|
||||
@@ -1088,6 +1088,9 @@
|
||||
'description': >
|
||||
Failed to parse initial configuration or cannot listen to the
|
||||
specified addresses.
|
||||
'422':
|
||||
'description': >
|
||||
The specified password does not meet the strength requirements.
|
||||
'500':
|
||||
'description': 'Cannot start the DNS server'
|
||||
'/login':
|
||||
@@ -1902,6 +1905,12 @@
|
||||
- 'doq'
|
||||
- 'dnscrypt'
|
||||
- ''
|
||||
'ecs':
|
||||
'type': 'string'
|
||||
'example': '192.168.0.0/16'
|
||||
'description': >
|
||||
The IP network defined by an EDNS Client-Subnet option in the
|
||||
request message if any.
|
||||
'elapsedMs':
|
||||
'type': 'string'
|
||||
'example': '54.023928'
|
||||
|
||||
@@ -134,10 +134,9 @@ underscores() {
|
||||
-e '_bsd.go'\
|
||||
-e '_darwin.go'\
|
||||
-e '_freebsd.go'\
|
||||
-e '_openbsd.go'\
|
||||
-e '_linux.go'\
|
||||
-e '_little.go'\
|
||||
-e '_nolinux.go'\
|
||||
-e '_openbsd.go'\
|
||||
-e '_others.go'\
|
||||
-e '_test.go'\
|
||||
-e '_unix.go'\
|
||||
|
||||
Reference in New Issue
Block a user