Compare commits
179 Commits
v0.108.0-b
...
release-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30244f361f | ||
|
|
083991fb21 | ||
|
|
e3200d5046 | ||
|
|
21f6ed36fe | ||
|
|
77d04d44eb | ||
|
|
b34d119255 | ||
|
|
63bd71a10c | ||
|
|
faf2b32389 | ||
|
|
d23da1b757 | ||
|
|
beb8e36eee | ||
|
|
fe70161c01 | ||
|
|
39fa4b1f8e | ||
|
|
c7a8883201 | ||
|
|
3fd467413c | ||
|
|
9728dd856f | ||
|
|
ecadf78d60 | ||
|
|
eba4612d72 | ||
|
|
9200163f85 | ||
|
|
3c17853344 | ||
|
|
993a3fc42c | ||
|
|
7bb9b2416b | ||
|
|
2de321ce24 | ||
|
|
30b2b85ff1 | ||
|
|
6ea4788f56 | ||
|
|
3c52a021b9 | ||
|
|
0ceea9af5f | ||
|
|
39b404be19 | ||
|
|
56dc3eab02 | ||
|
|
554a38eeb1 | ||
|
|
c8d3afe869 | ||
|
|
44222c604c | ||
|
|
cbf221585e | ||
|
|
48322f6d0d | ||
|
|
d5a213c639 | ||
|
|
8166c4bc33 | ||
|
|
133cd9ef6b | ||
|
|
11146f73ed | ||
|
|
1beb18db47 | ||
|
|
f7bc2273a7 | ||
|
|
d1e735a003 | ||
|
|
af4ff5c748 | ||
|
|
fc951c1226 | ||
|
|
f81fd42472 | ||
|
|
1029ea5966 | ||
|
|
c0abdb4bc7 | ||
|
|
6681178ad3 | ||
|
|
e73605c4c5 | ||
|
|
c7017d49aa | ||
|
|
191d3bde49 | ||
|
|
18876a8e5c | ||
|
|
aa4a0d9880 | ||
|
|
d03d731d65 | ||
|
|
33b58a42fe | ||
|
|
2e9e708647 | ||
|
|
8ad22841ab | ||
|
|
32cf02264c | ||
|
|
0e8445b38f | ||
|
|
cb27ecd6c0 | ||
|
|
535220b3df | ||
|
|
7b9cfa94f8 | ||
|
|
b3f2e88e9c | ||
|
|
aa7a8d45e4 | ||
|
|
49cdef3d6a | ||
|
|
fecd146552 | ||
|
|
b01efd8c98 | ||
|
|
bd4dfb261c | ||
|
|
e754e4d2f6 | ||
|
|
b220e35c99 | ||
|
|
4f5131f423 | ||
|
|
dcb043df5f | ||
|
|
86e5756262 | ||
|
|
ba0cf5739b | ||
|
|
c4a13b92d2 | ||
|
|
723279121a | ||
|
|
3ad7649f7d | ||
|
|
2898a49d86 | ||
|
|
1547f9d35e | ||
|
|
adadd55c42 | ||
|
|
33b0225aa4 | ||
|
|
97d4058d80 | ||
|
|
86207e719d | ||
|
|
113f94ff46 | ||
|
|
5673deb391 | ||
|
|
3548a393ed | ||
|
|
254515f274 | ||
|
|
bccbecc6ea | ||
|
|
66f53803af | ||
|
|
faef005ce7 | ||
|
|
941cd2a562 | ||
|
|
6a4a9a0239 | ||
|
|
b9dbe6f1b6 | ||
|
|
7fec111ef8 | ||
|
|
5e1bd99718 | ||
|
|
9d75f72ceb | ||
|
|
d98d96db1a | ||
|
|
6a0ef2df15 | ||
|
|
75c2eb4c8a | ||
|
|
d021a67d66 | ||
|
|
4ed97cab12 | ||
|
|
a38742eed7 | ||
|
|
5efa95ed26 | ||
|
|
04db7db607 | ||
|
|
d17c6c6bb3 | ||
|
|
b2052f2ef1 | ||
|
|
cddcf852c2 | ||
|
|
1def426b45 | ||
|
|
b114fd5279 | ||
|
|
d27c3284f6 | ||
|
|
ba24a26b53 | ||
|
|
3e6678b6b4 | ||
|
|
83fd6f9782 | ||
|
|
52bc1b3f10 | ||
|
|
dd2153b7ac | ||
|
|
dd96a34861 | ||
|
|
daf26ee25a | ||
|
|
7e140eaaac | ||
|
|
d07a712988 | ||
|
|
95863288bf | ||
|
|
ea12be658b | ||
|
|
faa7c9aae5 | ||
|
|
e3653e8c25 | ||
|
|
b40cb24822 | ||
|
|
74004c1aa0 | ||
|
|
3e240741f1 | ||
|
|
6cfdbef1a5 | ||
|
|
d9bde6425b | ||
|
|
e2ae9e1591 | ||
|
|
5ebcbfa9ad | ||
|
|
e276bd7a31 | ||
|
|
659b2529bf | ||
|
|
97b3ed43ab | ||
|
|
767d6d3f28 | ||
|
|
31fc9bfc52 | ||
|
|
3f06b02409 | ||
|
|
5bf958ec6b | ||
|
|
959d9ff9a0 | ||
|
|
4813b4de25 | ||
|
|
119100924c | ||
|
|
bd584de4ee | ||
|
|
ede85ab2f2 | ||
|
|
12c20288e4 | ||
|
|
5bbbf89c10 | ||
|
|
d55393ecd5 | ||
|
|
2b5927306f | ||
|
|
4f016b6ed7 | ||
|
|
3a2a6d10ec | ||
|
|
2491426b09 | ||
|
|
5ebdd1390e | ||
|
|
b7f0247575 | ||
|
|
e28186a28a | ||
|
|
de1a7ce48f | ||
|
|
48480fb33b | ||
|
|
f41332fe6b | ||
|
|
1f8b340b8f | ||
|
|
fdaf1d09d3 | ||
|
|
b9682c4f10 | ||
|
|
69dcb4effd | ||
|
|
d50fd0ba91 | ||
|
|
c2c7b4c731 | ||
|
|
952d5f3a3d | ||
|
|
3f126c9ec9 | ||
|
|
0be58ef918 | ||
|
|
8f9053e2fc | ||
|
|
68452e5330 | ||
|
|
2eacc46eaa | ||
|
|
74dcc91ea7 | ||
|
|
dd7bf61323 | ||
|
|
2819d6cace | ||
|
|
75355a6883 | ||
|
|
e9c007d56b | ||
|
|
84c9085516 | ||
|
|
9f36e57c1e | ||
|
|
7528699fc2 | ||
|
|
d280151c18 | ||
|
|
b44c755d25 | ||
|
|
e4078e87a1 | ||
|
|
be36204756 | ||
|
|
b5409d6d00 | ||
|
|
f3d6bce03e |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -1,7 +1,7 @@
|
||||
'name': 'build'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.18.7'
|
||||
'GO_VERSION': '1.18.6'
|
||||
'NODE_VERSION': '14'
|
||||
|
||||
'on':
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -1,7 +1,7 @@
|
||||
'name': 'lint'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.18.7'
|
||||
'GO_VERSION': '1.18.6'
|
||||
|
||||
'on':
|
||||
'push':
|
||||
|
||||
97
CHANGELOG.md
97
CHANGELOG.md
@@ -15,82 +15,15 @@ and this project adheres to
|
||||
## [v0.108.0] - TBA (APPROX.)
|
||||
-->
|
||||
|
||||
## Added
|
||||
|
||||
- The ability to put [ClientIDs][clientid] into DNS-over-HTTPS hostnames as
|
||||
opposed to URL paths ([#3418]). Note that AdGuard Home checks the server name
|
||||
only if the URL does not contain a ClientID.
|
||||
|
||||
[#3418]: https://github.com/AdguardTeam/AdGuardHome/issues/3418
|
||||
|
||||
[clientid]: https://github.com/AdguardTeam/AdGuardHome/wiki/Clients#clientid
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
## [v0.107.17] - 2022-11-02 (APPROX.)
|
||||
|
||||
See also the [v0.107.17 GitHub milestone][ms-v0.107.17].
|
||||
|
||||
[ms-v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/milestone/52?closed=1
|
||||
-->
|
||||
|
||||
|
||||
|
||||
## [v0.107.16] - 2022-10-07
|
||||
|
||||
This is a security update. There is no GitHub milestone, since no GitHub issues
|
||||
were resolved.
|
||||
|
||||
## Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the
|
||||
CVE-2022-2879, CVE-2022-2880, and CVE-2022-41715 Go vulnerabilities fixed in
|
||||
[Go 1.18.7][go-1.18.7].
|
||||
|
||||
[go-1.18.7]: https://groups.google.com/g/golang-announce/c/xtuG5faxtaU
|
||||
|
||||
|
||||
|
||||
## [v0.107.15] - 2022-10-03
|
||||
## [v0.107.15] - 2022-10-26 (APPROX.)
|
||||
|
||||
See also the [v0.107.15 GitHub milestone][ms-v0.107.15].
|
||||
|
||||
### Security
|
||||
|
||||
- As an additional CSRF protection measure, AdGuard Home now ensures that
|
||||
requests that change its state but have no body (such as `POST
|
||||
/control/stats_reset` requests) do not have a `Content-Type` header set on
|
||||
them ([#4970]).
|
||||
|
||||
### Added
|
||||
|
||||
#### Experimental HTTP/3 Support
|
||||
|
||||
See [#3955] and the related issues for more details. These features are still
|
||||
experimental and may break or change in the future.
|
||||
|
||||
- DNS-over-HTTP/3 DNS and web UI client request support. This feature must be
|
||||
explicitly enabled by setting the new property `dns.serve_http3` in the
|
||||
configuration file to `true`.
|
||||
- DNS-over-HTTP upstreams can now upgrade to HTTP/3 if the new configuration
|
||||
file property `dns.use_http3_upstreams` is set to `true`.
|
||||
- Upstreams with forced DNS-over-HTTP/3 and no fallback to prior HTTP versions
|
||||
using the `h3://` scheme.
|
||||
|
||||
### Fixed
|
||||
|
||||
- User-specific blocked services not applying correctly ([#4945], [#4982],
|
||||
[#4983]).
|
||||
- `only application/json is allowed` errors in various APIs ([#4970]).
|
||||
|
||||
[#3955]: https://github.com/AdguardTeam/AdGuardHome/issues/3955
|
||||
[#4945]: https://github.com/AdguardTeam/AdGuardHome/issues/4945
|
||||
[#4970]: https://github.com/AdguardTeam/AdGuardHome/issues/4970
|
||||
[#4982]: https://github.com/AdguardTeam/AdGuardHome/issues/4982
|
||||
[#4983]: https://github.com/AdguardTeam/AdGuardHome/issues/4983
|
||||
|
||||
[ms-v0.107.15]: https://github.com/AdguardTeam/AdGuardHome/milestone/51?closed=1
|
||||
-->
|
||||
|
||||
|
||||
|
||||
@@ -130,8 +63,8 @@ bodies are documented in `openapi/openapi.yaml` and `openapi/CHANGELOG.md`.
|
||||
|
||||
#### Stricter Content-Type Checks (BREAKING API CHANGE)
|
||||
|
||||
All JSON APIs that expect a body now check if the request actually has
|
||||
`Content-Type` set to `application/json`.
|
||||
All JSON APIs now check if the request actually has the `application/json`
|
||||
content-type.
|
||||
|
||||
#### Other Security Changes
|
||||
|
||||
@@ -188,7 +121,7 @@ See also the [v0.107.12 GitHub milestone][ms-v0.107.12].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
CVE-2022-27664 and CVE-2022-32190 Go vulnerabilities fixed in
|
||||
[Go 1.18.6][go-1.18.6].
|
||||
|
||||
@@ -309,7 +242,7 @@ See also the [v0.107.9 GitHub milestone][ms-v0.107.9].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
CVE-2022-32189 Go vulnerability fixed in [Go 1.18.5][go-1.18.5]. Go 1.17
|
||||
support has also been removed, as it has reached end of life and will not
|
||||
receive security updates.
|
||||
@@ -352,7 +285,7 @@ See also the [v0.107.8 GitHub milestone][ms-v0.107.8].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
CVE-2022-1705, CVE-2022-32148, CVE-2022-30631, and other Go vulnerabilities
|
||||
fixed in [Go 1.17.12][go-1.17.12].
|
||||
|
||||
@@ -388,7 +321,7 @@ See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-29526], [CVE-2022-30634], [CVE-2022-30629], [CVE-2022-30580], and
|
||||
[CVE-2022-29804] Go vulnerabilities.
|
||||
- Enforced password strength policy ([#3503]).
|
||||
@@ -545,7 +478,7 @@ See also the [v0.107.6 GitHub milestone][ms-v0.107.6].
|
||||
### Security
|
||||
|
||||
- `User-Agent` HTTP header removed from outgoing DNS-over-HTTPS requests.
|
||||
- Go version has been updated to prevent the possibility of exploiting the
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] Go vulnerabilities.
|
||||
|
||||
### Added
|
||||
@@ -600,7 +533,7 @@ were resolved.
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-24921] Go vulnerability.
|
||||
|
||||
[CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921
|
||||
@@ -613,7 +546,7 @@ See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] Go vulnerabilities.
|
||||
|
||||
### Fixed
|
||||
@@ -1350,13 +1283,11 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...HEAD
|
||||
[v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...v0.107.17
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.15...HEAD
|
||||
[v0.107.15]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...v0.107.15
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...HEAD
|
||||
[v0.107.16]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.15...v0.107.16
|
||||
[v0.107.15]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...v0.107.15
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...HEAD
|
||||
[v0.107.14]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.13...v0.107.14
|
||||
[v0.107.13]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.12...v0.107.13
|
||||
[v0.107.12]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.11...v0.107.12
|
||||
|
||||
3
Makefile
3
Makefile
@@ -34,8 +34,6 @@ YARN_INSTALL_FLAGS = $(YARN_FLAGS) --network-timeout 120000 --silent\
|
||||
--ignore-engines --ignore-optional --ignore-platform\
|
||||
--ignore-scripts
|
||||
|
||||
NEXTAPI = 0
|
||||
|
||||
# Macros for the build-release target. If FRONTEND_PREBUILT is 0, the
|
||||
# default, the macro $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT)) expands
|
||||
# into BUILD_RELEASE_DEPS_0, and so both frontend and backend
|
||||
@@ -63,7 +61,6 @@ ENV = env\
|
||||
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
|
||||
RACE='$(RACE)'\
|
||||
SIGN='$(SIGN)'\
|
||||
NEXTAPI='$(NEXTAPI)'\
|
||||
VERBOSE='$(VERBOSE)'\
|
||||
VERSION='$(VERSION)'\
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.2'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.1'
|
||||
|
||||
'stages':
|
||||
- 'Build frontend':
|
||||
@@ -322,7 +322,7 @@
|
||||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.2'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.1'
|
||||
# release-vX.Y.Z branches are the branches from which the actual final release
|
||||
# is built.
|
||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||
@@ -337,4 +337,4 @@
|
||||
# are the ones that actually get released.
|
||||
'variables':
|
||||
'channel': 'release'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.2'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.1'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'key': 'AHBRTSPECS'
|
||||
'name': 'AdGuard Home - Build and run tests'
|
||||
'variables':
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.2'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.1'
|
||||
|
||||
'stages':
|
||||
- 'Tests':
|
||||
|
||||
@@ -215,7 +215,6 @@
|
||||
"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_doh3": "encrypted DNS-over-HTTPS with forced <0>HTTP/3</0> and no fallback to HTTP/2 or below;",
|
||||
"example_upstream_doq": "encrypted <0>DNS-over-QUIC</0>;",
|
||||
"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);",
|
||||
@@ -606,7 +605,7 @@
|
||||
"blocklist": "Blocklist",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Cache size",
|
||||
"cache_size_desc": "DNS cache size (in bytes). To disable caching, leave empty.",
|
||||
"cache_size_desc": "DNS cache size (in bytes).",
|
||||
"cache_ttl_min_override": "Override minimum TTL",
|
||||
"cache_ttl_max_override": "Override maximum TTL",
|
||||
"enter_cache_size": "Enter cache size (bytes)",
|
||||
|
||||
@@ -10,17 +10,11 @@ class Api {
|
||||
async makeRequest(path, method = 'POST', config) {
|
||||
const url = `${this.baseUrl}/${path}`;
|
||||
|
||||
const axiosConfig = config || {};
|
||||
if (method !== 'GET' && axiosConfig.data) {
|
||||
axiosConfig.headers = axiosConfig.headers || {};
|
||||
axiosConfig.headers['Content-Type'] = axiosConfig.headers['Content-Type'] || 'application/json';
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios({
|
||||
url,
|
||||
method,
|
||||
...axiosConfig,
|
||||
...config,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -61,6 +55,7 @@ class Api {
|
||||
const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS;
|
||||
const config = {
|
||||
data: servers,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
@@ -69,6 +64,7 @@ class Api {
|
||||
const { path, method } = this.GLOBAL_VERSION;
|
||||
const config = {
|
||||
data,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
@@ -104,6 +100,7 @@ class Api {
|
||||
const { path, method } = this.FILTERING_REFRESH;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
|
||||
return this.makeRequest(path, method, parameters);
|
||||
@@ -113,6 +110,7 @@ class Api {
|
||||
const { path, method } = this.FILTERING_ADD_FILTER;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
|
||||
return this.makeRequest(path, method, parameters);
|
||||
@@ -122,6 +120,7 @@ class Api {
|
||||
const { path, method } = this.FILTERING_REMOVE_FILTER;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
|
||||
return this.makeRequest(path, method, parameters);
|
||||
@@ -131,6 +130,7 @@ class Api {
|
||||
const { path, method } = this.FILTERING_SET_RULES;
|
||||
const parameters = {
|
||||
data: rules,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -139,6 +139,7 @@ class Api {
|
||||
const { path, method } = this.FILTERING_CONFIG;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -147,6 +148,7 @@ class Api {
|
||||
const { path, method } = this.FILTERING_SET_URL;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -237,6 +239,7 @@ class Api {
|
||||
const { path, method } = this.CHANGE_LANGUAGE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -272,6 +275,7 @@ class Api {
|
||||
const { path, method } = this.DHCP_SET_CONFIG;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -280,6 +284,7 @@ class Api {
|
||||
const { path, method } = this.DHCP_FIND_ACTIVE;
|
||||
const parameters = {
|
||||
data: req,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -288,6 +293,7 @@ class Api {
|
||||
const { path, method } = this.DHCP_ADD_STATIC_LEASE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -296,6 +302,7 @@ class Api {
|
||||
const { path, method } = this.DHCP_REMOVE_STATIC_LEASE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -326,6 +333,7 @@ class Api {
|
||||
const { path, method } = this.INSTALL_CONFIGURE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -334,6 +342,7 @@ class Api {
|
||||
const { path, method } = this.INSTALL_CHECK_CONFIG;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -354,6 +363,7 @@ class Api {
|
||||
const { path, method } = this.TLS_CONFIG;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -362,6 +372,7 @@ class Api {
|
||||
const { path, method } = this.TLS_VALIDATE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -386,6 +397,7 @@ class Api {
|
||||
const { path, method } = this.ADD_CLIENT;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -394,6 +406,7 @@ class Api {
|
||||
const { path, method } = this.DELETE_CLIENT;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -402,6 +415,7 @@ class Api {
|
||||
const { path, method } = this.UPDATE_CLIENT;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -426,6 +440,7 @@ class Api {
|
||||
const { path, method } = this.ACCESS_SET;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -446,6 +461,7 @@ class Api {
|
||||
const { path, method } = this.REWRITE_ADD;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -454,6 +470,7 @@ class Api {
|
||||
const { path, method } = this.REWRITE_DELETE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -479,6 +496,7 @@ class Api {
|
||||
const { path, method } = this.BLOCKED_SERVICES_SET;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
@@ -506,6 +524,7 @@ class Api {
|
||||
const { path, method } = this.STATS_CONFIG;
|
||||
const config = {
|
||||
data,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
@@ -541,6 +560,7 @@ class Api {
|
||||
const { path, method } = this.QUERY_LOG_CONFIG;
|
||||
const config = {
|
||||
data,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
@@ -557,6 +577,7 @@ class Api {
|
||||
const { path, method } = this.LOGIN;
|
||||
const config = {
|
||||
data,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
@@ -583,6 +604,7 @@ class Api {
|
||||
const { path, method } = this.SET_DNS_CONFIG;
|
||||
const config = {
|
||||
data,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
@@ -57,22 +57,6 @@ const Examples = (props) => (
|
||||
example_upstream_doh
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>h3://unfiltered.adguard-dns.com/dns-query</code>: <Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://en.wikipedia.org/wiki/HTTP/3"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
HTTP/3
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_upstream_doh3
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>quic://unfiltered.adguard-dns.com</code>: <Trans
|
||||
components={[
|
||||
|
||||
16
go.mod
16
go.mod
@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.45.2
|
||||
github.com/AdguardTeam/dnsproxy v0.44.0
|
||||
github.com/AdguardTeam/golibs v0.10.9
|
||||
github.com/AdguardTeam/urlfilter v0.16.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
@@ -18,7 +18,7 @@ require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220822114210-de18a9d48e84
|
||||
github.com/kardianos/service v1.2.1
|
||||
github.com/lucas-clemente/quic-go v0.29.1
|
||||
github.com/lucas-clemente/quic-go v0.29.0
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
|
||||
github.com/mdlayher/netlink v1.6.0
|
||||
// TODO(a.garipov): This package is deprecated; find a new one or use
|
||||
@@ -28,10 +28,10 @@ require (
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/ti-mo/netfilter v0.4.0
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
|
||||
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
||||
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c
|
||||
golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.0
|
||||
@@ -43,12 +43,10 @@ require (
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
|
||||
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
|
||||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
|
||||
github.com/mdlayher/packet v1.0.0 // indirect
|
||||
@@ -59,7 +57,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220922195421-2adab6b8c60e // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 // indirect
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
|
||||
35
go.sum
35
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/AdguardTeam/dnsproxy v0.45.2 h1:K9BXkQAfAKjrzbWbczpA2IA1owLe/edv0nG0e2+Esko=
|
||||
github.com/AdguardTeam/dnsproxy v0.45.2/go.mod h1:h+0r4GDvHHY2Wu6r7oqva+O37h00KofYysfzy1TEXFE=
|
||||
github.com/AdguardTeam/dnsproxy v0.44.0 h1:JzIxEXF4OyJq4wZVHeZkM1af4VfuwcgrUzjgdBGljsE=
|
||||
github.com/AdguardTeam/dnsproxy v0.44.0/go.mod h1:HsxYYW/bC8uo+4eX9pRW21hFD6gWZdrvcfBb1R6/AzU=
|
||||
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
|
||||
github.com/AdguardTeam/golibs v0.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0=
|
||||
@@ -23,8 +23,6 @@ github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1O
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
|
||||
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -90,10 +88,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucas-clemente/quic-go v0.29.1 h1:Z+WMJ++qMLhvpFkRZA+jl3BTxUjm415YBmWanXB8zP0=
|
||||
github.com/lucas-clemente/quic-go v0.29.1/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE=
|
||||
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/lucas-clemente/quic-go v0.29.0 h1:Vw0mGTfmWqGzh4jx/kMymsIkFK6rErFVmg+t9RLrnZE=
|
||||
github.com/lucas-clemente/quic-go v0.29.0/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
|
||||
@@ -129,7 +125,6 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
@@ -173,16 +168,16 @@ go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9 h1:lNtcVz/3bOstm7Vebox+5m3nLh/BYWnhmc3AhXOW6oI=
|
||||
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
|
||||
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
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.6.0-dev.0.20220922195421-2adab6b8c60e h1:WhB000cGjOfbJiedMGvJkMTclI18VD69w27k+sceql8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220922195421-2adab6b8c60e/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 h1:VtCrPQXM5Wo9l7XN64SjBMczl48j8mkP+2e3OhYlz+0=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -194,7 +189,6 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
@@ -206,8 +200,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -230,7 +224,6 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -254,8 +247,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77 h1:C1tElbkWrsSkn3IRl1GCW/gETw1TywWIPgwZtXTZbYg=
|
||||
golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77/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=
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// Package aghchan contains channel utilities.
|
||||
package aghchan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Receive returns an error if it cannot receive a value form c before timeout
|
||||
// runs out.
|
||||
func Receive[T any](c <-chan T, timeout time.Duration) (v T, ok bool, err error) {
|
||||
var zero T
|
||||
timeoutCh := time.After(timeout)
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
// TODO(a.garipov): Consider implementing [errors.Aser] for
|
||||
// os.ErrTimeout.
|
||||
return zero, false, fmt.Errorf("did not receive after %s", timeout)
|
||||
case v, ok = <-c:
|
||||
return v, ok, nil
|
||||
}
|
||||
}
|
||||
|
||||
// MustReceive panics if it cannot receive a value form c before timeout runs
|
||||
// out.
|
||||
func MustReceive[T any](c <-chan T, timeout time.Duration) (v T, ok bool) {
|
||||
v, ok, err := Receive(c, timeout)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return v, ok
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func OK(w http.ResponseWriter) {
|
||||
// Error writes formatted message to w and also logs it.
|
||||
func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...any) {
|
||||
text := fmt.Sprintf(format, args...)
|
||||
log.Error("%s %s %s: %s", r.Method, r.Host, r.URL, text)
|
||||
log.Error("%s %s: %s", r.Method, r.URL, text)
|
||||
http.Error(w, text, code)
|
||||
}
|
||||
|
||||
@@ -62,16 +62,9 @@ func WriteTextPlainDeprecated(w http.ResponseWriter, r *http.Request) (isPlainTe
|
||||
}
|
||||
|
||||
// WriteJSONResponse sets the content-type header in w.Header() to
|
||||
// "application/json", writes a header with a "200 OK" status, encodes resp to
|
||||
// w, calls [Error] on any returned error, and returns it as well.
|
||||
// "application/json", encodes resp to w, calls Error on any returned error, and
|
||||
// returns it as well.
|
||||
func WriteJSONResponse(w http.ResponseWriter, r *http.Request, resp any) (err error) {
|
||||
return WriteJSONResponseCode(w, r, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// WriteJSONResponseCode is like [WriteJSONResponse] but adds the ability to
|
||||
// redefine the status code.
|
||||
func WriteJSONResponseCode(w http.ResponseWriter, r *http.Request, code int, resp any) (err error) {
|
||||
w.WriteHeader(code)
|
||||
w.Header().Set(HdrNameContentType, HdrValApplicationJSON)
|
||||
err = json.NewEncoder(w).Encode(resp)
|
||||
if err != nil {
|
||||
|
||||
@@ -8,8 +8,8 @@ package aghhttp
|
||||
const (
|
||||
HdrNameAcceptEncoding = "Accept-Encoding"
|
||||
HdrNameAccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
||||
HdrNameContentEncoding = "Content-Encoding"
|
||||
HdrNameContentType = "Content-Type"
|
||||
HdrNameContentEncoding = "Content-Encoding"
|
||||
HdrNameServer = "Server"
|
||||
HdrNameTrailer = "Trailer"
|
||||
HdrNameUserAgent = "User-Agent"
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghchan"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
@@ -163,9 +163,15 @@ func TestHostsContainer_refresh(t *testing.T) {
|
||||
checkRefresh := func(t *testing.T, want *HostsRecord) {
|
||||
t.Helper()
|
||||
|
||||
upd, ok := aghchan.MustReceive(hc.Upd(), 1*time.Second)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, upd)
|
||||
var ok bool
|
||||
var upd *netutil.IPMap
|
||||
select {
|
||||
case upd, ok = <-hc.Upd():
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, upd)
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("did not receive after 1s")
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, upd.Len())
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package aghtest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"net"
|
||||
|
||||
@@ -16,8 +15,6 @@ import (
|
||||
|
||||
// Standard Library
|
||||
|
||||
// Package fs
|
||||
|
||||
// type check
|
||||
var _ fs.FS = &FS{}
|
||||
|
||||
@@ -61,8 +58,6 @@ func (fsys *StatFS) Stat(name string) (fs.FileInfo, error) {
|
||||
return fsys.OnStat(name)
|
||||
}
|
||||
|
||||
// Package net
|
||||
|
||||
// type check
|
||||
var _ net.Listener = (*Listener)(nil)
|
||||
|
||||
@@ -88,9 +83,31 @@ func (l *Listener) Close() (err error) {
|
||||
return l.OnClose()
|
||||
}
|
||||
|
||||
// Module AdGuardHome
|
||||
// Module dnsproxy
|
||||
|
||||
// Package aghos
|
||||
// type check
|
||||
var _ upstream.Upstream = (*UpstreamMock)(nil)
|
||||
|
||||
// UpstreamMock is a mock [upstream.Upstream] implementation for tests.
|
||||
//
|
||||
// TODO(a.garipov): Replace with all uses of Upstream with UpstreamMock and
|
||||
// rename it to just Upstream.
|
||||
type UpstreamMock struct {
|
||||
OnAddress func() (addr string)
|
||||
OnExchange func(req *dns.Msg) (resp *dns.Msg, err error)
|
||||
}
|
||||
|
||||
// Address implements the [upstream.Upstream] interface for *UpstreamMock.
|
||||
func (u *UpstreamMock) Address() (addr string) {
|
||||
return u.OnAddress()
|
||||
}
|
||||
|
||||
// Exchange implements the [upstream.Upstream] interface for *UpstreamMock.
|
||||
func (u *UpstreamMock) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return u.OnExchange(req)
|
||||
}
|
||||
|
||||
// Module AdGuardHome
|
||||
|
||||
// type check
|
||||
var _ aghos.FSWatcher = (*FSWatcher)(nil)
|
||||
@@ -116,57 +133,3 @@ func (w *FSWatcher) Add(name string) (err error) {
|
||||
func (w *FSWatcher) Close() (err error) {
|
||||
return w.OnClose()
|
||||
}
|
||||
|
||||
// Package websvc
|
||||
|
||||
// ServiceWithConfig is a mock [websvc.ServiceWithConfig] implementation for
|
||||
// tests.
|
||||
type ServiceWithConfig[ConfigType any] struct {
|
||||
OnStart func() (err error)
|
||||
OnShutdown func(ctx context.Context) (err error)
|
||||
OnConfig func() (c ConfigType)
|
||||
}
|
||||
|
||||
// Start implements the [websvc.ServiceWithConfig] interface for
|
||||
// *ServiceWithConfig.
|
||||
func (s *ServiceWithConfig[_]) Start() (err error) {
|
||||
return s.OnStart()
|
||||
}
|
||||
|
||||
// Shutdown implements the [websvc.ServiceWithConfig] interface for
|
||||
// *ServiceWithConfig.
|
||||
func (s *ServiceWithConfig[_]) Shutdown(ctx context.Context) (err error) {
|
||||
return s.OnShutdown(ctx)
|
||||
}
|
||||
|
||||
// Config implements the [websvc.ServiceWithConfig] interface for
|
||||
// *ServiceWithConfig.
|
||||
func (s *ServiceWithConfig[ConfigType]) Config() (c ConfigType) {
|
||||
return s.OnConfig()
|
||||
}
|
||||
|
||||
// Module dnsproxy
|
||||
|
||||
// Package upstream
|
||||
|
||||
// type check
|
||||
var _ upstream.Upstream = (*UpstreamMock)(nil)
|
||||
|
||||
// UpstreamMock is a mock [upstream.Upstream] implementation for tests.
|
||||
//
|
||||
// TODO(a.garipov): Replace with all uses of Upstream with UpstreamMock and
|
||||
// rename it to just Upstream.
|
||||
type UpstreamMock struct {
|
||||
OnAddress func() (addr string)
|
||||
OnExchange func(req *dns.Msg) (resp *dns.Msg, err error)
|
||||
}
|
||||
|
||||
// Address implements the [upstream.Upstream] interface for *UpstreamMock.
|
||||
func (u *UpstreamMock) Address() (addr string) {
|
||||
return u.OnAddress()
|
||||
}
|
||||
|
||||
// Exchange implements the [upstream.Upstream] interface for *UpstreamMock.
|
||||
func (u *UpstreamMock) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return u.OnExchange(req)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package aghtest_test
|
||||
|
||||
import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
)
|
||||
|
||||
// type check
|
||||
var _ websvc.ServiceWithConfig[struct{}] = (*aghtest.ServiceWithConfig[struct{}])(nil)
|
||||
var _ aghos.FSWatcher = (*aghtest.FSWatcher)(nil)
|
||||
|
||||
@@ -78,7 +78,18 @@ func (s *server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) {
|
||||
status.Leases = s.Leases(LeasesDynamic)
|
||||
status.StaticLeases = s.Leases(LeasesStatic)
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, status)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(w).Encode(status)
|
||||
if err != nil {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
"Unable to marshal DHCP status json: %s",
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) enableDHCP(ifaceName string) (code int, err error) {
|
||||
@@ -235,7 +246,22 @@ func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
s.setConfFromJSON(conf, srv4, srv6)
|
||||
if conf.Enabled != aghalg.NBNull {
|
||||
s.conf.Enabled = conf.Enabled == aghalg.NBTrue
|
||||
}
|
||||
|
||||
if conf.InterfaceName != "" {
|
||||
s.conf.InterfaceName = conf.InterfaceName
|
||||
}
|
||||
|
||||
if srv4 != nil {
|
||||
s.srv4 = srv4
|
||||
}
|
||||
|
||||
if srv6 != nil {
|
||||
s.srv6 = srv6
|
||||
}
|
||||
|
||||
s.conf.ConfigModified()
|
||||
|
||||
err = s.dbLoad()
|
||||
@@ -254,26 +280,6 @@ func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// setConfFromJSON sets configuration parameters in s from the new configuration
|
||||
// decoded from JSON.
|
||||
func (s *server) setConfFromJSON(conf *dhcpServerConfigJSON, srv4, srv6 DHCPServer) {
|
||||
if conf.Enabled != aghalg.NBNull {
|
||||
s.conf.Enabled = conf.Enabled == aghalg.NBTrue
|
||||
}
|
||||
|
||||
if conf.InterfaceName != "" {
|
||||
s.conf.InterfaceName = conf.InterfaceName
|
||||
}
|
||||
|
||||
if srv4 != nil {
|
||||
s.srv4 = srv4
|
||||
}
|
||||
|
||||
if srv6 != nil {
|
||||
s.srv6 = srv6
|
||||
}
|
||||
}
|
||||
|
||||
type netInterfaceJSON struct {
|
||||
Name string `json:"name"`
|
||||
HardwareAddr string `json:"hardware_address"`
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// jsonError is a generic JSON error response.
|
||||
@@ -24,9 +25,15 @@ type jsonError struct {
|
||||
// TODO(a.garipov): Either take the logger from the server after we've
|
||||
// refactored logging or make this not a method of *Server.
|
||||
func (s *server) notImplemented(w http.ResponseWriter, r *http.Request) {
|
||||
_ = aghhttp.WriteJSONResponseCode(w, r, http.StatusNotImplemented, &jsonError{
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
|
||||
err := json.NewEncoder(w).Encode(&jsonError{
|
||||
Message: aghos.Unsupported("dhcp").Error(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Debug("writing 501 json response: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// registerHandlers sets the handlers for DHCP HTTP API that always respond with
|
||||
|
||||
@@ -183,7 +183,15 @@ func (s *Server) accessListJSON() (j accessListJSON) {
|
||||
}
|
||||
|
||||
func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
|
||||
_ = aghhttp.WriteJSONResponse(w, r, s.accessListJSON())
|
||||
j := s.accessListJSON()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(j)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "encoding response: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// validateAccessSet checks the internal accessListJSON lists. To search for
|
||||
|
||||
@@ -123,14 +123,7 @@ type quicConnection interface {
|
||||
func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string, err error) {
|
||||
proto := pctx.Proto
|
||||
if proto == proxy.ProtoHTTPS {
|
||||
clientID, err = clientIDFromDNSContextHTTPS(pctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("checking url: %w", err)
|
||||
} else if clientID != "" {
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// Go on and check the domain name as well.
|
||||
return clientIDFromDNSContextHTTPS(pctx)
|
||||
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
|
||||
return "", nil
|
||||
}
|
||||
@@ -140,9 +133,31 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
|
||||
return "", nil
|
||||
}
|
||||
|
||||
cliSrvName, err := clientServerName(pctx, proto)
|
||||
if err != nil {
|
||||
return "", err
|
||||
cliSrvName := ""
|
||||
switch proto {
|
||||
case proxy.ProtoTLS:
|
||||
conn := pctx.Conn
|
||||
tc, ok := conn.(tlsConn)
|
||||
if !ok {
|
||||
return "", fmt.Errorf(
|
||||
"proxy ctx conn of proto %s is %T, want *tls.Conn",
|
||||
proto,
|
||||
conn,
|
||||
)
|
||||
}
|
||||
|
||||
cliSrvName = tc.ConnectionState().ServerName
|
||||
case proxy.ProtoQUIC:
|
||||
conn, ok := pctx.QUICConnection.(quicConnection)
|
||||
if !ok {
|
||||
return "", fmt.Errorf(
|
||||
"proxy ctx quic conn of proto %s is %T, want quic.Connection",
|
||||
proto,
|
||||
pctx.QUICConnection,
|
||||
)
|
||||
}
|
||||
|
||||
cliSrvName = conn.ConnectionState().TLS.ServerName
|
||||
}
|
||||
|
||||
clientID, err = clientIDFromClientServerName(
|
||||
@@ -156,35 +171,3 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
|
||||
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// clientServerName returns the TLS server name based on the protocol.
|
||||
func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string, err error) {
|
||||
switch proto {
|
||||
case proxy.ProtoHTTPS:
|
||||
if connState := pctx.HTTPRequest.TLS; connState != nil {
|
||||
srvName = pctx.HTTPRequest.TLS.ServerName
|
||||
}
|
||||
case proxy.ProtoQUIC:
|
||||
qConn := pctx.QUICConnection
|
||||
conn, ok := qConn.(quicConnection)
|
||||
if !ok {
|
||||
return "", fmt.Errorf(
|
||||
"proxy ctx quic conn of proto %s is %T, want quic.Connection",
|
||||
proto,
|
||||
qConn,
|
||||
)
|
||||
}
|
||||
|
||||
srvName = conn.ConnectionState().TLS.ServerName
|
||||
case proxy.ProtoTLS:
|
||||
conn := pctx.Conn
|
||||
tc, ok := conn.(tlsConn)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("proxy ctx conn of proto %s is %T, want *tls.Conn", proto, conn)
|
||||
}
|
||||
|
||||
srvName = tc.ConnectionState().ServerName
|
||||
}
|
||||
|
||||
return srvName, nil
|
||||
}
|
||||
|
||||
@@ -160,22 +160,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
|
||||
wantClientID: "insensitive",
|
||||
wantErrMsg: ``,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
name: "https_no_clientid",
|
||||
proto: proxy.ProtoHTTPS,
|
||||
hostSrvName: "example.com",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: "",
|
||||
strictSNI: true,
|
||||
}, {
|
||||
name: "https_clientid",
|
||||
proto: proxy.ProtoHTTPS,
|
||||
hostSrvName: "example.com",
|
||||
cliSrvName: "cli.example.com",
|
||||
wantClientID: "cli",
|
||||
wantErrMsg: "",
|
||||
strictSNI: true,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -189,32 +173,16 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
|
||||
conf: ServerConfig{TLSConfig: tlsConf},
|
||||
}
|
||||
|
||||
var (
|
||||
conn net.Conn
|
||||
qconn quic.Connection
|
||||
httpReq *http.Request
|
||||
)
|
||||
|
||||
switch tc.proto {
|
||||
case proxy.ProtoHTTPS:
|
||||
u := &url.URL{
|
||||
Path: "/dns-query",
|
||||
}
|
||||
|
||||
connState := &tls.ConnectionState{
|
||||
ServerName: tc.cliSrvName,
|
||||
}
|
||||
|
||||
httpReq = &http.Request{
|
||||
URL: u,
|
||||
TLS: connState,
|
||||
}
|
||||
case proxy.ProtoQUIC:
|
||||
qconn = testQUICConnection{
|
||||
var conn net.Conn
|
||||
if tc.proto == proxy.ProtoTLS {
|
||||
conn = testTLSConn{
|
||||
serverName: tc.cliSrvName,
|
||||
}
|
||||
case proxy.ProtoTLS:
|
||||
conn = testTLSConn{
|
||||
}
|
||||
|
||||
var qconn quic.Connection
|
||||
if tc.proto == proxy.ProtoQUIC {
|
||||
qconn = testQUICConnection{
|
||||
serverName: tc.cliSrvName,
|
||||
}
|
||||
}
|
||||
@@ -222,7 +190,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
|
||||
pctx := &proxy.DNSContext{
|
||||
Proto: tc.proto,
|
||||
Conn: conn,
|
||||
HTTPRequest: httpReq,
|
||||
QUICConnection: qconn,
|
||||
}
|
||||
|
||||
@@ -238,76 +205,56 @@ func TestClientIDFromDNSContextHTTPS(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
cliSrvName string
|
||||
wantClientID string
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "no_clientid",
|
||||
path: "/dns-query",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "no_clientid_slash",
|
||||
path: "/dns-query/",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "clientid",
|
||||
path: "/dns-query/cli",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "cli",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "clientid_slash",
|
||||
path: "/dns-query/cli/",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "cli",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "clientid_case",
|
||||
path: "/dns-query/InSeNsItIvE",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "insensitive",
|
||||
wantErrMsg: ``,
|
||||
}, {
|
||||
name: "bad_url",
|
||||
path: "/foo",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `clientid check: invalid path "/foo"`,
|
||||
}, {
|
||||
name: "extra",
|
||||
path: "/dns-query/cli/foo",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `clientid check: invalid path "/dns-query/cli/foo": extra parts`,
|
||||
}, {
|
||||
name: "invalid_clientid",
|
||||
path: "/dns-query/!!!",
|
||||
cliSrvName: "example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `clientid check: invalid clientid "!!!": bad domain name label rune '!'`,
|
||||
}, {
|
||||
name: "both_ids",
|
||||
path: "/dns-query/right",
|
||||
cliSrvName: "wrong.example.com",
|
||||
wantClientID: "right",
|
||||
wantErrMsg: "",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
connState := &tls.ConnectionState{
|
||||
ServerName: tc.cliSrvName,
|
||||
}
|
||||
|
||||
r := &http.Request{
|
||||
URL: &url.URL{
|
||||
Path: tc.path,
|
||||
},
|
||||
TLS: connState,
|
||||
}
|
||||
|
||||
pctx := &proxy.DNSContext{
|
||||
|
||||
@@ -201,10 +201,6 @@ type ServerConfig struct {
|
||||
// Register an HTTP handler
|
||||
HTTPRegister aghhttp.RegisterFunc
|
||||
|
||||
// LocalPTRResolvers is a slice of addresses to be used as upstreams for
|
||||
// resolving PTR queries for local addresses.
|
||||
LocalPTRResolvers []string
|
||||
|
||||
// ResolveClients signals if the RDNS should resolve clients' addresses.
|
||||
ResolveClients bool
|
||||
|
||||
@@ -212,12 +208,9 @@ type ServerConfig struct {
|
||||
// locally-served networks should be resolved via private PTR resolvers.
|
||||
UsePrivateRDNS bool
|
||||
|
||||
// ServeHTTP3 defines if HTTP/3 is be allowed for incoming requests.
|
||||
ServeHTTP3 bool
|
||||
|
||||
// UseHTTP3Upstreams defines if HTTP/3 is be allowed for DNS-over-HTTPS
|
||||
// upstreams.
|
||||
UseHTTP3Upstreams bool
|
||||
// LocalPTRResolvers is a slice of addresses to be used as upstreams for
|
||||
// resolving PTR queries for local addresses.
|
||||
LocalPTRResolvers []string
|
||||
}
|
||||
|
||||
// if any of ServerConfig values are zero, then default values from below are used
|
||||
@@ -233,7 +226,6 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
|
||||
conf = proxy.Config{
|
||||
UDPListenAddr: srvConf.UDPListenAddrs,
|
||||
TCPListenAddr: srvConf.TCPListenAddrs,
|
||||
HTTP3: srvConf.ServeHTTP3,
|
||||
Ratelimit: int(srvConf.Ratelimit),
|
||||
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
||||
RefuseAny: srvConf.RefuseAny,
|
||||
@@ -332,20 +324,6 @@ func (s *Server) initDefaultSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
|
||||
// depending on configuration.
|
||||
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
|
||||
if !http3 {
|
||||
return upstream.DefaultHTTPVersions
|
||||
}
|
||||
|
||||
return []upstream.HTTPVersion{
|
||||
upstream.HTTPVersion3,
|
||||
upstream.HTTPVersion2,
|
||||
upstream.HTTPVersion11,
|
||||
}
|
||||
}
|
||||
|
||||
// prepareUpstreamSettings - prepares upstream DNS server settings
|
||||
func (s *Server) prepareUpstreamSettings() error {
|
||||
// We're setting a customized set of RootCAs
|
||||
@@ -375,14 +353,12 @@ func (s *Server) prepareUpstreamSettings() error {
|
||||
upstreams = s.conf.UpstreamDNS
|
||||
}
|
||||
|
||||
httpVersions := UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams)
|
||||
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
|
||||
upstreamConfig, err := proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: s.conf.BootstrapDNS,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
HTTPVersions: httpVersions,
|
||||
Bootstrap: s.conf.BootstrapDNS,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -395,9 +371,8 @@ func (s *Server) prepareUpstreamSettings() error {
|
||||
uc, err = proxy.ParseUpstreamsConfig(
|
||||
defaultDNS,
|
||||
&upstream.Options{
|
||||
Bootstrap: s.conf.BootstrapDNS,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
HTTPVersions: httpVersions,
|
||||
Bootstrap: s.conf.BootstrapDNS,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -151,7 +151,7 @@ func (s *Server) checkHostRules(host string, rrtype uint16, setts *filtering.Set
|
||||
}
|
||||
|
||||
// filterDNSResponse checks each resource record of the response's answer
|
||||
// section from pctx and returns a non-nil res if at least one of canonical
|
||||
// section from pctx 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(
|
||||
pctx *proxy.DNSContext,
|
||||
|
||||
@@ -112,7 +112,13 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
DefautLocalPTRUpstreams: defLocalPTRUps,
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err = json.NewEncoder(w).Encode(resp); err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "json.Encoder: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (req *jsonDNSConfig) checkBlockingMode() (err error) {
|
||||
@@ -343,10 +349,7 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro
|
||||
|
||||
conf, err = proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: []string{},
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
&upstream.Options{Bootstrap: []string{}, Timeout: DefaultTimeout},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -409,15 +412,7 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet)
|
||||
return nil
|
||||
}
|
||||
|
||||
var protocols = []string{
|
||||
"h3://",
|
||||
"https://",
|
||||
"quic://",
|
||||
"sdns://",
|
||||
"tcp://",
|
||||
"tls://",
|
||||
"udp://",
|
||||
}
|
||||
var protocols = []string{"udp://", "tcp://", "tls://", "https://", "sdns://", "quic://"}
|
||||
|
||||
// validateUpstream returns an error if u alongside with domains is not a valid
|
||||
// upstream configuration. useDefault is true if the upstream is
|
||||
@@ -664,7 +659,24 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
result[host] = "OK"
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, result)
|
||||
jsonVal, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
"Unable to marshal status json: %s",
|
||||
err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(jsonVal)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleDoH is the DNS-over-HTTPs handler.
|
||||
@@ -680,13 +692,11 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) handleDoH(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.conf.TLSAllowUnencryptedDoH && r.TLS == nil {
|
||||
aghhttp.Error(r, w, http.StatusNotFound, "Not Found")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !s.IsRunning() {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "dns server is not running")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
@@ -117,8 +116,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
|
||||
s.conf = tc.conf()
|
||||
s.handleGetConfig(w, nil)
|
||||
|
||||
cType := w.Header().Get(aghhttp.HdrNameContentType)
|
||||
assert.Equal(t, aghhttp.HdrValApplicationJSON, cType)
|
||||
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
||||
assert.JSONEq(t, string(caseWant), w.Body.String())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -453,7 +453,13 @@ func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string) {
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) {
|
||||
_ = aghhttp.WriteJSONResponse(w, r, serviceIDs)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(serviceIDs)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "encoding available services: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -461,7 +467,13 @@ func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Req
|
||||
list := d.Config.BlockedServices
|
||||
d.confLock.RUnlock()
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, list)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(list)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "encoding services: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -301,7 +301,14 @@ func (d *DNSFilter) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err = json.NewEncoder(w).Encode(resp)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type filterJSON struct {
|
||||
@@ -354,7 +361,17 @@ func (d *DNSFilter) handleFilteringStatus(w http.ResponseWriter, r *http.Request
|
||||
resp.UserRules = d.UserRules
|
||||
d.filtersMu.RUnlock()
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
jsonVal, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(jsonVal)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "http write: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set filtering configuration
|
||||
@@ -456,7 +473,11 @@ func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(resp)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "encoding response: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterFilteringHandlers - register handlers
|
||||
|
||||
@@ -240,7 +240,13 @@ func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
d.confLock.Unlock()
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, arr)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(arr)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "json.Encode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -380,13 +381,17 @@ func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
|
||||
resp := &struct {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(&struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}{
|
||||
Enabled: d.Config.SafeBrowsingEnabled,
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -400,11 +405,13 @@ func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
|
||||
resp := &struct {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(&struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}{
|
||||
Enabled: d.Config.ParentalEnabled,
|
||||
})
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -145,13 +146,21 @@ func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
||||
resp := &struct {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(&struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}{
|
||||
Enabled: d.Config.SafeSearchEnabled,
|
||||
})
|
||||
if err != nil {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
"Unable to write response json: %s",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
}
|
||||
|
||||
var safeSearchDomains = map[string]string{
|
||||
|
||||
@@ -12,11 +12,16 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
aghtest.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
func TestNewSessionToken(t *testing.T) {
|
||||
// Successful case.
|
||||
token, err := newSessionToken()
|
||||
|
||||
@@ -456,9 +456,8 @@ func (clients *clientsContainer) findUpstreams(
|
||||
conf, err = proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: config.DNS.BootstrapDNS,
|
||||
Timeout: config.DNS.UpstreamTimeout.Duration,
|
||||
HTTPVersions: dnsforward.UpstreamHTTPVersions(config.DNS.UseHTTP3Upstreams),
|
||||
Bootstrap: config.DNS.BootstrapDNS,
|
||||
Timeout: config.DNS.UpstreamTimeout.Duration,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -166,19 +166,6 @@ type dnsConfig struct {
|
||||
// LocalPTRResolvers is the slice of addresses to be used as upstreams
|
||||
// for PTR queries for locally-served networks.
|
||||
LocalPTRResolvers []string `yaml:"local_ptr_upstreams"`
|
||||
|
||||
// ServeHTTP3 defines if HTTP/3 is be allowed for incoming requests.
|
||||
//
|
||||
// TODO(a.garipov): Add to the UI when HTTP/3 support is no longer
|
||||
// experimental.
|
||||
ServeHTTP3 bool `yaml:"serve_http3"`
|
||||
|
||||
// UseHTTP3Upstreams defines if HTTP/3 is be allowed for DNS-over-HTTPS
|
||||
// upstreams.
|
||||
//
|
||||
// TODO(a.garipov): Add to the UI when HTTP/3 support is no longer
|
||||
// experimental.
|
||||
UseHTTP3Upstreams bool `yaml:"use_http3_upstreams"`
|
||||
}
|
||||
|
||||
type tlsConfigSettings struct {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
@@ -97,16 +97,16 @@ func collectDNSAddresses() (addrs []string, err error) {
|
||||
|
||||
// statusResponse is a response for /control/status endpoint.
|
||||
type statusResponse struct {
|
||||
Version string `json:"version"`
|
||||
Language string `json:"language"`
|
||||
DNSAddrs []string `json:"dns_addresses"`
|
||||
DNSPort int `json:"dns_port"`
|
||||
HTTPPort int `json:"http_port"`
|
||||
IsProtectionEnabled bool `json:"protection_enabled"`
|
||||
// TODO(e.burkov): Inspect if front-end doesn't requires this field as
|
||||
// openapi.yaml declares.
|
||||
IsDHCPAvailable bool `json:"dhcp_available"`
|
||||
IsRunning bool `json:"running"`
|
||||
IsDHCPAvailable bool `json:"dhcp_available"`
|
||||
IsRunning bool `json:"running"`
|
||||
Version string `json:"version"`
|
||||
Language string `json:"language"`
|
||||
}
|
||||
|
||||
func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -125,12 +125,12 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
defer config.RUnlock()
|
||||
|
||||
resp = statusResponse{
|
||||
Version: version.Version(),
|
||||
DNSAddrs: dnsAddrs,
|
||||
DNSPort: config.DNS.Port,
|
||||
HTTPPort: config.BindPort,
|
||||
Language: config.Language,
|
||||
IsRunning: isRunning(),
|
||||
Version: version.Version(),
|
||||
Language: config.Language,
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -154,12 +154,19 @@ type profileJSON struct {
|
||||
}
|
||||
|
||||
func handleGetProfile(w http.ResponseWriter, r *http.Request) {
|
||||
pj := profileJSON{}
|
||||
u := Context.auth.getCurrentUser(r)
|
||||
resp := &profileJSON{
|
||||
Name: u.Name,
|
||||
|
||||
pj.Name = u.Name
|
||||
|
||||
data, err := json.Marshal(pj)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "json.Marshal: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
@@ -189,26 +196,29 @@ func httpRegister(method, url string, handler http.HandlerFunc) {
|
||||
Context.mux.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
|
||||
}
|
||||
|
||||
// ensure returns a wrapped handler that makes sure that the request has the
|
||||
// correct method as well as additional method and header checks.
|
||||
func ensure(
|
||||
method string,
|
||||
handler func(http.ResponseWriter, *http.Request),
|
||||
) (wrapped func(http.ResponseWriter, *http.Request)) {
|
||||
// ----------------------------------
|
||||
// helper functions for HTTP handlers
|
||||
// ----------------------------------
|
||||
func ensure(method string, handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
m, u := r.Method, r.URL
|
||||
log.Debug("started %s %s %s", m, r.Host, u)
|
||||
defer func() { log.Debug("finished %s %s %s in %s", m, r.Host, u, time.Since(start)) }()
|
||||
log.Debug("%s %v", r.Method, r.URL)
|
||||
|
||||
if m != method {
|
||||
aghhttp.Error(r, w, http.StatusMethodNotAllowed, "only method %s is allowed", method)
|
||||
if r.Method != method {
|
||||
aghhttp.Error(r, w, http.StatusMethodNotAllowed, "only %s is allowed", method)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if modifiesData(m) {
|
||||
if !ensureContentType(w, r) {
|
||||
if method == http.MethodPost || method == http.MethodPut || method == http.MethodDelete {
|
||||
if r.Header.Get(aghhttp.HdrNameContentType) != aghhttp.HdrValApplicationJSON {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusUnsupportedMediaType,
|
||||
"only %s is allowed",
|
||||
aghhttp.HdrValApplicationJSON,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -220,42 +230,6 @@ func ensure(
|
||||
}
|
||||
}
|
||||
|
||||
// modifiesData returns true if m is an HTTP method that can modify data.
|
||||
func modifiesData(m string) (ok bool) {
|
||||
return m == http.MethodPost || m == http.MethodPut || m == http.MethodDelete
|
||||
}
|
||||
|
||||
// ensureContentType makes sure that the content type of a data-modifying
|
||||
// request is set correctly. If it is not, ensureContentType writes a response
|
||||
// to w, and ok is false.
|
||||
func ensureContentType(w http.ResponseWriter, r *http.Request) (ok bool) {
|
||||
const statusUnsup = http.StatusUnsupportedMediaType
|
||||
|
||||
cType := r.Header.Get(aghhttp.HdrNameContentType)
|
||||
if r.ContentLength == 0 {
|
||||
if cType == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Assume that browsers always send a content type when submitting HTML
|
||||
// forms and require no content type for requests with no body to make
|
||||
// sure that the request comes from JavaScript.
|
||||
aghhttp.Error(r, w, statusUnsup, "empty body with content-type %q not allowed", cType)
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
const wantCType = aghhttp.HdrValApplicationJSON
|
||||
if cType == wantCType {
|
||||
return true
|
||||
}
|
||||
|
||||
aghhttp.Error(r, w, statusUnsup, "only content-type %s is allowed", wantCType)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func ensurePOST(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return ensure(http.MethodPost, handler)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/lucas-clemente/quic-go/http3"
|
||||
)
|
||||
|
||||
// getAddrsResponse is the response for /install/get_addresses endpoint.
|
||||
@@ -329,7 +328,6 @@ func copyInstallSettings(dst, src *configuration) {
|
||||
// shutdownTimeout is the timeout for shutting HTTP server down operation.
|
||||
const shutdownTimeout = 5 * time.Second
|
||||
|
||||
// shutdownSrv shuts srv down and prints error messages to the log.
|
||||
func shutdownSrv(ctx context.Context, srv *http.Server) {
|
||||
defer log.OnPanic("")
|
||||
|
||||
@@ -338,38 +336,13 @@ func shutdownSrv(ctx context.Context, srv *http.Server) {
|
||||
}
|
||||
|
||||
err := srv.Shutdown(ctx)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
const msgFmt = "shutting down http server %q: %s"
|
||||
if errors.Is(err, context.Canceled) {
|
||||
log.Debug(msgFmt, srv.Addr, err)
|
||||
} else {
|
||||
log.Error(msgFmt, srv.Addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// shutdownSrv3 shuts srv down and prints error messages to the log.
|
||||
//
|
||||
// TODO(a.garipov): Think of a good way to merge with [shutdownSrv].
|
||||
func shutdownSrv3(srv *http3.Server) {
|
||||
defer log.OnPanic("")
|
||||
|
||||
if srv == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := srv.Close()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
const msgFmt = "shutting down http/3 server %q: %s"
|
||||
if errors.Is(err, context.Canceled) {
|
||||
log.Debug(msgFmt, srv.Addr, err)
|
||||
} else {
|
||||
log.Error(msgFmt, srv.Addr, err)
|
||||
if err != nil {
|
||||
const msgFmt = "shutting down http server %q: %s"
|
||||
if errors.Is(err, context.Canceled) {
|
||||
log.Debug(msgFmt, srv.Addr, err)
|
||||
} else {
|
||||
log.Error(msgFmt, srv.Addr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,11 +545,16 @@ func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "encoding check_config: %s", err)
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"Failed to encode 'check_config' JSON data: %s",
|
||||
err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
body := nonBetaReqBody.String()
|
||||
r.Body = io.NopCloser(strings.NewReader(body))
|
||||
r.ContentLength = int64(len(body))
|
||||
@@ -644,7 +622,13 @@ func (web *Web) handleInstallConfigureBeta(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "encoding configure: %s", err)
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"Failed to encode 'check_config' JSON data: %s",
|
||||
err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -246,13 +246,10 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||
newConf.FilterHandler = applyAdditionalFiltering
|
||||
newConf.GetCustomUpstreamByClient = Context.clients.findUpstreams
|
||||
|
||||
newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
|
||||
newConf.UpstreamTimeout = dnsConf.UpstreamTimeout.Duration
|
||||
|
||||
newConf.ResolveClients = config.Clients.Sources.RDNS
|
||||
newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS
|
||||
newConf.ServeHTTP3 = dnsConf.ServeHTTP3
|
||||
newConf.UseHTTP3Upstreams = dnsConf.UseHTTP3Upstreams
|
||||
newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
|
||||
newConf.UpstreamTimeout = dnsConf.UpstreamTimeout.Duration
|
||||
|
||||
return newConf, nil
|
||||
}
|
||||
@@ -361,13 +358,7 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering
|
||||
log.Debug("%s: using settings for client %q (%s; %q)", pref, c.Name, clientIP, clientID)
|
||||
|
||||
if c.UseOwnBlockedServices {
|
||||
// TODO(e.burkov): Get rid of this crutch.
|
||||
svcs := c.BlockedServices
|
||||
if svcs == nil {
|
||||
svcs = []string{}
|
||||
}
|
||||
Context.filters.ApplyBlockedServices(setts, svcs)
|
||||
log.Debug("%s: services for client %q set: %s", pref, c.Name, svcs)
|
||||
Context.filters.ApplyBlockedServices(setts, c.BlockedServices)
|
||||
}
|
||||
|
||||
setts.ClientName = c.Name
|
||||
|
||||
@@ -97,15 +97,9 @@ var Context homeContext
|
||||
|
||||
// Main is the entry point
|
||||
func Main(clientBuildFS fs.FS) {
|
||||
initCmdLineOpts()
|
||||
|
||||
// The configuration file path can be overridden, but other command-line
|
||||
// options have to override config values. Therefore, do it manually
|
||||
// instead of using package flag.
|
||||
//
|
||||
// TODO(a.garipov): The comment above is most likely false. Replace with
|
||||
// package flag.
|
||||
opts := loadCmdLineOpts()
|
||||
// config can be specified, which reads options from there, but other command line flags have to override config values
|
||||
// therefore, we must do it manually instead of using a lib
|
||||
args := loadOptions()
|
||||
|
||||
Context.appSignalChannel = make(chan os.Signal)
|
||||
signal.Notify(Context.appSignalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||
@@ -126,18 +120,26 @@ func Main(clientBuildFS fs.FS) {
|
||||
}
|
||||
}()
|
||||
|
||||
if opts.serviceControlAction != "" {
|
||||
handleServiceControlAction(opts, clientBuildFS)
|
||||
if args.serviceControlAction != "" {
|
||||
handleServiceControlAction(args, clientBuildFS)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// run the protection
|
||||
run(opts, clientBuildFS)
|
||||
run(args, clientBuildFS)
|
||||
}
|
||||
|
||||
func setupContext(opts options) {
|
||||
setupContextFlags(opts)
|
||||
func setupContext(args options) {
|
||||
Context.runningAsService = args.runningAsService
|
||||
Context.disableUpdate = args.disableUpdate ||
|
||||
version.Channel() == version.ChannelDevelopment
|
||||
|
||||
Context.firstRun = detectFirstRun()
|
||||
if Context.firstRun {
|
||||
log.Info("This is the first time AdGuard Home is launched")
|
||||
checkPermissions()
|
||||
}
|
||||
|
||||
switch version.Channel() {
|
||||
case version.ChannelEdge, version.ChannelDevelopment:
|
||||
@@ -172,13 +174,13 @@ func setupContext(opts options) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if opts.checkConfig {
|
||||
if args.checkConfig {
|
||||
log.Info("configuration file is ok")
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if !opts.noEtcHosts && config.Clients.Sources.HostsFile {
|
||||
if !args.noEtcHosts && config.Clients.Sources.HostsFile {
|
||||
err = setupHostsContainer()
|
||||
fatalOnError(err)
|
||||
}
|
||||
@@ -187,24 +189,6 @@ func setupContext(opts options) {
|
||||
Context.mux = http.NewServeMux()
|
||||
}
|
||||
|
||||
// setupContextFlags sets global flags and prints their status to the log.
|
||||
func setupContextFlags(opts options) {
|
||||
Context.firstRun = detectFirstRun()
|
||||
if Context.firstRun {
|
||||
log.Info("This is the first time AdGuard Home is launched")
|
||||
checkPermissions()
|
||||
}
|
||||
|
||||
Context.runningAsService = opts.runningAsService
|
||||
// Don't print the runningAsService flag, since that has already been done
|
||||
// in [run].
|
||||
|
||||
Context.disableUpdate = opts.disableUpdate || version.Channel() == version.ChannelDevelopment
|
||||
if Context.disableUpdate {
|
||||
log.Info("AdGuard Home updates are disabled")
|
||||
}
|
||||
}
|
||||
|
||||
// logIfUnsupported logs a formatted warning if the error is one of the
|
||||
// unsupported errors and returns nil. If err is nil, logIfUnsupported returns
|
||||
// nil. Otherwise, it returns err.
|
||||
@@ -286,7 +270,7 @@ func setupHostsContainer() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupConfig(opts options) (err error) {
|
||||
func setupConfig(args options) (err error) {
|
||||
config.DNS.DnsfilterConf.EtcHosts = Context.etcHosts
|
||||
config.DNS.DnsfilterConf.ConfigModified = onConfigModified
|
||||
config.DNS.DnsfilterConf.HTTPRegister = httpRegister
|
||||
@@ -328,9 +312,9 @@ func setupConfig(opts options) (err error) {
|
||||
|
||||
Context.clients.Init(config.Clients.Persistent, Context.dhcpServer, Context.etcHosts, arpdb)
|
||||
|
||||
if opts.bindPort != 0 {
|
||||
if args.bindPort != 0 {
|
||||
tcpPorts := aghalg.UniqChecker[tcpPort]{}
|
||||
addPorts(tcpPorts, tcpPort(opts.bindPort), tcpPort(config.BetaBindPort))
|
||||
addPorts(tcpPorts, tcpPort(args.bindPort), tcpPort(config.BetaBindPort))
|
||||
|
||||
udpPorts := aghalg.UniqChecker[udpPort]{}
|
||||
addPorts(udpPorts, udpPort(config.DNS.Port))
|
||||
@@ -352,23 +336,23 @@ func setupConfig(opts options) (err error) {
|
||||
return fmt.Errorf("validating udp ports: %w", err)
|
||||
}
|
||||
|
||||
config.BindPort = opts.bindPort
|
||||
config.BindPort = args.bindPort
|
||||
}
|
||||
|
||||
// override bind host/port from the console
|
||||
if opts.bindHost != nil {
|
||||
config.BindHost = opts.bindHost
|
||||
if args.bindHost != nil {
|
||||
config.BindHost = args.bindHost
|
||||
}
|
||||
if len(opts.pidFile) != 0 && writePIDFile(opts.pidFile) {
|
||||
Context.pidFileName = opts.pidFile
|
||||
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
|
||||
Context.pidFileName = args.pidFile
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initWeb(opts options, clientBuildFS fs.FS) (web *Web, err error) {
|
||||
func initWeb(args options, clientBuildFS fs.FS) (web *Web, err error) {
|
||||
var clientFS, clientBetaFS fs.FS
|
||||
if opts.localFrontend {
|
||||
if args.localFrontend {
|
||||
log.Info("warning: using local frontend files")
|
||||
|
||||
clientFS = os.DirFS("build/static")
|
||||
@@ -397,11 +381,9 @@ func initWeb(opts options, clientBuildFS fs.FS) (web *Web, err error) {
|
||||
|
||||
clientFS: clientFS,
|
||||
clientBetaFS: clientBetaFS,
|
||||
|
||||
serveHTTP3: config.DNS.ServeHTTP3,
|
||||
}
|
||||
|
||||
web = newWeb(&webConf)
|
||||
web = CreateWeb(&webConf)
|
||||
if web == nil {
|
||||
return nil, fmt.Errorf("initializing web: %w", err)
|
||||
}
|
||||
@@ -416,24 +398,24 @@ func fatalOnError(err error) {
|
||||
}
|
||||
|
||||
// run configures and starts AdGuard Home.
|
||||
func run(opts options, clientBuildFS fs.FS) {
|
||||
func run(args options, clientBuildFS fs.FS) {
|
||||
// configure config filename
|
||||
initConfigFilename(opts)
|
||||
initConfigFilename(args)
|
||||
|
||||
// configure working dir and config path
|
||||
initWorkingDir(opts)
|
||||
initWorkingDir(args)
|
||||
|
||||
// configure log level and output
|
||||
configureLogger(opts)
|
||||
configureLogger(args)
|
||||
|
||||
// Print the first message after logger is configured.
|
||||
log.Info(version.Full())
|
||||
log.Debug("current working directory is %s", Context.workDir)
|
||||
if opts.runningAsService {
|
||||
if args.runningAsService {
|
||||
log.Info("AdGuard Home is running as a service")
|
||||
}
|
||||
|
||||
setupContext(opts)
|
||||
setupContext(args)
|
||||
|
||||
err := configureOS(config)
|
||||
fatalOnError(err)
|
||||
@@ -443,7 +425,7 @@ func run(opts options, clientBuildFS fs.FS) {
|
||||
// but also avoid relying on automatic Go init() function
|
||||
filtering.InitModule()
|
||||
|
||||
err = setupConfig(opts)
|
||||
err = setupConfig(args)
|
||||
fatalOnError(err)
|
||||
|
||||
if !Context.firstRun {
|
||||
@@ -472,7 +454,7 @@ func run(opts options, clientBuildFS fs.FS) {
|
||||
}
|
||||
|
||||
sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
|
||||
GLMode = opts.glinetMode
|
||||
GLMode = args.glinetMode
|
||||
var rateLimiter *authRateLimiter
|
||||
if config.AuthAttempts > 0 && config.AuthBlockMin > 0 {
|
||||
rateLimiter = newAuthRateLimiter(
|
||||
@@ -499,7 +481,7 @@ func run(opts options, clientBuildFS fs.FS) {
|
||||
log.Fatalf("Can't initialize TLS module")
|
||||
}
|
||||
|
||||
Context.web, err = initWeb(opts, clientBuildFS)
|
||||
Context.web, err = initWeb(args, clientBuildFS)
|
||||
fatalOnError(err)
|
||||
|
||||
if !Context.firstRun {
|
||||
@@ -591,10 +573,10 @@ func writePIDFile(fn string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func initConfigFilename(opts options) {
|
||||
func initConfigFilename(args options) {
|
||||
// config file path can be overridden by command-line arguments:
|
||||
if opts.confFilename != "" {
|
||||
Context.configFilename = opts.confFilename
|
||||
if args.configFilename != "" {
|
||||
Context.configFilename = args.configFilename
|
||||
} else {
|
||||
// Default config file name
|
||||
Context.configFilename = "AdGuardHome.yaml"
|
||||
@@ -603,15 +585,15 @@ func initConfigFilename(opts options) {
|
||||
|
||||
// initWorkingDir initializes the workDir
|
||||
// if no command-line arguments specified, we use the directory where our binary file is located
|
||||
func initWorkingDir(opts options) {
|
||||
func initWorkingDir(args options) {
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if opts.workDir != "" {
|
||||
if args.workDir != "" {
|
||||
// If there is a custom config file, use it's directory as our working dir
|
||||
Context.workDir = opts.workDir
|
||||
Context.workDir = args.workDir
|
||||
} else {
|
||||
Context.workDir = filepath.Dir(execPath)
|
||||
}
|
||||
@@ -625,15 +607,15 @@ func initWorkingDir(opts options) {
|
||||
}
|
||||
|
||||
// configureLogger configures logger level and output
|
||||
func configureLogger(opts options) {
|
||||
func configureLogger(args options) {
|
||||
ls := getLogSettings()
|
||||
|
||||
// command-line arguments can override config settings
|
||||
if opts.verbose || config.Verbose {
|
||||
if args.verbose || config.Verbose {
|
||||
ls.Verbose = true
|
||||
}
|
||||
if opts.logFile != "" {
|
||||
ls.File = opts.logFile
|
||||
if args.logFile != "" {
|
||||
ls.File = args.logFile
|
||||
} else if config.File != "" {
|
||||
ls.File = config.File
|
||||
}
|
||||
@@ -654,7 +636,7 @@ func configureLogger(opts options) {
|
||||
// happen pretty quickly.
|
||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
||||
|
||||
if opts.runningAsService && ls.File == "" && runtime.GOOS == "windows" {
|
||||
if args.runningAsService && ls.File == "" && runtime.GOOS == "windows" {
|
||||
// When running as a Windows service, use eventlog by default if nothing
|
||||
// else is configured. Otherwise, we'll simply lose the log output.
|
||||
ls.File = configSyslog
|
||||
@@ -744,29 +726,25 @@ func exitWithError() {
|
||||
os.Exit(64)
|
||||
}
|
||||
|
||||
// loadCmdLineOpts reads command line arguments and initializes configuration
|
||||
// from them. If there is an error or an effect, loadCmdLineOpts processes them
|
||||
// and exits.
|
||||
func loadCmdLineOpts() (opts options) {
|
||||
opts, eff, err := parseCmdOpts(os.Args[0], os.Args[1:])
|
||||
// loadOptions reads command line arguments and initializes configuration
|
||||
func loadOptions() options {
|
||||
o, f, err := parse(os.Args[0], os.Args[1:])
|
||||
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
printHelp(os.Args[0])
|
||||
|
||||
_ = printHelp(os.Args[0])
|
||||
exitWithError()
|
||||
}
|
||||
|
||||
if eff != nil {
|
||||
err = eff()
|
||||
} else if f != nil {
|
||||
err = f()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
exitWithError()
|
||||
} else {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
return opts
|
||||
return o
|
||||
}
|
||||
|
||||
// printWebAddrs prints addresses built from proto, addr, and an appropriate
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
aghtest.DiscardLogOutput(m)
|
||||
initCmdLineOpts()
|
||||
}
|
||||
@@ -5,60 +5,30 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Replace with package flag.
|
||||
|
||||
// options represents the command-line options.
|
||||
// options passed from command-line arguments
|
||||
type options struct {
|
||||
// confFilename is the path to the configuration file.
|
||||
confFilename string
|
||||
verbose bool // is verbose logging enabled
|
||||
configFilename string // path to the config file
|
||||
workDir string // path to the working directory where we will store the filters data and the querylog
|
||||
bindHost net.IP // host address to bind HTTP server on
|
||||
bindPort int // port to serve HTTP pages on
|
||||
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
|
||||
pidFile string // File name to save PID to
|
||||
checkConfig bool // Check configuration and exit
|
||||
disableUpdate bool // If set, don't check for updates
|
||||
|
||||
// workDir is the path to the working directory where AdGuard Home stores
|
||||
// filter data, the query log, and other data.
|
||||
workDir string
|
||||
|
||||
// logFile is the path to the log file. If empty, AdGuard Home writes to
|
||||
// stdout; if "syslog", to syslog.
|
||||
logFile string
|
||||
|
||||
// pidFile is the file name for the file to which the PID is saved.
|
||||
pidFile string
|
||||
|
||||
// serviceControlAction is the service action to perform. See
|
||||
// [service.ControlAction] and [handleServiceControlAction].
|
||||
// service control action (see service.ControlAction array + "status" command)
|
||||
serviceControlAction string
|
||||
|
||||
// bindHost is the address on which to serve the HTTP UI.
|
||||
bindHost net.IP
|
||||
|
||||
// bindPort is the port on which to serve the HTTP UI.
|
||||
bindPort int
|
||||
|
||||
// checkConfig is true if the current invocation is only required to check
|
||||
// the configuration file and exit.
|
||||
checkConfig bool
|
||||
|
||||
// disableUpdate, if set, makes AdGuard Home not check for updates.
|
||||
disableUpdate bool
|
||||
|
||||
// verbose shows if verbose logging is enabled.
|
||||
verbose bool
|
||||
|
||||
// runningAsService flag is set to true when options are passed from the
|
||||
// service runner
|
||||
//
|
||||
// TODO(a.garipov): Perhaps this could be determined by a non-empty
|
||||
// serviceControlAction?
|
||||
// runningAsService flag is set to true when options are passed from the service runner
|
||||
runningAsService bool
|
||||
|
||||
// glinetMode shows if the GL-Inet compatibility mode is enabled.
|
||||
glinetMode bool
|
||||
glinetMode bool // Activate GL-Inet compatibility mode
|
||||
|
||||
// noEtcHosts flag should be provided when /etc/hosts file shouldn't be
|
||||
// used.
|
||||
@@ -69,85 +39,88 @@ type options struct {
|
||||
localFrontend bool
|
||||
}
|
||||
|
||||
// initCmdLineOpts completes initialization of the global command-line option
|
||||
// slice. It must only be called once.
|
||||
func initCmdLineOpts() {
|
||||
// The --help option cannot be put directly into cmdLineOpts, because that
|
||||
// causes initialization cycle due to printHelp referencing cmdLineOpts.
|
||||
cmdLineOpts = append(cmdLineOpts, cmdLineOpt{
|
||||
updateWithValue: nil,
|
||||
updateNoValue: nil,
|
||||
effect: func(o options, exec string) (effect, error) {
|
||||
return func() error { printHelp(exec); exitWithError(); return nil }, nil
|
||||
},
|
||||
serialize: func(o options) (val string, ok bool) { return "", false },
|
||||
description: "Print this help.",
|
||||
longName: "help",
|
||||
shortName: "",
|
||||
})
|
||||
// functions used for their side-effects
|
||||
type effect func() error
|
||||
|
||||
type arg struct {
|
||||
description string // a short, English description of the argument
|
||||
longName string // the name of the argument used after '--'
|
||||
shortName string // the name of the argument used after '-'
|
||||
|
||||
// only one of updateWithValue, updateNoValue, and effect should be present
|
||||
|
||||
updateWithValue func(o options, v string) (options, error) // the mutator for arguments with parameters
|
||||
updateNoValue func(o options) (options, error) // the mutator for arguments without parameters
|
||||
effect func(o options, exec string) (f effect, err error) // the side-effect closure generator
|
||||
|
||||
serialize func(o options) []string // the re-serialization function back to arguments (return nil for omit)
|
||||
}
|
||||
|
||||
// effect is the type for functions used for their side-effects.
|
||||
type effect func() (err error)
|
||||
// {type}SliceOrNil functions check their parameter of type {type}
|
||||
// against its zero value and return nil if the parameter value is
|
||||
// zero otherwise they return a string slice of the parameter
|
||||
|
||||
// cmdLineOpt contains the data for a single command-line option. Only one of
|
||||
// updateWithValue, updateNoValue, and effect must be present.
|
||||
type cmdLineOpt struct {
|
||||
updateWithValue func(o options, v string) (updated options, err error)
|
||||
updateNoValue func(o options) (updated options, err error)
|
||||
effect func(o options, exec string) (eff effect, err error)
|
||||
func ipSliceOrNil(ip net.IP) []string {
|
||||
if ip == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// serialize is a function that encodes the option into a slice of
|
||||
// command-line arguments, if necessary. If ok is false, this option should
|
||||
// be skipped.
|
||||
serialize func(o options) (val string, ok bool)
|
||||
|
||||
description string
|
||||
longName string
|
||||
shortName string
|
||||
return []string{ip.String()}
|
||||
}
|
||||
|
||||
// cmdLineOpts are all command-line options of AdGuard Home.
|
||||
var cmdLineOpts = []cmdLineOpt{{
|
||||
updateWithValue: func(o options, v string) (options, error) {
|
||||
o.confFilename = v
|
||||
return o, nil
|
||||
},
|
||||
updateNoValue: nil,
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) {
|
||||
return o.confFilename, o.confFilename != ""
|
||||
},
|
||||
description: "Path to the config file.",
|
||||
longName: "config",
|
||||
shortName: "c",
|
||||
}, {
|
||||
updateWithValue: func(o options, v string) (options, error) { o.workDir = v; return o, nil },
|
||||
updateNoValue: nil,
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) { return o.workDir, o.workDir != "" },
|
||||
description: "Path to the working directory.",
|
||||
longName: "work-dir",
|
||||
shortName: "w",
|
||||
}, {
|
||||
updateWithValue: func(o options, v string) (options, error) {
|
||||
o.bindHost = net.ParseIP(v)
|
||||
return o, nil
|
||||
},
|
||||
updateNoValue: nil,
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) {
|
||||
if o.bindHost == nil {
|
||||
return "", false
|
||||
}
|
||||
func stringSliceOrNil(s string) []string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return o.bindHost.String(), true
|
||||
},
|
||||
description: "Host address to bind HTTP server on.",
|
||||
longName: "host",
|
||||
shortName: "h",
|
||||
}, {
|
||||
updateWithValue: func(o options, v string) (options, error) {
|
||||
return []string{s}
|
||||
}
|
||||
|
||||
func intSliceOrNil(i int) []string {
|
||||
if i == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []string{strconv.Itoa(i)}
|
||||
}
|
||||
|
||||
func boolSliceOrNil(b bool) []string {
|
||||
if b {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var args []arg
|
||||
|
||||
var configArg = arg{
|
||||
"Path to the config file.",
|
||||
"config", "c",
|
||||
func(o options, v string) (options, error) { o.configFilename = v; return o, nil },
|
||||
nil,
|
||||
nil,
|
||||
func(o options) []string { return stringSliceOrNil(o.configFilename) },
|
||||
}
|
||||
|
||||
var workDirArg = arg{
|
||||
"Path to the working directory.",
|
||||
"work-dir", "w",
|
||||
func(o options, v string) (options, error) { o.workDir = v; return o, nil }, nil, nil,
|
||||
func(o options) []string { return stringSliceOrNil(o.workDir) },
|
||||
}
|
||||
|
||||
var hostArg = arg{
|
||||
"Host address to bind HTTP server on.",
|
||||
"host", "h",
|
||||
func(o options, v string) (options, error) { o.bindHost = net.ParseIP(v); return o, nil }, nil, nil,
|
||||
func(o options) []string { return ipSliceOrNil(o.bindHost) },
|
||||
}
|
||||
|
||||
var portArg = arg{
|
||||
"Port to serve HTTP pages on.",
|
||||
"port", "p",
|
||||
func(o options, v string) (options, error) {
|
||||
var err error
|
||||
var p int
|
||||
minPort, maxPort := 0, 1<<16-1
|
||||
@@ -158,119 +131,78 @@ var cmdLineOpts = []cmdLineOpt{{
|
||||
} else {
|
||||
o.bindPort = p
|
||||
}
|
||||
|
||||
return o, err
|
||||
},
|
||||
updateNoValue: nil,
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) {
|
||||
if o.bindPort == 0 {
|
||||
return "", false
|
||||
}
|
||||
}, nil, nil,
|
||||
func(o options) []string { return intSliceOrNil(o.bindPort) },
|
||||
}
|
||||
|
||||
return strconv.Itoa(o.bindPort), true
|
||||
},
|
||||
description: "Port to serve HTTP pages on.",
|
||||
longName: "port",
|
||||
shortName: "p",
|
||||
}, {
|
||||
updateWithValue: func(o options, v string) (options, error) {
|
||||
var serviceArg = arg{
|
||||
"Service control action: status, install, uninstall, start, stop, restart, reload (configuration).",
|
||||
"service", "s",
|
||||
func(o options, v string) (options, error) {
|
||||
o.serviceControlAction = v
|
||||
return o, nil
|
||||
},
|
||||
updateNoValue: nil,
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) {
|
||||
return o.serviceControlAction, o.serviceControlAction != ""
|
||||
},
|
||||
description: `Service control action: status, install (as a service), ` +
|
||||
`uninstall (as a service), start, stop, restart, reload (configuration).`,
|
||||
longName: "service",
|
||||
shortName: "s",
|
||||
}, {
|
||||
updateWithValue: func(o options, v string) (options, error) { o.logFile = v; return o, nil },
|
||||
updateNoValue: nil,
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) { return o.logFile, o.logFile != "" },
|
||||
description: `Path to log file. If empty, write to stdout; ` +
|
||||
`if "syslog", write to system log.`,
|
||||
longName: "logfile",
|
||||
shortName: "l",
|
||||
}, {
|
||||
updateWithValue: func(o options, v string) (options, error) { o.pidFile = v; return o, nil },
|
||||
updateNoValue: nil,
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) { return o.pidFile, o.pidFile != "" },
|
||||
description: "Path to a file where PID is stored.",
|
||||
longName: "pidfile",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: func(o options) (options, error) { o.checkConfig = true; return o, nil },
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) { return "", o.checkConfig },
|
||||
description: "Check configuration and exit.",
|
||||
longName: "check-config",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: func(o options) (options, error) { o.disableUpdate = true; return o, nil },
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) { return "", o.disableUpdate },
|
||||
description: "Don't check for updates.",
|
||||
longName: "no-check-update",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: nil,
|
||||
effect: func(_ options, _ string) (f effect, err error) {
|
||||
}, nil, nil,
|
||||
func(o options) []string { return stringSliceOrNil(o.serviceControlAction) },
|
||||
}
|
||||
|
||||
var logfileArg = arg{
|
||||
"Path to log file. If empty: write to stdout; if 'syslog': write to system log.",
|
||||
"logfile", "l",
|
||||
func(o options, v string) (options, error) { o.logFile = v; return o, nil }, nil, nil,
|
||||
func(o options) []string { return stringSliceOrNil(o.logFile) },
|
||||
}
|
||||
|
||||
var pidfileArg = arg{
|
||||
"Path to a file where PID is stored.",
|
||||
"pidfile", "",
|
||||
func(o options, v string) (options, error) { o.pidFile = v; return o, nil }, nil, nil,
|
||||
func(o options) []string { return stringSliceOrNil(o.pidFile) },
|
||||
}
|
||||
|
||||
var checkConfigArg = arg{
|
||||
"Check configuration and exit.",
|
||||
"check-config", "",
|
||||
nil, func(o options) (options, error) { o.checkConfig = true; return o, nil }, nil,
|
||||
func(o options) []string { return boolSliceOrNil(o.checkConfig) },
|
||||
}
|
||||
|
||||
var noCheckUpdateArg = arg{
|
||||
"Don't check for updates.",
|
||||
"no-check-update", "",
|
||||
nil, func(o options) (options, error) { o.disableUpdate = true; return o, nil }, nil,
|
||||
func(o options) []string { return boolSliceOrNil(o.disableUpdate) },
|
||||
}
|
||||
|
||||
var disableMemoryOptimizationArg = arg{
|
||||
"Deprecated. 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
|
||||
},
|
||||
serialize: func(o options) (val string, ok bool) { return "", false },
|
||||
description: "Deprecated. Disable memory optimization.",
|
||||
longName: "no-mem-optimization",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: func(o options) (options, error) { o.noEtcHosts = true; return o, nil },
|
||||
effect: func(_ options, _ string) (f effect, err error) {
|
||||
log.Info(
|
||||
"warning: --no-etc-hosts flag is deprecated and will be removed in the future versions",
|
||||
)
|
||||
func(o options) []string { return nil },
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
serialize: func(o options) (val string, ok bool) { return "", o.noEtcHosts },
|
||||
description: "Deprecated. Do not use the OS-provided hosts.",
|
||||
longName: "no-etc-hosts",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: func(o options) (options, error) { o.localFrontend = true; return o, nil },
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) { return "", o.localFrontend },
|
||||
description: "Use local frontend directories.",
|
||||
longName: "local-frontend",
|
||||
var verboseArg = arg{
|
||||
"Enable verbose output.",
|
||||
"verbose", "v",
|
||||
nil, func(o options) (options, error) { o.verbose = true; return o, nil }, nil,
|
||||
func(o options) []string { return boolSliceOrNil(o.verbose) },
|
||||
}
|
||||
|
||||
var glinetArg = arg{
|
||||
"Run in GL-Inet compatibility mode.",
|
||||
"glinet", "",
|
||||
nil, func(o options) (options, error) { o.glinetMode = true; return o, nil }, nil,
|
||||
func(o options) []string { return boolSliceOrNil(o.glinetMode) },
|
||||
}
|
||||
|
||||
var versionArg = arg{
|
||||
description: "Show the version and exit. Show more detailed version description with -v.",
|
||||
longName: "version",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: func(o options) (options, error) { o.verbose = true; return o, nil },
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) { return "", o.verbose },
|
||||
description: "Enable verbose output.",
|
||||
longName: "verbose",
|
||||
shortName: "v",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: func(o options) (options, error) { o.glinetMode = true; return o, nil },
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) { return "", o.glinetMode },
|
||||
description: "Run in GL-Inet compatibility mode.",
|
||||
longName: "glinet",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: nil,
|
||||
effect: func(o options, exec string) (effect, error) {
|
||||
@@ -280,178 +212,176 @@ var cmdLineOpts = []cmdLineOpt{{
|
||||
} else {
|
||||
fmt.Println(version.Full())
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
|
||||
return nil
|
||||
}, nil
|
||||
},
|
||||
serialize: func(o options) (val string, ok bool) { return "", false },
|
||||
description: "Show the version and exit. Show more detailed version description with -v.",
|
||||
longName: "version",
|
||||
shortName: "",
|
||||
}}
|
||||
serialize: func(o options) []string { return nil },
|
||||
}
|
||||
|
||||
// printHelp prints the entire help message. It exits with an error code if
|
||||
// there are any I/O errors.
|
||||
func printHelp(exec string) {
|
||||
b := &strings.Builder{}
|
||||
var helpArg = arg{
|
||||
"Print this help.",
|
||||
"help", "",
|
||||
nil, nil, func(o options, exec string) (effect, error) {
|
||||
return func() error { _ = printHelp(exec); os.Exit(64); return nil }, nil
|
||||
},
|
||||
func(o options) []string { return nil },
|
||||
}
|
||||
|
||||
stringutil.WriteToBuilder(
|
||||
b,
|
||||
"Usage:\n\n",
|
||||
fmt.Sprintf("%s [options]\n\n", exec),
|
||||
"Options:\n",
|
||||
)
|
||||
var noEtcHostsArg = arg{
|
||||
description: "Deprecated. Do not use the OS-provided hosts.",
|
||||
longName: "no-etc-hosts",
|
||||
shortName: "",
|
||||
updateWithValue: nil,
|
||||
updateNoValue: func(o options) (options, error) { o.noEtcHosts = true; return o, nil },
|
||||
effect: func(_ options, _ string) (f effect, err error) {
|
||||
log.Info(
|
||||
"warning: --no-etc-hosts flag is deprecated and will be removed in the future versions",
|
||||
)
|
||||
|
||||
var err error
|
||||
for _, opt := range cmdLineOpts {
|
||||
return nil, nil
|
||||
},
|
||||
serialize: func(o options) []string { return boolSliceOrNil(o.noEtcHosts) },
|
||||
}
|
||||
|
||||
var localFrontendArg = arg{
|
||||
description: "Use local frontend directories.",
|
||||
longName: "local-frontend",
|
||||
shortName: "",
|
||||
updateWithValue: nil,
|
||||
updateNoValue: func(o options) (options, error) { o.localFrontend = true; return o, nil },
|
||||
effect: nil,
|
||||
serialize: func(o options) []string { return boolSliceOrNil(o.localFrontend) },
|
||||
}
|
||||
|
||||
func init() {
|
||||
args = []arg{
|
||||
configArg,
|
||||
workDirArg,
|
||||
hostArg,
|
||||
portArg,
|
||||
serviceArg,
|
||||
logfileArg,
|
||||
pidfileArg,
|
||||
checkConfigArg,
|
||||
noCheckUpdateArg,
|
||||
disableMemoryOptimizationArg,
|
||||
noEtcHostsArg,
|
||||
localFrontendArg,
|
||||
verboseArg,
|
||||
glinetArg,
|
||||
versionArg,
|
||||
helpArg,
|
||||
}
|
||||
}
|
||||
|
||||
func getUsageLines(exec string, args []arg) []string {
|
||||
usage := []string{
|
||||
"Usage:",
|
||||
"",
|
||||
fmt.Sprintf("%s [options]", exec),
|
||||
"",
|
||||
"Options:",
|
||||
}
|
||||
for _, arg := range args {
|
||||
val := ""
|
||||
if opt.updateWithValue != nil {
|
||||
if arg.updateWithValue != nil {
|
||||
val = " VALUE"
|
||||
}
|
||||
|
||||
longDesc := opt.longName + val
|
||||
if opt.shortName != "" {
|
||||
_, err = fmt.Fprintf(b, " -%s, --%-28s %s\n", opt.shortName, longDesc, opt.description)
|
||||
if arg.shortName != "" {
|
||||
usage = append(usage, fmt.Sprintf(" -%s, %-30s %s",
|
||||
arg.shortName,
|
||||
"--"+arg.longName+val,
|
||||
arg.description))
|
||||
} else {
|
||||
_, err = fmt.Fprintf(b, " --%-32s %s\n", longDesc, opt.description)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// The only error here can be from incorrect Fprintf usage, which is
|
||||
// a programmer error.
|
||||
panic(err)
|
||||
usage = append(usage, fmt.Sprintf(" %-34s %s",
|
||||
"--"+arg.longName+val,
|
||||
arg.description))
|
||||
}
|
||||
}
|
||||
|
||||
_, err = fmt.Print(b)
|
||||
if err != nil {
|
||||
// Exit immediately, since not being able to print out a help message
|
||||
// essentially means that the I/O is very broken at the moment.
|
||||
exitWithError()
|
||||
}
|
||||
return usage
|
||||
}
|
||||
|
||||
// parseCmdOpts parses the command-line arguments into options and effects.
|
||||
func parseCmdOpts(cmdName string, args []string) (o options, eff effect, err error) {
|
||||
// Don't use range since the loop changes the loop variable.
|
||||
argsLen := len(args)
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
isKnown := false
|
||||
for _, opt := range cmdLineOpts {
|
||||
isKnown = argMatches(opt, arg)
|
||||
if !isKnown {
|
||||
continue
|
||||
}
|
||||
|
||||
if opt.updateWithValue != nil {
|
||||
i++
|
||||
if i >= argsLen {
|
||||
return o, eff, fmt.Errorf("got %s without argument", arg)
|
||||
}
|
||||
|
||||
o, err = opt.updateWithValue(o, args[i])
|
||||
} else {
|
||||
o, eff, err = updateOptsNoValue(o, eff, opt, cmdName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return o, eff, fmt.Errorf("applying option %s: %w", arg, err)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if !isKnown {
|
||||
return o, eff, fmt.Errorf("unknown option %s", arg)
|
||||
}
|
||||
}
|
||||
|
||||
return o, eff, err
|
||||
}
|
||||
|
||||
// argMatches returns true if arg matches command-line option opt.
|
||||
func argMatches(opt cmdLineOpt, arg string) (ok bool) {
|
||||
if arg == "" || arg[0] != '-' {
|
||||
return false
|
||||
}
|
||||
|
||||
arg = arg[1:]
|
||||
if arg == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return (opt.shortName != "" && arg == opt.shortName) ||
|
||||
(arg[0] == '-' && arg[1:] == opt.longName)
|
||||
}
|
||||
|
||||
// updateOptsNoValue sets values or effects from opt into o or prev.
|
||||
func updateOptsNoValue(
|
||||
o options,
|
||||
prev effect,
|
||||
opt cmdLineOpt,
|
||||
cmdName string,
|
||||
) (updated options, chained effect, err error) {
|
||||
if opt.updateNoValue != nil {
|
||||
o, err = opt.updateNoValue(o)
|
||||
if err != nil {
|
||||
return o, prev, err
|
||||
}
|
||||
|
||||
return o, prev, nil
|
||||
}
|
||||
|
||||
next, err := opt.effect(o, cmdName)
|
||||
if err != nil {
|
||||
return o, prev, err
|
||||
}
|
||||
|
||||
chained = chainEffect(prev, next)
|
||||
|
||||
return o, chained, nil
|
||||
}
|
||||
|
||||
// chainEffect chans the next effect after the prev one. If prev is nil, eff
|
||||
// only calls next. If next is nil, eff is prev; if prev is nil, eff is next.
|
||||
func chainEffect(prev, next effect) (eff effect) {
|
||||
if prev == nil {
|
||||
return next
|
||||
} else if next == nil {
|
||||
return prev
|
||||
}
|
||||
|
||||
eff = func() (err error) {
|
||||
err = prev()
|
||||
func printHelp(exec string) error {
|
||||
for _, line := range getUsageLines(exec, args) {
|
||||
_, err := fmt.Println(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
return eff
|
||||
return nil
|
||||
}
|
||||
|
||||
// optsToArgs converts command line options into a list of arguments.
|
||||
func optsToArgs(o options) (args []string) {
|
||||
for _, opt := range cmdLineOpts {
|
||||
val, ok := opt.serialize(o)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
func argMatches(a arg, v string) bool {
|
||||
return v == "--"+a.longName || (a.shortName != "" && v == "-"+a.shortName)
|
||||
}
|
||||
|
||||
if opt.shortName != "" {
|
||||
args = append(args, "-"+opt.shortName)
|
||||
} else {
|
||||
args = append(args, "--"+opt.longName)
|
||||
func parse(exec string, ss []string) (o options, f effect, err error) {
|
||||
for i := 0; i < len(ss); i++ {
|
||||
v := ss[i]
|
||||
knownParam := false
|
||||
for _, arg := range args {
|
||||
if argMatches(arg, v) {
|
||||
if arg.updateWithValue != nil {
|
||||
if i+1 >= len(ss) {
|
||||
return o, f, fmt.Errorf("got %s without argument", v)
|
||||
}
|
||||
i++
|
||||
o, err = arg.updateWithValue(o, ss[i])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if arg.updateNoValue != nil {
|
||||
o, err = arg.updateNoValue(o)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if arg.effect != nil {
|
||||
var eff effect
|
||||
eff, err = arg.effect(o, exec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if eff != nil {
|
||||
prevf := f
|
||||
f = func() (ferr error) {
|
||||
if prevf != nil {
|
||||
ferr = prevf()
|
||||
}
|
||||
if ferr == nil {
|
||||
ferr = eff()
|
||||
}
|
||||
return ferr
|
||||
}
|
||||
}
|
||||
}
|
||||
knownParam = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if val != "" {
|
||||
args = append(args, val)
|
||||
if !knownParam {
|
||||
return o, f, fmt.Errorf("unknown option %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
return
|
||||
}
|
||||
|
||||
func shortestFlag(a arg) string {
|
||||
if a.shortName != "" {
|
||||
return "-" + a.shortName
|
||||
}
|
||||
return "--" + a.longName
|
||||
}
|
||||
|
||||
func serialize(o options) []string {
|
||||
ss := []string{}
|
||||
for _, arg := range args {
|
||||
s := arg.serialize(o)
|
||||
if s != nil {
|
||||
ss = append(ss, append([]string{shortestFlag(arg)}, s...)...)
|
||||
}
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func testParseOK(t *testing.T, ss ...string) options {
|
||||
t.Helper()
|
||||
|
||||
o, _, err := parseCmdOpts("", ss)
|
||||
o, _, err := parse("", ss)
|
||||
require.NoError(t, err)
|
||||
|
||||
return o
|
||||
@@ -21,7 +21,7 @@ func testParseOK(t *testing.T, ss ...string) options {
|
||||
func testParseErr(t *testing.T, descr string, ss ...string) {
|
||||
t.Helper()
|
||||
|
||||
_, _, err := parseCmdOpts("", ss)
|
||||
_, _, err := parse("", ss)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -38,11 +38,11 @@ func TestParseVerbose(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseConfigFilename(t *testing.T) {
|
||||
assert.Equal(t, "", testParseOK(t).confFilename, "empty is no config filename")
|
||||
assert.Equal(t, "path", testParseOK(t, "-c", "path").confFilename, "-c is config filename")
|
||||
assert.Equal(t, "", testParseOK(t).configFilename, "empty is no config filename")
|
||||
assert.Equal(t, "path", testParseOK(t, "-c", "path").configFilename, "-c is config filename")
|
||||
testParseParamMissing(t, "-c")
|
||||
|
||||
assert.Equal(t, "path", testParseOK(t, "--config", "path").confFilename, "--config is config filename")
|
||||
assert.Equal(t, "path", testParseOK(t, "--config", "path").configFilename, "--config is config filename")
|
||||
testParseParamMissing(t, "--config")
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestParseDisableUpdate(t *testing.T) {
|
||||
|
||||
// TODO(e.burkov): Remove after v0.108.0.
|
||||
func TestParseDisableMemoryOptimization(t *testing.T) {
|
||||
o, eff, err := parseCmdOpts("", []string{"--no-mem-optimization"})
|
||||
o, eff, err := parse("", []string{"--no-mem-optimization"})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Nil(t, eff)
|
||||
@@ -130,73 +130,73 @@ func TestParseUnknown(t *testing.T) {
|
||||
testParseErr(t, "unknown dash", "-")
|
||||
}
|
||||
|
||||
func TestOptsToArgs(t *testing.T) {
|
||||
func TestSerialize(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
opts options
|
||||
ss []string
|
||||
}{{
|
||||
name: "empty",
|
||||
args: []string{},
|
||||
opts: options{},
|
||||
ss: []string{},
|
||||
}, {
|
||||
name: "config_filename",
|
||||
args: []string{"-c", "path"},
|
||||
opts: options{confFilename: "path"},
|
||||
opts: options{configFilename: "path"},
|
||||
ss: []string{"-c", "path"},
|
||||
}, {
|
||||
name: "work_dir",
|
||||
args: []string{"-w", "path"},
|
||||
opts: options{workDir: "path"},
|
||||
ss: []string{"-w", "path"},
|
||||
}, {
|
||||
name: "bind_host",
|
||||
args: []string{"-h", "1.2.3.4"},
|
||||
opts: options{bindHost: net.IP{1, 2, 3, 4}},
|
||||
ss: []string{"-h", "1.2.3.4"},
|
||||
}, {
|
||||
name: "bind_port",
|
||||
args: []string{"-p", "666"},
|
||||
opts: options{bindPort: 666},
|
||||
ss: []string{"-p", "666"},
|
||||
}, {
|
||||
name: "log_file",
|
||||
args: []string{"-l", "path"},
|
||||
opts: options{logFile: "path"},
|
||||
ss: []string{"-l", "path"},
|
||||
}, {
|
||||
name: "pid_file",
|
||||
args: []string{"--pidfile", "path"},
|
||||
opts: options{pidFile: "path"},
|
||||
ss: []string{"--pidfile", "path"},
|
||||
}, {
|
||||
name: "disable_update",
|
||||
args: []string{"--no-check-update"},
|
||||
opts: options{disableUpdate: true},
|
||||
ss: []string{"--no-check-update"},
|
||||
}, {
|
||||
name: "control_action",
|
||||
args: []string{"-s", "run"},
|
||||
opts: options{serviceControlAction: "run"},
|
||||
ss: []string{"-s", "run"},
|
||||
}, {
|
||||
name: "glinet_mode",
|
||||
args: []string{"--glinet"},
|
||||
opts: options{glinetMode: true},
|
||||
ss: []string{"--glinet"},
|
||||
}, {
|
||||
name: "multiple",
|
||||
args: []string{
|
||||
opts: options{
|
||||
serviceControlAction: "run",
|
||||
configFilename: "config",
|
||||
workDir: "work",
|
||||
pidFile: "pid",
|
||||
disableUpdate: true,
|
||||
},
|
||||
ss: []string{
|
||||
"-c", "config",
|
||||
"-w", "work",
|
||||
"-s", "run",
|
||||
"--pidfile", "pid",
|
||||
"--no-check-update",
|
||||
},
|
||||
opts: options{
|
||||
serviceControlAction: "run",
|
||||
confFilename: "config",
|
||||
workDir: "work",
|
||||
pidFile: "pid",
|
||||
disableUpdate: true,
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := optsToArgs(tc.opts)
|
||||
assert.ElementsMatch(t, tc.args, result)
|
||||
result := serialize(tc.opts)
|
||||
assert.ElementsMatch(t, tc.ss, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
|
||||
DisplayName: serviceDisplayName,
|
||||
Description: serviceDescription,
|
||||
WorkingDirectory: pwd,
|
||||
Arguments: optsToArgs(runOpts),
|
||||
Arguments: serialize(runOpts),
|
||||
}
|
||||
configureService(svcConfig)
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if !webCheckPortAvailable(setts.PortHTTPS) {
|
||||
if !WebCheckPortAvailable(setts.PortHTTPS) {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
@@ -356,7 +356,7 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Investigate and perhaps check other ports.
|
||||
if !webCheckPortAvailable(data.PortHTTPS) {
|
||||
if !WebCheckPortAvailable(data.PortHTTPS) {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/NYTimes/gziphandler"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/http3"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
@@ -55,56 +53,40 @@ type webConfig struct {
|
||||
WriteTimeout time.Duration
|
||||
|
||||
firstRun bool
|
||||
|
||||
serveHTTP3 bool
|
||||
}
|
||||
|
||||
// httpsServer contains the data for the HTTPS server.
|
||||
type httpsServer struct {
|
||||
// server is the pre-HTTP/3 HTTPS server.
|
||||
server *http.Server
|
||||
// server3 is the HTTP/3 HTTPS server. If it is not nil,
|
||||
// [httpsServer.server] must also be non-nil.
|
||||
server3 *http3.Server
|
||||
|
||||
// TODO(a.garipov): Why is there a *sync.Cond here? Remove.
|
||||
cond *sync.Cond
|
||||
condLock sync.Mutex
|
||||
cert tls.Certificate
|
||||
inShutdown bool
|
||||
enabled bool
|
||||
// HTTPSServer - HTTPS Server
|
||||
type HTTPSServer struct {
|
||||
server *http.Server
|
||||
cond *sync.Cond
|
||||
condLock sync.Mutex
|
||||
shutdown bool // if TRUE, don't restart the server
|
||||
enabled bool
|
||||
cert tls.Certificate
|
||||
}
|
||||
|
||||
// Web is the web UI and API server.
|
||||
// Web - module object
|
||||
type Web struct {
|
||||
conf *webConfig
|
||||
|
||||
// TODO(a.garipov): Refactor all these servers.
|
||||
httpServer *http.Server
|
||||
|
||||
// httpServerBeta is a server for new client.
|
||||
httpServerBeta *http.Server
|
||||
conf *webConfig
|
||||
forceHTTPS bool
|
||||
httpServer *http.Server // HTTP module
|
||||
httpsServer HTTPSServer // HTTPS module
|
||||
|
||||
// handlerBeta is the handler for new client.
|
||||
handlerBeta http.Handler
|
||||
|
||||
// installerBeta is the pre-install handler for new client.
|
||||
installerBeta http.Handler
|
||||
|
||||
// httpsServer is the server that handles HTTPS traffic. If it is not nil,
|
||||
// [Web.http3Server] must also not be nil.
|
||||
httpsServer httpsServer
|
||||
|
||||
forceHTTPS bool
|
||||
// httpServerBeta is a server for new client.
|
||||
httpServerBeta *http.Server
|
||||
}
|
||||
|
||||
// newWeb creates a new instance of the web UI and API server.
|
||||
func newWeb(conf *webConfig) (w *Web) {
|
||||
log.Info("web: initializing")
|
||||
// CreateWeb - create module
|
||||
func CreateWeb(conf *webConfig) *Web {
|
||||
log.Info("Initialize web module")
|
||||
|
||||
w = &Web{
|
||||
conf: conf,
|
||||
}
|
||||
w := Web{}
|
||||
w.conf = conf
|
||||
|
||||
clientFS := http.FileServer(http.FS(conf.clientFS))
|
||||
betaClientFS := http.FileServer(http.FS(conf.clientBetaFS))
|
||||
@@ -126,15 +108,12 @@ func newWeb(conf *webConfig) (w *Web) {
|
||||
}
|
||||
|
||||
w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock)
|
||||
|
||||
return w
|
||||
return &w
|
||||
}
|
||||
|
||||
// webCheckPortAvailable checks if port, which is considered an HTTPS port, is
|
||||
// available, unless the HTTPS server isn't active.
|
||||
//
|
||||
// TODO(a.garipov): Adapt for HTTP/3.
|
||||
func webCheckPortAvailable(port int) (ok bool) {
|
||||
// WebCheckPortAvailable - check if port is available
|
||||
// BUT: if we are already using this port, no need
|
||||
func WebCheckPortAvailable(port int) bool {
|
||||
return Context.web.httpsServer.server != nil ||
|
||||
aghnet.CheckPort("tcp", config.BindHost, port) == nil
|
||||
}
|
||||
@@ -142,7 +121,7 @@ func webCheckPortAvailable(port int) (ok bool) {
|
||||
// TLSConfigChanged updates the TLS configuration and restarts the HTTPS server
|
||||
// if necessary.
|
||||
func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf tlsConfigSettings) {
|
||||
log.Debug("web: applying new tls configuration")
|
||||
log.Debug("Web: applying new TLS configuration")
|
||||
web.conf.PortHTTPS = tlsConf.PortHTTPS
|
||||
web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0)
|
||||
|
||||
@@ -164,8 +143,6 @@ func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf tlsConfigSettings)
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, shutdownTimeout)
|
||||
shutdownSrv(ctx, web.httpsServer.server)
|
||||
shutdownSrv3(web.httpsServer.server3)
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
@@ -183,7 +160,7 @@ func (web *Web) Start() {
|
||||
go web.tlsServerLoop()
|
||||
|
||||
// this loop is used as an ability to change listening host and/or port
|
||||
for !web.httpsServer.inShutdown {
|
||||
for !web.httpsServer.shutdown {
|
||||
printHTTPAddresses(aghhttp.SchemeHTTP)
|
||||
errs := make(chan error, 2)
|
||||
|
||||
@@ -254,7 +231,7 @@ func (web *Web) Close(ctx context.Context) {
|
||||
log.Info("stopping http server...")
|
||||
|
||||
web.httpsServer.cond.L.Lock()
|
||||
web.httpsServer.inShutdown = true
|
||||
web.httpsServer.shutdown = true
|
||||
web.httpsServer.cond.L.Unlock()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
@@ -262,7 +239,6 @@ func (web *Web) Close(ctx context.Context) {
|
||||
defer cancel()
|
||||
|
||||
shutdownSrv(ctx, web.httpsServer.server)
|
||||
shutdownSrv3(web.httpsServer.server3)
|
||||
shutdownSrv(ctx, web.httpServer)
|
||||
shutdownSrv(ctx, web.httpServerBeta)
|
||||
|
||||
@@ -272,7 +248,7 @@ func (web *Web) Close(ctx context.Context) {
|
||||
func (web *Web) tlsServerLoop() {
|
||||
for {
|
||||
web.httpsServer.cond.L.Lock()
|
||||
if web.httpsServer.inShutdown {
|
||||
if web.httpsServer.shutdown {
|
||||
web.httpsServer.cond.L.Unlock()
|
||||
break
|
||||
}
|
||||
@@ -280,7 +256,7 @@ func (web *Web) tlsServerLoop() {
|
||||
// this mechanism doesn't let us through until all conditions are met
|
||||
for !web.httpsServer.enabled { // sleep until necessary data is supplied
|
||||
web.httpsServer.cond.Wait()
|
||||
if web.httpsServer.inShutdown {
|
||||
if web.httpsServer.shutdown {
|
||||
web.httpsServer.cond.L.Unlock()
|
||||
return
|
||||
}
|
||||
@@ -288,10 +264,11 @@ func (web *Web) tlsServerLoop() {
|
||||
|
||||
web.httpsServer.cond.L.Unlock()
|
||||
|
||||
addr := netutil.JoinHostPort(web.conf.BindHost.String(), web.conf.PortHTTPS)
|
||||
// prepare HTTPS server
|
||||
address := netutil.JoinHostPort(web.conf.BindHost.String(), web.conf.PortHTTPS)
|
||||
web.httpsServer.server = &http.Server{
|
||||
ErrorLog: log.StdLog("web: https", log.DEBUG),
|
||||
Addr: addr,
|
||||
Addr: address,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{web.httpsServer.cert},
|
||||
RootCAs: Context.tlsRoots,
|
||||
@@ -305,40 +282,10 @@ func (web *Web) tlsServerLoop() {
|
||||
}
|
||||
|
||||
printHTTPAddresses(aghhttp.SchemeHTTPS)
|
||||
|
||||
if web.conf.serveHTTP3 {
|
||||
go web.mustStartHTTP3(addr)
|
||||
}
|
||||
|
||||
log.Debug("web: starting https server")
|
||||
err := web.httpsServer.server.ListenAndServeTLS("", "")
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
if err != http.ErrServerClosed {
|
||||
cleanupAlways()
|
||||
log.Fatalf("web: https: %s", err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (web *Web) mustStartHTTP3(address string) {
|
||||
defer log.OnPanic("web: http3")
|
||||
|
||||
web.httpsServer.server3 = &http3.Server{
|
||||
// TODO(a.garipov): See if there is a way to use the error log as
|
||||
// well as timeouts here.
|
||||
Addr: address,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{web.httpsServer.cert},
|
||||
RootCAs: Context.tlsRoots,
|
||||
CipherSuites: aghtls.SaferCipherSuites(),
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
||||
}
|
||||
|
||||
log.Debug("web: starting http/3 server")
|
||||
err := web.httpsServer.server3.ListenAndServe()
|
||||
if !errors.Is(err, quic.ErrServerClosed) {
|
||||
cleanupAlways()
|
||||
log.Fatalf("web: http3: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// Package agh contains common entities and interfaces of AdGuard Home.
|
||||
//
|
||||
// TODO(a.garipov): Move to the upper-level internal/.
|
||||
package agh
|
||||
|
||||
import "context"
|
||||
|
||||
// Service is the interface for API servers.
|
||||
//
|
||||
// TODO(a.garipov): Consider adding a context to Start.
|
||||
//
|
||||
// TODO(a.garipov): Consider adding a Wait method or making an extension
|
||||
// interface for that.
|
||||
type Service interface {
|
||||
// Start starts the service. It does not block.
|
||||
Start() (err error)
|
||||
|
||||
// Shutdown gracefully stops the service. ctx is used to determine
|
||||
// a timeout before trying to stop the service less gracefully.
|
||||
Shutdown(ctx context.Context) (err error)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Service = EmptyService{}
|
||||
|
||||
// EmptyService is a Service that does nothing.
|
||||
type EmptyService struct{}
|
||||
|
||||
// Start implements the Service interface for EmptyService.
|
||||
func (EmptyService) Start() (err error) { return nil }
|
||||
|
||||
// Shutdown implements the Service interface for EmptyService.
|
||||
func (EmptyService) Shutdown(_ context.Context) (err error) { return nil }
|
||||
@@ -1,70 +0,0 @@
|
||||
// Package cmd is the AdGuard Home entry point. It contains the on-disk
|
||||
// configuration file utilities, signal processing logic, and so on.
|
||||
//
|
||||
// TODO(a.garipov): Move to the upper-level internal/.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// Main is the entry point of application.
|
||||
func Main(clientBuildFS fs.FS) {
|
||||
// Initial Configuration
|
||||
|
||||
start := time.Now()
|
||||
rand.Seed(start.UnixNano())
|
||||
|
||||
// TODO(a.garipov): Set up logging.
|
||||
|
||||
// Web Service
|
||||
|
||||
// TODO(a.garipov): Use in the Web service.
|
||||
_ = clientBuildFS
|
||||
|
||||
// TODO(a.garipov): Make configurable.
|
||||
web := websvc.New(&websvc.Config{
|
||||
// TODO(a.garipov): Use an actual implementation.
|
||||
ConfigManager: nil,
|
||||
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:3001")},
|
||||
Start: start,
|
||||
Timeout: 60 * time.Second,
|
||||
ForceHTTPS: false,
|
||||
})
|
||||
|
||||
err := web.Start()
|
||||
fatalOnError(err)
|
||||
|
||||
sigHdlr := newSignalHandler(
|
||||
web,
|
||||
)
|
||||
|
||||
go sigHdlr.handle()
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
// defaultTimeout is the timeout used for some operations where another timeout
|
||||
// hasn't been defined yet.
|
||||
const defaultTimeout = 15 * time.Second
|
||||
|
||||
// ctxWithDefaultTimeout is a helper function that returns a context with
|
||||
// timeout set to defaultTimeout.
|
||||
func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), defaultTimeout)
|
||||
}
|
||||
|
||||
// fatalOnError is a helper that exits the program with an error code if err is
|
||||
// not nil. It must only be used within Main.
|
||||
func fatalOnError(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// signalHandler processes incoming signals and shuts services down.
|
||||
type signalHandler struct {
|
||||
signal chan os.Signal
|
||||
|
||||
// services are the services that are shut down before application
|
||||
// exiting.
|
||||
services []agh.Service
|
||||
}
|
||||
|
||||
// handle processes OS signals.
|
||||
func (h *signalHandler) handle() {
|
||||
defer log.OnPanic("signalHandler.handle")
|
||||
|
||||
for sig := range h.signal {
|
||||
log.Info("sighdlr: received signal %q", sig)
|
||||
|
||||
if aghos.IsShutdownSignal(sig) {
|
||||
h.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit status constants.
|
||||
const (
|
||||
statusSuccess = 0
|
||||
statusError = 1
|
||||
)
|
||||
|
||||
// shutdown gracefully shuts down all services.
|
||||
func (h *signalHandler) shutdown() {
|
||||
ctx, cancel := ctxWithDefaultTimeout()
|
||||
defer cancel()
|
||||
|
||||
status := statusSuccess
|
||||
|
||||
log.Info("sighdlr: shutting down services")
|
||||
for i, service := range h.services {
|
||||
err := service.Shutdown(ctx)
|
||||
if err != nil {
|
||||
log.Error("sighdlr: shutting down service at index %d: %s", i, err)
|
||||
status = statusError
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("sighdlr: shutting down adguard home")
|
||||
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
// newSignalHandler returns a new signalHandler that shuts down svcs.
|
||||
func newSignalHandler(svcs ...agh.Service) (h *signalHandler) {
|
||||
h = &signalHandler{
|
||||
signal: make(chan os.Signal, 1),
|
||||
services: svcs,
|
||||
}
|
||||
|
||||
aghos.NotifyShutdownSignal(h.signal)
|
||||
|
||||
return h
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
// Package dnssvc contains the AdGuard Home DNS service.
|
||||
//
|
||||
// TODO(a.garipov): Define, if all methods of a *Service should work with a nil
|
||||
// receiver.
|
||||
package dnssvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
// TODO(a.garipov): Add a “dnsproxy proxy” package to shield us from changes
|
||||
// and replacement of module dnsproxy.
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
)
|
||||
|
||||
// Config is the AdGuard Home DNS service configuration structure.
|
||||
//
|
||||
// TODO(a.garipov): Add timeout for incoming requests.
|
||||
type Config struct {
|
||||
// Addresses are the addresses on which to serve plain DNS queries.
|
||||
Addresses []netip.AddrPort
|
||||
|
||||
// Upstreams are the DNS upstreams to use. If not set, upstreams are
|
||||
// created using data from BootstrapServers, UpstreamServers, and
|
||||
// UpstreamTimeout.
|
||||
//
|
||||
// TODO(a.garipov): Think of a better scheme. Those other three parameters
|
||||
// are here only to make Config work properly.
|
||||
Upstreams []upstream.Upstream
|
||||
|
||||
// BootstrapServers are the addresses for bootstrapping the upstream DNS
|
||||
// server addresses.
|
||||
BootstrapServers []string
|
||||
|
||||
// UpstreamServers are the upstream DNS server addresses to use.
|
||||
UpstreamServers []string
|
||||
|
||||
// UpstreamTimeout is the timeout for upstream requests.
|
||||
UpstreamTimeout time.Duration
|
||||
}
|
||||
|
||||
// Service is the AdGuard Home DNS service. A nil *Service is a valid
|
||||
// [agh.Service] that does nothing.
|
||||
type Service struct {
|
||||
// running is an atomic boolean value. Keep it the first value in the
|
||||
// struct to ensure atomic alignment. 0 means that the service is not
|
||||
// running, 1 means that it is running.
|
||||
//
|
||||
// TODO(a.garipov): Use [atomic.Bool] in Go 1.19 or get rid of it
|
||||
// completely.
|
||||
running uint64
|
||||
|
||||
proxy *proxy.Proxy
|
||||
bootstraps []string
|
||||
upstreams []string
|
||||
upsTimeout time.Duration
|
||||
}
|
||||
|
||||
// New returns a new properly initialized *Service. If c is nil, svc is a nil
|
||||
// *Service that does nothing. The fields of c must not be modified after
|
||||
// calling New.
|
||||
func New(c *Config) (svc *Service, err error) {
|
||||
if c == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
svc = &Service{
|
||||
bootstraps: c.BootstrapServers,
|
||||
upstreams: c.UpstreamServers,
|
||||
upsTimeout: c.UpstreamTimeout,
|
||||
}
|
||||
|
||||
var upstreams []upstream.Upstream
|
||||
if len(c.Upstreams) > 0 {
|
||||
upstreams = c.Upstreams
|
||||
} else {
|
||||
upstreams, err = addressesToUpstreams(
|
||||
c.UpstreamServers,
|
||||
c.BootstrapServers,
|
||||
c.UpstreamTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting upstreams: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
svc.proxy = &proxy.Proxy{
|
||||
Config: proxy.Config{
|
||||
UDPListenAddr: udpAddrs(c.Addresses),
|
||||
TCPListenAddr: tcpAddrs(c.Addresses),
|
||||
UpstreamConfig: &proxy.UpstreamConfig{
|
||||
Upstreams: upstreams,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = svc.proxy.Init()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy: %w", err)
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
// addressesToUpstreams is a wrapper around [upstream.AddressToUpstream]. It
|
||||
// accepts a slice of addresses and other upstream parameters, and returns a
|
||||
// slice of upstreams.
|
||||
func addressesToUpstreams(
|
||||
upsStrs []string,
|
||||
bootstraps []string,
|
||||
timeout time.Duration,
|
||||
) (upstreams []upstream.Upstream, err error) {
|
||||
upstreams = make([]upstream.Upstream, len(upsStrs))
|
||||
for i, upsStr := range upsStrs {
|
||||
upstreams[i], err = upstream.AddressToUpstream(upsStr, &upstream.Options{
|
||||
Bootstrap: bootstraps,
|
||||
Timeout: timeout,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("upstream at index %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return upstreams, nil
|
||||
}
|
||||
|
||||
// tcpAddrs converts []netip.AddrPort into []*net.TCPAddr.
|
||||
func tcpAddrs(addrPorts []netip.AddrPort) (tcpAddrs []*net.TCPAddr) {
|
||||
if addrPorts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tcpAddrs = make([]*net.TCPAddr, len(addrPorts))
|
||||
for i, a := range addrPorts {
|
||||
tcpAddrs[i] = net.TCPAddrFromAddrPort(a)
|
||||
}
|
||||
|
||||
return tcpAddrs
|
||||
}
|
||||
|
||||
// udpAddrs converts []netip.AddrPort into []*net.UDPAddr.
|
||||
func udpAddrs(addrPorts []netip.AddrPort) (udpAddrs []*net.UDPAddr) {
|
||||
if addrPorts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
udpAddrs = make([]*net.UDPAddr, len(addrPorts))
|
||||
for i, a := range addrPorts {
|
||||
udpAddrs[i] = net.UDPAddrFromAddrPort(a)
|
||||
}
|
||||
|
||||
return udpAddrs
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ agh.Service = (*Service)(nil)
|
||||
|
||||
// Start implements the [agh.Service] interface for *Service. svc may be nil.
|
||||
// After Start exits, all DNS servers have tried to start, but there is no
|
||||
// guarantee that they did. Errors from the servers are written to the log.
|
||||
func (svc *Service) Start() (err error) {
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// TODO(a.garipov): [proxy.Proxy.Start] doesn't actually have any way to
|
||||
// tell when all servers are actually up, so at best this is merely an
|
||||
// assumption.
|
||||
if err != nil {
|
||||
atomic.StoreUint64(&svc.running, 0)
|
||||
} else {
|
||||
atomic.StoreUint64(&svc.running, 1)
|
||||
}
|
||||
}()
|
||||
|
||||
return svc.proxy.Start()
|
||||
}
|
||||
|
||||
// Shutdown implements the [agh.Service] interface for *Service. svc may be
|
||||
// nil.
|
||||
func (svc *Service) Shutdown(ctx context.Context) (err error) {
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return svc.proxy.Stop()
|
||||
}
|
||||
|
||||
// Config returns the current configuration of the web service. Config must not
|
||||
// be called simultaneously with Start. If svc was initialized with ":0"
|
||||
// addresses, addrs will not return the actual bound ports until Start is
|
||||
// finished.
|
||||
func (svc *Service) Config() (c *Config) {
|
||||
// TODO(a.garipov): Do we need to get the TCP addresses separately?
|
||||
|
||||
var addrs []netip.AddrPort
|
||||
if atomic.LoadUint64(&svc.running) == 1 {
|
||||
udpAddrs := svc.proxy.Addrs(proxy.ProtoUDP)
|
||||
addrs = make([]netip.AddrPort, len(udpAddrs))
|
||||
for i, a := range udpAddrs {
|
||||
addrs[i] = a.(*net.UDPAddr).AddrPort()
|
||||
}
|
||||
} else {
|
||||
conf := svc.proxy.Config
|
||||
udpAddrs := conf.UDPListenAddr
|
||||
addrs = make([]netip.AddrPort, len(udpAddrs))
|
||||
for i, a := range udpAddrs {
|
||||
addrs[i] = a.AddrPort()
|
||||
}
|
||||
}
|
||||
|
||||
c = &Config{
|
||||
Addresses: addrs,
|
||||
BootstrapServers: svc.bootstraps,
|
||||
UpstreamServers: svc.upstreams,
|
||||
UpstreamTimeout: svc.upsTimeout,
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package dnssvc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
aghtest.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// testTimeout is the common timeout for tests.
|
||||
const testTimeout = 100 * time.Millisecond
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
const (
|
||||
bootstrapAddr = "bootstrap.example"
|
||||
upstreamAddr = "upstream.example"
|
||||
)
|
||||
|
||||
ups := &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) {
|
||||
return upstreamAddr
|
||||
},
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = (&dns.Msg{}).SetReply(req)
|
||||
|
||||
return resp, nil
|
||||
},
|
||||
}
|
||||
|
||||
c := &dnssvc.Config{
|
||||
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")},
|
||||
Upstreams: []upstream.Upstream{ups},
|
||||
BootstrapServers: []string{bootstrapAddr},
|
||||
UpstreamServers: []string{upstreamAddr},
|
||||
UpstreamTimeout: testTimeout,
|
||||
}
|
||||
|
||||
svc, err := dnssvc.New(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = svc.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
gotConf := svc.Config()
|
||||
require.NotNil(t, gotConf)
|
||||
require.Len(t, gotConf.Addresses, 1)
|
||||
|
||||
addr := gotConf.Addresses[0]
|
||||
|
||||
t.Run("dns", func(t *testing.T) {
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: "example.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
defer cancel()
|
||||
|
||||
cli := &dns.Client{}
|
||||
resp, _, excErr := cli.ExchangeContext(ctx, req, addr.String())
|
||||
require.NoError(t, excErr)
|
||||
|
||||
assert.NotNil(t, resp)
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
defer cancel()
|
||||
|
||||
err = svc.Shutdown(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
)
|
||||
|
||||
// DNS Settings Handlers
|
||||
|
||||
// ReqPatchSettingsDNS describes the request to the PATCH /api/v1/settings/dns
|
||||
// HTTP API.
|
||||
type ReqPatchSettingsDNS struct {
|
||||
// TODO(a.garipov): Add more as we go.
|
||||
|
||||
Addresses []netip.AddrPort `json:"addresses"`
|
||||
BootstrapServers []string `json:"bootstrap_servers"`
|
||||
UpstreamServers []string `json:"upstream_servers"`
|
||||
UpstreamTimeout JSONDuration `json:"upstream_timeout"`
|
||||
}
|
||||
|
||||
// HTTPAPIDNSSettings are the DNS settings as used by the HTTP API. See the
|
||||
// DnsSettings object in the OpenAPI specification.
|
||||
type HTTPAPIDNSSettings struct {
|
||||
// TODO(a.garipov): Add more as we go.
|
||||
|
||||
Addresses []netip.AddrPort `json:"addresses"`
|
||||
BootstrapServers []string `json:"bootstrap_servers"`
|
||||
UpstreamServers []string `json:"upstream_servers"`
|
||||
UpstreamTimeout JSONDuration `json:"upstream_timeout"`
|
||||
}
|
||||
|
||||
// handlePatchSettingsDNS is the handler for the PATCH /api/v1/settings/dns HTTP
|
||||
// API.
|
||||
func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Request) {
|
||||
req := &ReqPatchSettingsDNS{
|
||||
Addresses: []netip.AddrPort{},
|
||||
BootstrapServers: []string{},
|
||||
UpstreamServers: []string{},
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Validate nulls and proper JSON patch.
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
writeJSONErrorResponse(w, r, fmt.Errorf("decoding: %w", err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
newConf := &dnssvc.Config{
|
||||
Addresses: req.Addresses,
|
||||
BootstrapServers: req.BootstrapServers,
|
||||
UpstreamServers: req.UpstreamServers,
|
||||
UpstreamTimeout: time.Duration(req.UpstreamTimeout),
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
err = svc.confMgr.UpdateDNS(ctx, newConf)
|
||||
if err != nil {
|
||||
writeJSONErrorResponse(w, r, fmt.Errorf("updating: %w", err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
newSvc := svc.confMgr.DNS()
|
||||
err = newSvc.Start()
|
||||
if err != nil {
|
||||
writeJSONErrorResponse(w, r, fmt.Errorf("starting new service: %w", err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
writeJSONOKResponse(w, r, &HTTPAPIDNSSettings{
|
||||
Addresses: newConf.Addresses,
|
||||
BootstrapServers: newConf.BootstrapServers,
|
||||
UpstreamServers: newConf.UpstreamServers,
|
||||
UpstreamTimeout: JSONDuration(newConf.UpstreamTimeout),
|
||||
})
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package websvc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestService_HandlePatchSettingsDNS(t *testing.T) {
|
||||
wantDNS := &websvc.HTTPAPIDNSSettings{
|
||||
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.1.1:53")},
|
||||
BootstrapServers: []string{"1.0.0.1"},
|
||||
UpstreamServers: []string{"1.1.1.1"},
|
||||
UpstreamTimeout: websvc.JSONDuration(2 * time.Second),
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Use [atomic.Bool] in Go 1.19.
|
||||
var numStarted uint64
|
||||
confMgr := newConfigManager()
|
||||
confMgr.onDNS = func() (s websvc.ServiceWithConfig[*dnssvc.Config]) {
|
||||
return &aghtest.ServiceWithConfig[*dnssvc.Config]{
|
||||
OnStart: func() (err error) {
|
||||
atomic.AddUint64(&numStarted, 1)
|
||||
|
||||
return nil
|
||||
},
|
||||
OnShutdown: func(_ context.Context) (err error) { panic("not implemented") },
|
||||
OnConfig: func() (c *dnssvc.Config) { panic("not implemented") },
|
||||
}
|
||||
}
|
||||
confMgr.onUpdateDNS = func(ctx context.Context, c *dnssvc.Config) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, addr := newTestServer(t, confMgr)
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathV1SettingsDNS,
|
||||
}
|
||||
|
||||
req := jobj{
|
||||
"addresses": wantDNS.Addresses,
|
||||
"bootstrap_servers": wantDNS.BootstrapServers,
|
||||
"upstream_servers": wantDNS.UpstreamServers,
|
||||
"upstream_timeout": wantDNS.UpstreamTimeout,
|
||||
}
|
||||
|
||||
respBody := httpPatch(t, u, req, http.StatusOK)
|
||||
resp := &websvc.HTTPAPIDNSSettings{}
|
||||
err := json.Unmarshal(respBody, resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, uint64(1), numStarted)
|
||||
assert.Equal(t, wantDNS, resp)
|
||||
assert.Equal(t, wantDNS, resp)
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// HTTP Settings Handlers
|
||||
|
||||
// ReqPatchSettingsHTTP describes the request to the PATCH /api/v1/settings/http
|
||||
// HTTP API.
|
||||
type ReqPatchSettingsHTTP struct {
|
||||
// TODO(a.garipov): Add more as we go.
|
||||
//
|
||||
// TODO(a.garipov): Add wait time.
|
||||
|
||||
Addresses []netip.AddrPort `json:"addresses"`
|
||||
SecureAddresses []netip.AddrPort `json:"secure_addresses"`
|
||||
Timeout JSONDuration `json:"timeout"`
|
||||
}
|
||||
|
||||
// HTTPAPIHTTPSettings are the HTTP settings as used by the HTTP API. See the
|
||||
// HttpSettings object in the OpenAPI specification.
|
||||
type HTTPAPIHTTPSettings struct {
|
||||
// TODO(a.garipov): Add more as we go.
|
||||
|
||||
Addresses []netip.AddrPort `json:"addresses"`
|
||||
SecureAddresses []netip.AddrPort `json:"secure_addresses"`
|
||||
Timeout JSONDuration `json:"timeout"`
|
||||
ForceHTTPS bool `json:"force_https"`
|
||||
}
|
||||
|
||||
// handlePatchSettingsHTTP is the handler for the PATCH /api/v1/settings/http
|
||||
// HTTP API.
|
||||
func (svc *Service) handlePatchSettingsHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
req := &ReqPatchSettingsHTTP{}
|
||||
|
||||
// TODO(a.garipov): Validate nulls and proper JSON patch.
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
writeJSONErrorResponse(w, r, fmt.Errorf("decoding: %w", err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
newConf := &Config{
|
||||
ConfigManager: svc.confMgr,
|
||||
TLS: svc.tls,
|
||||
Addresses: req.Addresses,
|
||||
SecureAddresses: req.SecureAddresses,
|
||||
Timeout: time.Duration(req.Timeout),
|
||||
ForceHTTPS: svc.forceHTTPS,
|
||||
}
|
||||
|
||||
writeJSONOKResponse(w, r, &HTTPAPIHTTPSettings{
|
||||
Addresses: newConf.Addresses,
|
||||
SecureAddresses: newConf.SecureAddresses,
|
||||
Timeout: JSONDuration(newConf.Timeout),
|
||||
ForceHTTPS: newConf.ForceHTTPS,
|
||||
})
|
||||
|
||||
cancelUpd := func() {}
|
||||
updCtx := context.Background()
|
||||
|
||||
ctx := r.Context()
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
updCtx, cancelUpd = context.WithDeadline(updCtx, deadline)
|
||||
}
|
||||
|
||||
// Launch the new HTTP service in a separate goroutine to let this handler
|
||||
// finish and thus, this server to shutdown.
|
||||
go func() {
|
||||
defer cancelUpd()
|
||||
|
||||
updErr := svc.confMgr.UpdateWeb(updCtx, newConf)
|
||||
if updErr != nil {
|
||||
writeJSONErrorResponse(w, r, fmt.Errorf("updating: %w", updErr))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Consider better ways to do this.
|
||||
const maxUpdDur = 10 * time.Second
|
||||
updStart := time.Now()
|
||||
var newSvc ServiceWithConfig[*Config]
|
||||
for newSvc = svc.confMgr.Web(); newSvc == svc; {
|
||||
if time.Since(updStart) >= maxUpdDur {
|
||||
log.Error("websvc: failed to update svc after %s", maxUpdDur)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("websvc: waiting for new websvc to be configured")
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
updErr = newSvc.Start()
|
||||
if updErr != nil {
|
||||
log.Error("websvc: new svc failed to start with error: %s", updErr)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package websvc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestService_HandlePatchSettingsHTTP(t *testing.T) {
|
||||
wantWeb := &websvc.HTTPAPIHTTPSettings{
|
||||
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.1.1:80")},
|
||||
SecureAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.1.1:443")},
|
||||
Timeout: websvc.JSONDuration(10 * time.Second),
|
||||
ForceHTTPS: false,
|
||||
}
|
||||
|
||||
confMgr := newConfigManager()
|
||||
confMgr.onWeb = func() (s websvc.ServiceWithConfig[*websvc.Config]) {
|
||||
return websvc.New(&websvc.Config{
|
||||
TLS: &tls.Config{
|
||||
Certificates: []tls.Certificate{{}},
|
||||
},
|
||||
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:80")},
|
||||
SecureAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:443")},
|
||||
Timeout: 5 * time.Second,
|
||||
ForceHTTPS: true,
|
||||
})
|
||||
}
|
||||
confMgr.onUpdateWeb = func(ctx context.Context, c *websvc.Config) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, addr := newTestServer(t, confMgr)
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathV1SettingsHTTP,
|
||||
}
|
||||
|
||||
req := jobj{
|
||||
"addresses": wantWeb.Addresses,
|
||||
"secure_addresses": wantWeb.SecureAddresses,
|
||||
"timeout": wantWeb.Timeout,
|
||||
"force_https": wantWeb.ForceHTTPS,
|
||||
}
|
||||
|
||||
respBody := httpPatch(t, u, req, http.StatusOK)
|
||||
resp := &websvc.HTTPAPIHTTPSettings{}
|
||||
err := json.Unmarshal(respBody, resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, wantWeb, resp)
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// JSON Utilities
|
||||
|
||||
// nsecPerMsec is the number of nanoseconds in a millisecond.
|
||||
const nsecPerMsec = float64(time.Millisecond / time.Nanosecond)
|
||||
|
||||
// JSONDuration is a time.Duration that can be decoded from JSON and encoded
|
||||
// into JSON according to our API conventions.
|
||||
type JSONDuration time.Duration
|
||||
|
||||
// type check
|
||||
var _ json.Marshaler = JSONDuration(0)
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for JSONDuration. err is
|
||||
// always nil.
|
||||
func (d JSONDuration) MarshalJSON() (b []byte, err error) {
|
||||
msec := float64(time.Duration(d)) / nsecPerMsec
|
||||
b = strconv.AppendFloat(nil, msec, 'f', -1, 64)
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ json.Unmarshaler = (*JSONDuration)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Marshaler interface for *JSONDuration.
|
||||
func (d *JSONDuration) UnmarshalJSON(b []byte) (err error) {
|
||||
if d == nil {
|
||||
return fmt.Errorf("json duration is nil")
|
||||
}
|
||||
|
||||
msec, err := strconv.ParseFloat(string(b), 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing json time: %w", err)
|
||||
}
|
||||
|
||||
*d = JSONDuration(int64(msec * nsecPerMsec))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// JSONTime is a time.Time that can be decoded from JSON and encoded into JSON
|
||||
// according to our API conventions.
|
||||
type JSONTime time.Time
|
||||
|
||||
// type check
|
||||
var _ json.Marshaler = JSONTime{}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for JSONTime. err is
|
||||
// always nil.
|
||||
func (t JSONTime) MarshalJSON() (b []byte, err error) {
|
||||
msec := float64(time.Time(t).UnixNano()) / nsecPerMsec
|
||||
b = strconv.AppendFloat(nil, msec, 'f', -1, 64)
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ json.Unmarshaler = (*JSONTime)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Marshaler interface for *JSONTime.
|
||||
func (t *JSONTime) UnmarshalJSON(b []byte) (err error) {
|
||||
if t == nil {
|
||||
return fmt.Errorf("json time is nil")
|
||||
}
|
||||
|
||||
msec, err := strconv.ParseFloat(string(b), 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing json time: %w", err)
|
||||
}
|
||||
|
||||
*t = JSONTime(time.Unix(0, int64(msec*nsecPerMsec)).UTC())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeJSONOKResponse writes headers with the code 200 OK, encodes v into w,
|
||||
// and logs any errors it encounters. r is used to get additional information
|
||||
// from the request.
|
||||
func writeJSONOKResponse(w http.ResponseWriter, r *http.Request, v any) {
|
||||
writeJSONResponse(w, r, v, http.StatusOK)
|
||||
}
|
||||
|
||||
// writeJSONResponse writes headers with code, encodes v into w, and logs any
|
||||
// errors it encounters. r is used to get additional information from the
|
||||
// request.
|
||||
func writeJSONResponse(w http.ResponseWriter, r *http.Request, v any, code int) {
|
||||
// TODO(a.garipov): Put some of these to a middleware.
|
||||
h := w.Header()
|
||||
h.Set(aghhttp.HdrNameContentType, aghhttp.HdrValApplicationJSON)
|
||||
h.Set(aghhttp.HdrNameServer, aghhttp.UserAgent())
|
||||
|
||||
w.WriteHeader(code)
|
||||
|
||||
err := json.NewEncoder(w).Encode(v)
|
||||
if err != nil {
|
||||
log.Error("websvc: writing resp to %s %s: %s", r.Method, r.URL.Path, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorCode is the error code as used by the HTTP API. See the ErrorCode
|
||||
// definition in the OpenAPI specification.
|
||||
type ErrorCode string
|
||||
|
||||
// ErrorCode constants.
|
||||
//
|
||||
// TODO(a.garipov): Expand and document codes.
|
||||
const (
|
||||
// ErrorCodeTMP000 is the temporary error code used for all errors.
|
||||
ErrorCodeTMP000 = ""
|
||||
)
|
||||
|
||||
// HTTPAPIErrorResp is the error response as used by the HTTP API. See the
|
||||
// BadRequestResp, InternalServerErrorResp, and similar objects in the OpenAPI
|
||||
// specification.
|
||||
type HTTPAPIErrorResp struct {
|
||||
Code ErrorCode `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// writeJSONErrorResponse encodes err as a JSON error into w, and logs any
|
||||
// errors it encounters. r is used to get additional information from the
|
||||
// request.
|
||||
func writeJSONErrorResponse(w http.ResponseWriter, r *http.Request, err error) {
|
||||
log.Error("websvc: %s %s: %s", r.Method, r.URL.Path, err)
|
||||
|
||||
writeJSONResponse(w, r, &HTTPAPIErrorResp{
|
||||
Code: ErrorCodeTMP000,
|
||||
Msg: err.Error(),
|
||||
}, http.StatusUnprocessableEntity)
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package websvc_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testJSONTime is the JSON time for tests.
|
||||
var testJSONTime = websvc.JSONTime(time.Unix(1_234_567_890, 123_456_000).UTC())
|
||||
|
||||
// testJSONTimeStr is the string with the JSON encoding of testJSONTime.
|
||||
const testJSONTimeStr = "1234567890123.456"
|
||||
|
||||
func TestJSONTime_MarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErrMsg string
|
||||
in websvc.JSONTime
|
||||
want []byte
|
||||
}{{
|
||||
name: "unix_zero",
|
||||
wantErrMsg: "",
|
||||
in: websvc.JSONTime(time.Unix(0, 0)),
|
||||
want: []byte("0"),
|
||||
}, {
|
||||
name: "empty",
|
||||
wantErrMsg: "",
|
||||
in: websvc.JSONTime{},
|
||||
want: []byte("-6795364578871.345"),
|
||||
}, {
|
||||
name: "time",
|
||||
wantErrMsg: "",
|
||||
in: testJSONTime,
|
||||
want: []byte(testJSONTimeStr),
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.in.MarshalJSON()
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
in := &struct {
|
||||
A websvc.JSONTime
|
||||
}{
|
||||
A: testJSONTime,
|
||||
}
|
||||
|
||||
got, err := json.Marshal(in)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []byte(`{"A":`+testJSONTimeStr+`}`), got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestJSONTime_UnmarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErrMsg string
|
||||
want websvc.JSONTime
|
||||
data []byte
|
||||
}{{
|
||||
name: "time",
|
||||
wantErrMsg: "",
|
||||
want: testJSONTime,
|
||||
data: []byte(testJSONTimeStr),
|
||||
}, {
|
||||
name: "bad",
|
||||
wantErrMsg: `parsing json time: strconv.ParseFloat: parsing "{}": ` +
|
||||
`invalid syntax`,
|
||||
want: websvc.JSONTime{},
|
||||
data: []byte(`{}`),
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var got websvc.JSONTime
|
||||
err := got.UnmarshalJSON(tc.data)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
err := (*websvc.JSONTime)(nil).UnmarshalJSON([]byte("0"))
|
||||
require.Error(t, err)
|
||||
|
||||
msg := err.Error()
|
||||
assert.Equal(t, "json time is nil", msg)
|
||||
})
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
want := testJSONTime
|
||||
var got struct {
|
||||
A websvc.JSONTime
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(`{"A":`+testJSONTimeStr+`}`), &got)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, want, got.A)
|
||||
})
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package websvc
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Middlewares
|
||||
|
||||
// jsonMw sets the content type of the response to application/json.
|
||||
func jsonMw(h http.Handler) (wrapped http.HandlerFunc) {
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(f)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package websvc
|
||||
|
||||
// Path constants
|
||||
const (
|
||||
PathHealthCheck = "/health-check"
|
||||
|
||||
PathV1SettingsAll = "/api/v1/settings/all"
|
||||
PathV1SettingsDNS = "/api/v1/settings/dns"
|
||||
PathV1SettingsHTTP = "/api/v1/settings/http"
|
||||
PathV1SystemInfo = "/api/v1/system/info"
|
||||
)
|
||||
@@ -1,42 +0,0 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// All Settings Handlers
|
||||
|
||||
// RespGetV1SettingsAll describes the response of the GET /api/v1/settings/all
|
||||
// HTTP API.
|
||||
type RespGetV1SettingsAll struct {
|
||||
// TODO(a.garipov): Add more as we go.
|
||||
|
||||
DNS *HTTPAPIDNSSettings `json:"dns"`
|
||||
HTTP *HTTPAPIHTTPSettings `json:"http"`
|
||||
}
|
||||
|
||||
// handleGetSettingsAll is the handler for the GET /api/v1/settings/all HTTP
|
||||
// API.
|
||||
func (svc *Service) handleGetSettingsAll(w http.ResponseWriter, r *http.Request) {
|
||||
dnsSvc := svc.confMgr.DNS()
|
||||
dnsConf := dnsSvc.Config()
|
||||
|
||||
webSvc := svc.confMgr.Web()
|
||||
httpConf := webSvc.Config()
|
||||
|
||||
// TODO(a.garipov): Add all currently supported parameters.
|
||||
writeJSONOKResponse(w, r, &RespGetV1SettingsAll{
|
||||
DNS: &HTTPAPIDNSSettings{
|
||||
Addresses: dnsConf.Addresses,
|
||||
BootstrapServers: dnsConf.BootstrapServers,
|
||||
UpstreamServers: dnsConf.UpstreamServers,
|
||||
UpstreamTimeout: JSONDuration(dnsConf.UpstreamTimeout),
|
||||
},
|
||||
HTTP: &HTTPAPIHTTPSettings{
|
||||
Addresses: httpConf.Addresses,
|
||||
SecureAddresses: httpConf.SecureAddresses,
|
||||
Timeout: JSONDuration(httpConf.Timeout),
|
||||
ForceHTTPS: httpConf.ForceHTTPS,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package websvc_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestService_HandleGetSettingsAll(t *testing.T) {
|
||||
// TODO(a.garipov): Add all currently supported parameters.
|
||||
|
||||
wantDNS := &websvc.HTTPAPIDNSSettings{
|
||||
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")},
|
||||
BootstrapServers: []string{"94.140.14.140", "94.140.14.141"},
|
||||
UpstreamServers: []string{"94.140.14.14", "1.1.1.1"},
|
||||
UpstreamTimeout: websvc.JSONDuration(1 * time.Second),
|
||||
}
|
||||
|
||||
wantWeb := &websvc.HTTPAPIHTTPSettings{
|
||||
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:80")},
|
||||
SecureAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:443")},
|
||||
Timeout: websvc.JSONDuration(5 * time.Second),
|
||||
ForceHTTPS: true,
|
||||
}
|
||||
|
||||
confMgr := newConfigManager()
|
||||
confMgr.onDNS = func() (s websvc.ServiceWithConfig[*dnssvc.Config]) {
|
||||
c, err := dnssvc.New(&dnssvc.Config{
|
||||
Addresses: wantDNS.Addresses,
|
||||
UpstreamServers: wantDNS.UpstreamServers,
|
||||
BootstrapServers: wantDNS.BootstrapServers,
|
||||
UpstreamTimeout: time.Duration(wantDNS.UpstreamTimeout),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
confMgr.onWeb = func() (s websvc.ServiceWithConfig[*websvc.Config]) {
|
||||
return websvc.New(&websvc.Config{
|
||||
TLS: &tls.Config{
|
||||
Certificates: []tls.Certificate{{}},
|
||||
},
|
||||
Addresses: wantWeb.Addresses,
|
||||
SecureAddresses: wantWeb.SecureAddresses,
|
||||
Timeout: time.Duration(wantWeb.Timeout),
|
||||
ForceHTTPS: true,
|
||||
})
|
||||
}
|
||||
|
||||
_, addr := newTestServer(t, confMgr)
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathV1SettingsAll,
|
||||
}
|
||||
|
||||
body := httpGet(t, u, http.StatusOK)
|
||||
resp := &websvc.RespGetV1SettingsAll{}
|
||||
err := json.Unmarshal(body, resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, wantDNS, resp.DNS)
|
||||
assert.Equal(t, wantWeb, resp.HTTP)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
)
|
||||
|
||||
// System Handlers
|
||||
|
||||
// RespGetV1SystemInfo describes the response of the GET /api/v1/system/info
|
||||
// HTTP API.
|
||||
type RespGetV1SystemInfo struct {
|
||||
Arch string `json:"arch"`
|
||||
Channel string `json:"channel"`
|
||||
OS string `json:"os"`
|
||||
NewVersion string `json:"new_version,omitempty"`
|
||||
Start JSONTime `json:"start"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// handleGetV1SystemInfo is the handler for the GET /api/v1/system/info HTTP
|
||||
// API.
|
||||
func (svc *Service) handleGetV1SystemInfo(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSONOKResponse(w, r, &RespGetV1SystemInfo{
|
||||
Arch: runtime.GOARCH,
|
||||
Channel: version.Channel(),
|
||||
OS: runtime.GOOS,
|
||||
// TODO(a.garipov): Fill this when we have an updater.
|
||||
NewVersion: "",
|
||||
Start: JSONTime(svc.start),
|
||||
Version: version.Version(),
|
||||
})
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package websvc_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestService_handleGetV1SystemInfo(t *testing.T) {
|
||||
confMgr := newConfigManager()
|
||||
_, addr := newTestServer(t, confMgr)
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathV1SystemInfo,
|
||||
}
|
||||
|
||||
body := httpGet(t, u, http.StatusOK)
|
||||
resp := &websvc.RespGetV1SystemInfo{}
|
||||
err := json.Unmarshal(body, resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO(a.garipov): Consider making version.Channel and version.Version
|
||||
// testable and test these better.
|
||||
assert.NotEmpty(t, resp.Channel)
|
||||
|
||||
assert.Equal(t, resp.Arch, runtime.GOARCH)
|
||||
assert.Equal(t, resp.OS, runtime.GOOS)
|
||||
assert.Equal(t, testStart, time.Time(resp.Start))
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Wait Listener
|
||||
|
||||
// waitListener is a wrapper around a listener that also calls wg.Done() on the
|
||||
// first call to Accept. It is useful in situations where it is important to
|
||||
// catch the precise moment of the first call to Accept, for example when
|
||||
// starting an HTTP server.
|
||||
//
|
||||
// TODO(a.garipov): Move to aghnet?
|
||||
type waitListener struct {
|
||||
net.Listener
|
||||
|
||||
firstAcceptWG *sync.WaitGroup
|
||||
firstAcceptOnce sync.Once
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ net.Listener = (*waitListener)(nil)
|
||||
|
||||
// Accept implements the [net.Listener] interface for *waitListener.
|
||||
func (l *waitListener) Accept() (conn net.Conn, err error) {
|
||||
l.firstAcceptOnce.Do(l.firstAcceptWG.Done)
|
||||
|
||||
return l.Listener.Accept()
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghchan"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWaitListener_Accept(t *testing.T) {
|
||||
// TODO(a.garipov): use atomic.Bool in Go 1.19.
|
||||
var numAcceptCalls uint32
|
||||
var l net.Listener = &aghtest.Listener{
|
||||
OnAccept: func() (conn net.Conn, err error) {
|
||||
atomic.AddUint32(&numAcceptCalls, 1)
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
OnAddr: func() (addr net.Addr) { panic("not implemented") },
|
||||
OnClose: func() (err error) { panic("not implemented") },
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
done := make(chan struct{})
|
||||
go aghchan.MustReceive(done, testTimeout)
|
||||
|
||||
go func() {
|
||||
var wrapper net.Listener = &waitListener{
|
||||
Listener: l,
|
||||
firstAcceptWG: wg,
|
||||
}
|
||||
|
||||
_, _ = wrapper.Accept()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
close(done)
|
||||
|
||||
assert.Equal(t, uint32(1), atomic.LoadUint32(&numAcceptCalls))
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
// Package websvc contains the AdGuard Home HTTP API service.
|
||||
//
|
||||
// NOTE: Packages other than cmd must not import this package, as it imports
|
||||
// most other packages.
|
||||
//
|
||||
// TODO(a.garipov): Add tests.
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
httptreemux "github.com/dimfeld/httptreemux/v5"
|
||||
)
|
||||
|
||||
// ServiceWithConfig is an extension of the [agh.Service] interface for services
|
||||
// that can return their configuration.
|
||||
//
|
||||
// TODO(a.garipov): Consider removing this generic interface if we figure out
|
||||
// how to make it testable in a better way.
|
||||
type ServiceWithConfig[ConfigType any] interface {
|
||||
agh.Service
|
||||
|
||||
Config() (c ConfigType)
|
||||
}
|
||||
|
||||
// ConfigManager is the configuration manager interface.
|
||||
type ConfigManager interface {
|
||||
DNS() (svc ServiceWithConfig[*dnssvc.Config])
|
||||
Web() (svc ServiceWithConfig[*Config])
|
||||
|
||||
UpdateDNS(ctx context.Context, c *dnssvc.Config) (err error)
|
||||
UpdateWeb(ctx context.Context, c *Config) (err error)
|
||||
}
|
||||
|
||||
// Config is the AdGuard Home web service configuration structure.
|
||||
type Config struct {
|
||||
// ConfigManager is used to show information about services as well as
|
||||
// dynamically reconfigure them.
|
||||
ConfigManager ConfigManager
|
||||
|
||||
// TLS is the optional TLS configuration. If TLS is not nil,
|
||||
// SecureAddresses must not be empty.
|
||||
TLS *tls.Config
|
||||
|
||||
// Start is the time of start of AdGuard Home.
|
||||
Start time.Time
|
||||
|
||||
// Addresses are the addresses on which to serve the plain HTTP API.
|
||||
Addresses []netip.AddrPort
|
||||
|
||||
// SecureAddresses are the addresses on which to serve the HTTPS API. If
|
||||
// SecureAddresses is not empty, TLS must not be nil.
|
||||
SecureAddresses []netip.AddrPort
|
||||
|
||||
// Timeout is the timeout for all server operations.
|
||||
Timeout time.Duration
|
||||
|
||||
// ForceHTTPS tells if all requests to Addresses should be redirected to a
|
||||
// secure address instead.
|
||||
//
|
||||
// TODO(a.garipov): Use; define rules, which address to redirect to.
|
||||
ForceHTTPS bool
|
||||
}
|
||||
|
||||
// Service is the AdGuard Home web service. A nil *Service is a valid
|
||||
// [agh.Service] that does nothing.
|
||||
type Service struct {
|
||||
confMgr ConfigManager
|
||||
tls *tls.Config
|
||||
start time.Time
|
||||
servers []*http.Server
|
||||
timeout time.Duration
|
||||
forceHTTPS bool
|
||||
}
|
||||
|
||||
// New returns a new properly initialized *Service. If c is nil, svc is a nil
|
||||
// *Service that does nothing. The fields of c must not be modified after
|
||||
// calling New.
|
||||
func New(c *Config) (svc *Service) {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
svc = &Service{
|
||||
confMgr: c.ConfigManager,
|
||||
tls: c.TLS,
|
||||
start: c.Start,
|
||||
timeout: c.Timeout,
|
||||
forceHTTPS: c.ForceHTTPS,
|
||||
}
|
||||
|
||||
mux := newMux(svc)
|
||||
|
||||
for _, a := range c.Addresses {
|
||||
addr := a.String()
|
||||
errLog := log.StdLog("websvc: plain http: "+addr, log.ERROR)
|
||||
svc.servers = append(svc.servers, &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
ErrorLog: errLog,
|
||||
ReadTimeout: c.Timeout,
|
||||
WriteTimeout: c.Timeout,
|
||||
IdleTimeout: c.Timeout,
|
||||
ReadHeaderTimeout: c.Timeout,
|
||||
})
|
||||
}
|
||||
|
||||
for _, a := range c.SecureAddresses {
|
||||
addr := a.String()
|
||||
errLog := log.StdLog("websvc: https: "+addr, log.ERROR)
|
||||
svc.servers = append(svc.servers, &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
TLSConfig: c.TLS,
|
||||
ErrorLog: errLog,
|
||||
ReadTimeout: c.Timeout,
|
||||
WriteTimeout: c.Timeout,
|
||||
IdleTimeout: c.Timeout,
|
||||
ReadHeaderTimeout: c.Timeout,
|
||||
})
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
// newMux returns a new HTTP request multiplexor for the AdGuard Home web
|
||||
// service.
|
||||
func newMux(svc *Service) (mux *httptreemux.ContextMux) {
|
||||
mux = httptreemux.NewContextMux()
|
||||
|
||||
routes := []struct {
|
||||
handler http.HandlerFunc
|
||||
method string
|
||||
path string
|
||||
isJSON bool
|
||||
}{{
|
||||
handler: svc.handleGetHealthCheck,
|
||||
method: http.MethodGet,
|
||||
path: PathHealthCheck,
|
||||
isJSON: false,
|
||||
}, {
|
||||
handler: svc.handleGetSettingsAll,
|
||||
method: http.MethodGet,
|
||||
path: PathV1SettingsAll,
|
||||
isJSON: true,
|
||||
}, {
|
||||
handler: svc.handlePatchSettingsDNS,
|
||||
method: http.MethodPatch,
|
||||
path: PathV1SettingsDNS,
|
||||
isJSON: true,
|
||||
}, {
|
||||
handler: svc.handlePatchSettingsHTTP,
|
||||
method: http.MethodPatch,
|
||||
path: PathV1SettingsHTTP,
|
||||
isJSON: true,
|
||||
}, {
|
||||
handler: svc.handleGetV1SystemInfo,
|
||||
method: http.MethodGet,
|
||||
path: PathV1SystemInfo,
|
||||
isJSON: true,
|
||||
}}
|
||||
|
||||
for _, r := range routes {
|
||||
if r.isJSON {
|
||||
mux.Handle(r.method, r.path, jsonMw(r.handler))
|
||||
} else {
|
||||
mux.Handle(r.method, r.path, r.handler)
|
||||
}
|
||||
}
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
// addrs returns all addresses on which this server serves the HTTP API. addrs
|
||||
// must not be called simultaneously with Start. If svc was initialized with
|
||||
// ":0" addresses, addrs will not return the actual bound ports until Start is
|
||||
// finished.
|
||||
func (svc *Service) addrs() (addrs, secureAddrs []netip.AddrPort) {
|
||||
for _, srv := range svc.servers {
|
||||
addrPort, err := netip.ParseAddrPort(srv.Addr)
|
||||
if err != nil {
|
||||
// Technically shouldn't happen, since all servers must have a valid
|
||||
// address.
|
||||
panic(fmt.Errorf("websvc: server %q: bad address: %w", srv.Addr, err))
|
||||
}
|
||||
|
||||
// srv.Serve will set TLSConfig to an almost empty value, so, instead of
|
||||
// relying only on the nilness of TLSConfig, check the length of the
|
||||
// certificates field as well.
|
||||
if srv.TLSConfig == nil || len(srv.TLSConfig.Certificates) == 0 {
|
||||
addrs = append(addrs, addrPort)
|
||||
} else {
|
||||
secureAddrs = append(secureAddrs, addrPort)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return addrs, secureAddrs
|
||||
}
|
||||
|
||||
// handleGetHealthCheck is the handler for the GET /health-check HTTP API.
|
||||
func (svc *Service) handleGetHealthCheck(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = io.WriteString(w, "OK")
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ agh.Service = (*Service)(nil)
|
||||
|
||||
// Start implements the [agh.Service] interface for *Service. svc may be nil.
|
||||
// After Start exits, all HTTP servers have tried to start, possibly failing and
|
||||
// writing error messages to the log.
|
||||
func (svc *Service) Start() (err error) {
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(svc.servers))
|
||||
for _, srv := range svc.servers {
|
||||
go serve(srv, wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serve starts and runs srv and writes all errors into its log.
|
||||
func serve(srv *http.Server, wg *sync.WaitGroup) {
|
||||
addr := srv.Addr
|
||||
defer log.OnPanic(addr)
|
||||
|
||||
var proto string
|
||||
var l net.Listener
|
||||
var err error
|
||||
if srv.TLSConfig == nil {
|
||||
proto = "http"
|
||||
l, err = net.Listen("tcp", addr)
|
||||
} else {
|
||||
proto = "https"
|
||||
l, err = tls.Listen("tcp", addr, srv.TLSConfig)
|
||||
}
|
||||
if err != nil {
|
||||
srv.ErrorLog.Printf("starting srv %s: binding: %s", addr, err)
|
||||
}
|
||||
|
||||
// Update the server's address in case the address had the port zero, which
|
||||
// would mean that a random available port was automatically chosen.
|
||||
srv.Addr = l.Addr().String()
|
||||
|
||||
log.Info("websvc: starting srv %s://%s", proto, srv.Addr)
|
||||
|
||||
l = &waitListener{
|
||||
Listener: l,
|
||||
firstAcceptWG: wg,
|
||||
}
|
||||
|
||||
err = srv.Serve(l)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
srv.ErrorLog.Printf("starting srv %s: %s", addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown implements the [agh.Service] interface for *Service. svc may be
|
||||
// nil.
|
||||
func (svc *Service) Shutdown(ctx context.Context) (err error) {
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, srv := range svc.servers {
|
||||
serr := srv.Shutdown(ctx)
|
||||
if serr != nil {
|
||||
errs = append(errs, fmt.Errorf("shutting down srv %s: %w", srv.Addr, serr))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.List("shutting down", errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config returns the current configuration of the web service. Config must not
|
||||
// be called simultaneously with Start. If svc was initialized with ":0"
|
||||
// addresses, addrs will not return the actual bound ports until Start is
|
||||
// finished.
|
||||
func (svc *Service) Config() (c *Config) {
|
||||
c = &Config{
|
||||
ConfigManager: svc.confMgr,
|
||||
TLS: svc.tls,
|
||||
// Leave Addresses and SecureAddresses empty and get the actual
|
||||
// addresses that include the :0 ones later.
|
||||
Start: svc.start,
|
||||
Timeout: svc.timeout,
|
||||
ForceHTTPS: svc.forceHTTPS,
|
||||
}
|
||||
|
||||
c.Addresses, c.SecureAddresses = svc.addrs()
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package websvc
|
||||
|
||||
import "time"
|
||||
|
||||
// testTimeout is the common timeout for tests.
|
||||
const testTimeout = 1 * time.Second
|
||||
@@ -1,187 +0,0 @@
|
||||
package websvc_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
aghtest.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// testTimeout is the common timeout for tests.
|
||||
const testTimeout = 1 * time.Second
|
||||
|
||||
// testStart is the server start value for tests.
|
||||
var testStart = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// type check
|
||||
var _ websvc.ConfigManager = (*configManager)(nil)
|
||||
|
||||
// configManager is a [websvc.ConfigManager] for tests.
|
||||
type configManager struct {
|
||||
onDNS func() (svc websvc.ServiceWithConfig[*dnssvc.Config])
|
||||
onWeb func() (svc websvc.ServiceWithConfig[*websvc.Config])
|
||||
|
||||
onUpdateDNS func(ctx context.Context, c *dnssvc.Config) (err error)
|
||||
onUpdateWeb func(ctx context.Context, c *websvc.Config) (err error)
|
||||
}
|
||||
|
||||
// DNS implements the [websvc.ConfigManager] interface for *configManager.
|
||||
func (m *configManager) DNS() (svc websvc.ServiceWithConfig[*dnssvc.Config]) {
|
||||
return m.onDNS()
|
||||
}
|
||||
|
||||
// Web implements the [websvc.ConfigManager] interface for *configManager.
|
||||
func (m *configManager) Web() (svc websvc.ServiceWithConfig[*websvc.Config]) {
|
||||
return m.onWeb()
|
||||
}
|
||||
|
||||
// UpdateDNS implements the [websvc.ConfigManager] interface for *configManager.
|
||||
func (m *configManager) UpdateDNS(ctx context.Context, c *dnssvc.Config) (err error) {
|
||||
return m.onUpdateDNS(ctx, c)
|
||||
}
|
||||
|
||||
// UpdateWeb implements the [websvc.ConfigManager] interface for *configManager.
|
||||
func (m *configManager) UpdateWeb(ctx context.Context, c *websvc.Config) (err error) {
|
||||
return m.onUpdateWeb(ctx, c)
|
||||
}
|
||||
|
||||
// newConfigManager returns a *configManager all methods of which panic.
|
||||
func newConfigManager() (m *configManager) {
|
||||
return &configManager{
|
||||
onDNS: func() (svc websvc.ServiceWithConfig[*dnssvc.Config]) { panic("not implemented") },
|
||||
onWeb: func() (svc websvc.ServiceWithConfig[*websvc.Config]) { panic("not implemented") },
|
||||
onUpdateDNS: func(_ context.Context, _ *dnssvc.Config) (err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
onUpdateWeb: func(_ context.Context, _ *websvc.Config) (err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// newTestServer creates and starts a new web service instance as well as its
|
||||
// sole address. It also registers a cleanup procedure, which shuts the
|
||||
// instance down.
|
||||
//
|
||||
// TODO(a.garipov): Use svc or remove it.
|
||||
func newTestServer(
|
||||
t testing.TB,
|
||||
confMgr websvc.ConfigManager,
|
||||
) (svc *websvc.Service, addr netip.AddrPort) {
|
||||
t.Helper()
|
||||
|
||||
c := &websvc.Config{
|
||||
ConfigManager: confMgr,
|
||||
TLS: nil,
|
||||
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")},
|
||||
SecureAddresses: nil,
|
||||
Timeout: testTimeout,
|
||||
Start: testStart,
|
||||
ForceHTTPS: false,
|
||||
}
|
||||
|
||||
svc = websvc.New(c)
|
||||
|
||||
err := svc.Start()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
err = svc.Shutdown(ctx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
c = svc.Config()
|
||||
require.NotNil(t, c)
|
||||
require.Len(t, c.Addresses, 1)
|
||||
|
||||
return svc, c.Addresses[0]
|
||||
}
|
||||
|
||||
// jobj is a utility alias for JSON objects.
|
||||
type jobj map[string]any
|
||||
|
||||
// httpGet is a helper that performs an HTTP GET request and returns the body of
|
||||
// the response as well as checks that the status code is correct.
|
||||
//
|
||||
// TODO(a.garipov): Add helpers for other methods.
|
||||
func httpGet(t testing.TB, u *url.URL, wantCode int) (body []byte) {
|
||||
t.Helper()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
require.NoErrorf(t, err, "creating req")
|
||||
|
||||
httpCli := &http.Client{
|
||||
Timeout: testTimeout,
|
||||
}
|
||||
resp, err := httpCli.Do(req)
|
||||
require.NoErrorf(t, err, "performing req")
|
||||
require.Equal(t, wantCode, resp.StatusCode)
|
||||
|
||||
testutil.CleanupAndRequireSuccess(t, resp.Body.Close)
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoErrorf(t, err, "reading body")
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
// httpPatch is a helper that performs an HTTP PATCH request with JSON-encoded
|
||||
// reqBody as the request body and returns the body of the response as well as
|
||||
// checks that the status code is correct.
|
||||
//
|
||||
// TODO(a.garipov): Add helpers for other methods.
|
||||
func httpPatch(t testing.TB, u *url.URL, reqBody any, wantCode int) (body []byte) {
|
||||
t.Helper()
|
||||
|
||||
b, err := json.Marshal(reqBody)
|
||||
require.NoErrorf(t, err, "marshaling reqBody")
|
||||
|
||||
req, err := http.NewRequest(http.MethodPatch, u.String(), bytes.NewReader(b))
|
||||
require.NoErrorf(t, err, "creating req")
|
||||
|
||||
httpCli := &http.Client{
|
||||
Timeout: testTimeout,
|
||||
}
|
||||
resp, err := httpCli.Do(req)
|
||||
require.NoErrorf(t, err, "performing req")
|
||||
require.Equal(t, wantCode, resp.StatusCode)
|
||||
|
||||
testutil.CleanupAndRequireSuccess(t, resp.Body.Close)
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoErrorf(t, err, "reading body")
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
func TestService_Start_getHealthCheck(t *testing.T) {
|
||||
confMgr := newConfigManager()
|
||||
_, addr := newTestServer(t, confMgr)
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathHealthCheck,
|
||||
}
|
||||
|
||||
body := httpGet(t, u, http.StatusOK)
|
||||
|
||||
assert.Equal(t, []byte("OK"), body)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package querylog
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -47,7 +48,24 @@ func (l *queryLog) handleQueryLog(w http.ResponseWriter, r *http.Request) {
|
||||
// convert log entries to JSON
|
||||
data := l.entriesToJSON(entries, oldest)
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, data)
|
||||
jsonVal, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
"Couldn't marshal data into json: %s",
|
||||
err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(jsonVal)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *queryLog) handleQueryLogClear(_ http.ResponseWriter, _ *http.Request) {
|
||||
@@ -56,13 +74,23 @@ func (l *queryLog) handleQueryLogClear(_ http.ResponseWriter, _ *http.Request) {
|
||||
|
||||
// Get configuration
|
||||
func (l *queryLog) handleQueryLogInfo(w http.ResponseWriter, r *http.Request) {
|
||||
resp := qlogConfig{
|
||||
Enabled: l.conf.Enabled,
|
||||
Interval: l.conf.RotationIvl.Hours() / 24,
|
||||
AnonymizeClientIP: l.conf.AnonymizeClientIP,
|
||||
resp := qlogConfig{}
|
||||
resp.Enabled = l.conf.Enabled
|
||||
resp.Interval = l.conf.RotationIvl.Hours() / 24
|
||||
resp.AnonymizeClientIP = l.conf.AnonymizeClientIP
|
||||
|
||||
jsonVal, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(jsonVal)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "http write: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// AnonymizeIP masks ip to anonymize the client if the ip is a valid one.
|
||||
|
||||
@@ -55,7 +55,12 @@ func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(w).Encode(resp)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// configResp is the response to the GET /control/stats_info.
|
||||
@@ -66,7 +71,13 @@ type configResp struct {
|
||||
// handleStatsInfo handles requests to the GET /control/stats_info endpoint.
|
||||
func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
|
||||
resp := configResp{IntervalDays: atomic.LoadUint32(&s.limitHours) / 24}
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
err := json.NewEncoder(w).Encode(resp)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleStatsConfig handles requests to the POST /control/stats_config
|
||||
|
||||
@@ -63,6 +63,14 @@ func Version() (v string) {
|
||||
return version
|
||||
}
|
||||
|
||||
// Constants defining the format of module information string.
|
||||
const (
|
||||
modInfoAtSep = "@"
|
||||
modInfoDevSep = " "
|
||||
modInfoSumLeft = " (sum: "
|
||||
modInfoSumRight = ")"
|
||||
)
|
||||
|
||||
// fmtModule returns formatted information about module. The result looks like:
|
||||
//
|
||||
// github.com/Username/module@v1.2.3 (sum: someHASHSUM=)
|
||||
@@ -79,16 +87,14 @@ func fmtModule(m *debug.Module) (formatted string) {
|
||||
|
||||
stringutil.WriteToBuilder(b, m.Path)
|
||||
if ver := m.Version; ver != "" {
|
||||
sep := "@"
|
||||
sep := modInfoAtSep
|
||||
if ver == "(devel)" {
|
||||
sep = " "
|
||||
sep = modInfoDevSep
|
||||
}
|
||||
|
||||
stringutil.WriteToBuilder(b, sep, ver)
|
||||
}
|
||||
|
||||
if sum := m.Sum; sum != "" {
|
||||
stringutil.WriteToBuilder(b, "(sum: ", sum, ")")
|
||||
stringutil.WriteToBuilder(b, modInfoSumLeft, sum, modInfoSumRight)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
|
||||
21
main_next.go
21
main_next.go
@@ -1,21 +0,0 @@
|
||||
//go:build next
|
||||
// +build next
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/cmd"
|
||||
)
|
||||
|
||||
// Embed the prebuilt client here since we strive to keep .go files inside the
|
||||
// internal directory and the embed package is unable to embed files located
|
||||
// outside of the same or underlying directory.
|
||||
|
||||
//go:embed build2
|
||||
var clientBuildFS embed.FS
|
||||
|
||||
func main() {
|
||||
cmd.Main(clientBuildFS)
|
||||
}
|
||||
@@ -6,28 +6,6 @@
|
||||
|
||||
|
||||
|
||||
## v0.107.15: `POST` Requests Without Bodies
|
||||
|
||||
As an additional CSRF protection measure, AdGuard Home now ensures that requests
|
||||
that change its state but have no body do not have a `Content-Type` header set
|
||||
on them.
|
||||
|
||||
This concerns the following APIs:
|
||||
|
||||
* `POST /control/dhcp/reset_leases`;
|
||||
* `POST /control/dhcp/reset`;
|
||||
* `POST /control/parental/disable`;
|
||||
* `POST /control/parental/enable`;
|
||||
* `POST /control/querylog_clear`;
|
||||
* `POST /control/safebrowsing/disable`;
|
||||
* `POST /control/safebrowsing/enable`;
|
||||
* `POST /control/safesearch/disable`;
|
||||
* `POST /control/safesearch/enable`;
|
||||
* `POST /control/stats_reset`;
|
||||
* `POST /control/update`.
|
||||
|
||||
|
||||
|
||||
## v0.107.14: BREAKING API CHANGES
|
||||
|
||||
A Cross-Site Request Forgery (CSRF) vulnerability has been discovered. We have
|
||||
@@ -35,9 +13,6 @@ implemented several measures to prevent such vulnerabilities in the future, but
|
||||
some of these measures break backwards compatibility for the sake of better
|
||||
protection.
|
||||
|
||||
All JSON APIs that expect a body now check if the request actually has
|
||||
`Content-Type` set to `application/json`.
|
||||
|
||||
All new formats for the request and response bodies are documented in
|
||||
`openapi.yaml`.
|
||||
|
||||
|
||||
@@ -601,10 +601,11 @@
|
||||
'summary': 'Set user-defined filter rules'
|
||||
'requestBody':
|
||||
'content':
|
||||
'application/json':
|
||||
'text/plain':
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/SetRulesRequest'
|
||||
'description': 'Custom filtering rules.'
|
||||
'type': 'string'
|
||||
'example': '@@||yandex.ru^|'
|
||||
'description': 'All filtering rules, one line per rule'
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK.'
|
||||
@@ -1537,19 +1538,6 @@
|
||||
'properties':
|
||||
'updated':
|
||||
'type': 'integer'
|
||||
'SetRulesRequest':
|
||||
'description': 'Custom filtering rules setting request.'
|
||||
'example':
|
||||
'rules':
|
||||
- '||example.com^'
|
||||
- '# comment'
|
||||
- '@@||www.example.com^'
|
||||
'properties':
|
||||
'rules':
|
||||
'items':
|
||||
'type': 'string'
|
||||
'type': 'array'
|
||||
'type': 'object'
|
||||
'GetVersionRequest':
|
||||
'type': 'object'
|
||||
'description': '/version.json request data'
|
||||
|
||||
5041
openapi/v1.yaml
5041
openapi/v1.yaml
File diff suppressed because it is too large
Load Diff
@@ -123,14 +123,4 @@ CGO_ENABLED="$cgo_enabled"
|
||||
GO111MODULE='on'
|
||||
export CGO_ENABLED GO111MODULE
|
||||
|
||||
# Build the new binary if requested.
|
||||
if [ "${V1API:-0}" -eq '0' ]
|
||||
then
|
||||
tags_flags='--tags='
|
||||
else
|
||||
tags_flags='--tags=v1'
|
||||
fi
|
||||
readonly tags_flags
|
||||
|
||||
"$go" build --ldflags "$ldflags" "$race_flags" "$tags_flags" --trimpath "$o_flags" "$v_flags"\
|
||||
"$x_flags"
|
||||
"$go" build --ldflags "$ldflags" "$race_flags" --trimpath "$o_flags" "$v_flags" "$x_flags"
|
||||
|
||||
@@ -136,7 +136,6 @@ underscores() {
|
||||
-e '_freebsd.go'\
|
||||
-e '_linux.go'\
|
||||
-e '_little.go'\
|
||||
-e '_next.go'\
|
||||
-e '_openbsd.go'\
|
||||
-e '_others.go'\
|
||||
-e '_test.go'\
|
||||
@@ -223,13 +222,13 @@ govulncheck ./...
|
||||
|
||||
# Apply more lax standards to the code we haven't properly refactored yet.
|
||||
gocyclo --over 17 ./internal/querylog/
|
||||
gocyclo --over 13 ./internal/dhcpd ./internal/filtering/ ./internal/home/
|
||||
gocyclo --over 15 ./internal/home/ ./internal/dhcpd
|
||||
gocyclo --over 13 ./internal/filtering/
|
||||
|
||||
# Apply stricter standards to new or somewhat refactored code.
|
||||
gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\
|
||||
./internal/aghtest/ ./internal/dnsforward/ ./internal/stats/\
|
||||
./internal/tools/ ./internal/updater/ ./internal/next/ ./internal/version/\
|
||||
./main.go
|
||||
./internal/tools/ ./internal/updater/ ./internal/version/ ./main.go
|
||||
|
||||
ineffassign ./...
|
||||
|
||||
|
||||
Reference in New Issue
Block a user