Compare commits

..

14 Commits

Author SHA1 Message Date
Ainar Garipov
f1dd33346a Pull request: upd-chlog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 8885f3f2291947d76203873dce0ccfd5c270fa7f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 7 16:56:38 2022 +0300

    all: upd chlog
2022-10-07 17:05:01 +03:00
Ainar Garipov
960a7a75ed Pull request: upd-go
Merge in DNS/adguard-home from upd-go to master

Squashed commit of the following:

commit 3dffc8b5d8951216c1e695472199fc9e9d85e1c7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 7 14:30:23 2022 +0300

    all: fix chlog

commit cca70bd6cd27d04cd7cebe14a4e4bef112ce2bcb
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 7 14:07:39 2022 +0300

    all: upd go
2022-10-07 15:48:51 +03:00
Ainar Garipov
330ac30324 Pull request: 3418-clientid-doh
Closes #3418.

Squashed commit of the following:

commit 8a1180f8ef03d30ea3ae6a3e3121ddcac513f45b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Oct 5 17:26:22 2022 +0300

    all: imp docs, tests

commit 9629c69b39540db119044f2f79c1c4ed39de911f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Oct 5 15:34:33 2022 +0300

    dnsforward: accept clientids from doh client srvname
2022-10-05 18:11:09 +03:00
Ainar Garipov
2e0f6e5468 Pull request: refactor-opts
Updates #2893.

Squashed commit of the following:

commit c7027abd1088e27569367f3450e9225ff605b43d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Oct 5 16:54:23 2022 +0300

    home: imp docs

commit 86a5b0aca916a7db608eba8263ecdc6ca79c8043
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Oct 5 16:50:44 2022 +0300

    home: refactor opts more

commit 74c5989d1edf8d007dec847f4aaa0d7a0d24dc38
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Oct 5 15:17:26 2022 +0300

    home: refactor option parsing
2022-10-05 17:07:08 +03:00
Ainar Garipov
f557339ca0 Pull request: imp-cache-label
Merge in DNS/adguard-home from imp-cache-label to master

Squashed commit of the following:

commit 10f62aa078b5306525578e22476835ee2e7bac66
Merge: 08c2de0e fe8be370
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 4 16:30:43 2022 +0300

    Merge branch 'master' into imp-cache-label

commit 08c2de0edbb1138b47d1a02d6630aa99b7ddcec9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 4 16:19:36 2022 +0300

    client: imp label

commit e66fbbe3cc6f929ff26fe3d7b8e14acc95f5c0ff
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 4 16:17:15 2022 +0300

    client: imp upstream example

commit d073f71cc5df4ba5f7de7ed08ad1215f7a198539
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 4 15:44:58 2022 +0300

    client: imp upstreams

commit b78d06db645a9f496bed699f4d4bf8c7396148f3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 4 14:59:30 2022 +0300

    client: imp cache size label
2022-10-04 16:36:38 +03:00
Ainar Garipov
fe8be3701f Pull request: websvc-config-manager
Merge in DNS/adguard-home from websvc-config-manager to master

Squashed commit of the following:

commit 2143b47c6528030dfe059172888fddf9061e42da
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 4 14:50:47 2022 +0300

    next: add config manager
2022-10-04 16:02:55 +03:00
Ainar Garipov
c26ab190e7 Pull request: imp-json-resp
Merge in DNS/adguard-home from imp-json-resp to master

Squashed commit of the following:

commit 44532b6fa551815e5ea876e7320ce0a73c32b6fb
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Sep 30 15:59:58 2022 +0300

    all: imp json resp
2022-10-04 14:35:10 +03:00
Ainar Garipov
bf792b83f6 Pull request: upd-chlog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 368025b27cb74fc5b6acbfd9382adeebc4ca6cbd
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Oct 3 19:23:32 2022 +0300

    all: upd chlog
2022-10-03 19:32:55 +03:00
Ainar Garipov
0cce420261 Pull request: 3955-doh3
Updates #3955.

Squashed commit of the following:

commit acfd5ccc29ff03dfae1e51e52649acdf05042d9f
Merge: caeac6e5 61bd217e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Oct 3 18:00:37 2022 +0300

    Merge branch 'master' into 3955-doh3

commit caeac6e5401bcaa95bba8d2b84a943b6c9a5898a
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Oct 3 17:54:16 2022 +0300

    all: fix server closing; imp docs

commit 87396141ff49d48ae54b4184559070e7885bccc7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Oct 3 17:33:39 2022 +0300

    all: add doh3 support
2022-10-03 18:08:05 +03:00
Eugene Burkov
61bd217eb3 Pull request: 4983 fix custom svcs
Merge in DNS/adguard-home from 4983-fix-custom-svcs to master

Updates #4945.
Updates #4982.

* commit '739e0098ec127045197ea697bae78acb2bd2c729':
  all: imp code, wording
  home: fix empty svcs
  bugfix: Fixed bug which was causing clients to continue following global service blocks even when user opted to not do that
2022-10-03 17:57:13 +03:00
Eugene Burkov
739e0098ec all: imp code, wording 2022-10-03 17:48:11 +03:00
Eugene Burkov
27032ef79e home: fix empty svcs 2022-10-03 17:30:03 +03:00
Ishan Jain
5e626306d1 bugfix: Fixed bug which was causing clients to continue following global service blocks even when user opted to not do that 2022-10-03 00:02:35 +05:30
Eugene Burkov
2ffea605cf Pull request: home: fix client blocked svcs
Merge in DNS/adguard-home from fix-blocked-svcs to master

Squashed commit of the following:

commit 7e4ccbeb8866501c8f488efa76f7ce32369416ec
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Sep 30 16:48:27 2022 +0300

    all: imp log of changes

commit 171836fb8033ac1b7fc3ed3f4bd18669795fe1dd
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Sep 30 16:22:07 2022 +0300

    all: log changes

commit f5cde0a75a84b8efe59154ad019846ea06ec7c08
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Sep 30 16:04:14 2022 +0300

    home: fix client blocked svcs
2022-09-30 16:57:33 +03:00
40 changed files with 998 additions and 755 deletions

View File

@@ -1,7 +1,7 @@
'name': 'build'
'env':
'GO_VERSION': '1.18.6'
'GO_VERSION': '1.18.7'
'NODE_VERSION': '14'
'on':

View File

@@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.18.6'
'GO_VERSION': '1.18.7'
'on':
'push':

View File

@@ -15,6 +15,47 @@ 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
See also the [v0.107.15 GitHub milestone][ms-v0.107.15].
### Security
- As an additional CSRF protection measure, AdGuard Home now ensures that
@@ -22,21 +63,34 @@ and this project adheres to
/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
<!--
## [v0.107.15] - 2022-10-26 (APPROX.)
See also the [v0.107.15 GitHub milestone][ms-v0.107.15].
[#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
-->
@@ -134,7 +188,7 @@ See also the [v0.107.12 GitHub milestone][ms-v0.107.12].
### Security
- Go version was updated to prevent the possibility of exploiting the
- Go version has been 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].
@@ -255,7 +309,7 @@ See also the [v0.107.9 GitHub milestone][ms-v0.107.9].
### Security
- Go version was updated to prevent the possibility of exploiting the
- Go version has been 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.
@@ -298,7 +352,7 @@ See also the [v0.107.8 GitHub milestone][ms-v0.107.8].
### Security
- Go version was updated to prevent the possibility of exploiting the
- Go version has been 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].
@@ -334,7 +388,7 @@ See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
### Security
- Go version was updated to prevent the possibility of exploiting the
- Go version has been 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]).
@@ -491,7 +545,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 was updated to prevent the possibility of exploiting the
- Go version has been updated to prevent the possibility of exploiting the
[CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] Go vulnerabilities.
### Added
@@ -546,7 +600,7 @@ were resolved.
### Security
- Go version was updated to prevent the possibility of exploiting the
- Go version has been updated to prevent the possibility of exploiting the
[CVE-2022-24921] Go vulnerability.
[CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921
@@ -559,7 +613,7 @@ See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
### Security
- Go version was updated to prevent the possibility of exploiting the
- Go version has been updated to prevent the possibility of exploiting the
[CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] Go vulnerabilities.
### Fixed
@@ -1296,11 +1350,13 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!--
[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.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.14...HEAD
[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
[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

View File

@@ -7,7 +7,7 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:5.1'
'dockerGo': 'adguard/golang-ubuntu:5.2'
'stages':
- 'Build frontend':
@@ -322,7 +322,7 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:5.1'
'dockerGo': 'adguard/golang-ubuntu:5.2'
# release-vX.Y.Z branches are the branches from which the actual final release
# is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -337,4 +337,4 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:5.1'
'dockerGo': 'adguard/golang-ubuntu:5.2'

View File

@@ -5,7 +5,7 @@
'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests'
'variables':
'dockerGo': 'adguard/golang-ubuntu:5.1'
'dockerGo': 'adguard/golang-ubuntu:5.2'
'stages':
- 'Tests':

View File

@@ -215,6 +215,7 @@
"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);",
@@ -605,7 +606,7 @@
"blocklist": "Blocklist",
"milliseconds_abbreviation": "ms",
"cache_size": "Cache size",
"cache_size_desc": "DNS cache size (in bytes).",
"cache_size_desc": "DNS cache size (in bytes). To disable caching, leave empty.",
"cache_ttl_min_override": "Override minimum TTL",
"cache_ttl_max_override": "Override maximum TTL",
"enter_cache_size": "Enter cache size (bytes)",

View File

@@ -57,6 +57,22 @@ 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
View File

@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
go 1.18
require (
github.com/AdguardTeam/dnsproxy v0.44.0
github.com/AdguardTeam/dnsproxy v0.45.2
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.0
github.com/lucas-clemente/quic-go v0.29.1
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-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
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
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.0
@@ -43,10 +43,12 @@ 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
@@ -57,7 +59,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.20220818022119-ed83ed61efb9 // indirect
golang.org/x/mod v0.6.0-dev.0.20220922195421-2adab6b8c60e // 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
View File

@@ -1,5 +1,5 @@
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/dnsproxy v0.45.2 h1:K9BXkQAfAKjrzbWbczpA2IA1owLe/edv0nG0e2+Esko=
github.com/AdguardTeam/dnsproxy v0.45.2/go.mod h1:h+0r4GDvHHY2Wu6r7oqva+O37h00KofYysfzy1TEXFE=
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,6 +23,8 @@ 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=
@@ -88,8 +90,10 @@ 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.0 h1:Vw0mGTfmWqGzh4jx/kMymsIkFK6rErFVmg+t9RLrnZE=
github.com/lucas-clemente/quic-go v0.29.0/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE=
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/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=
@@ -125,6 +129,7 @@ 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=
@@ -168,16 +173,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-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/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/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.20220818022119-ed83ed61efb9 h1:VtCrPQXM5Wo9l7XN64SjBMczl48j8mkP+2e3OhYlz+0=
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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/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=
@@ -189,6 +194,7 @@ 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=
@@ -200,8 +206,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-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo=
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
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/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=
@@ -224,6 +230,7 @@ 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=
@@ -247,8 +254,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-20220906135438-9e1f76180b77 h1:C1tElbkWrsSkn3IRl1GCW/gETw1TywWIPgwZtXTZbYg=
golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77/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/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=

View File

@@ -62,9 +62,16 @@ func WriteTextPlainDeprecated(w http.ResponseWriter, r *http.Request) (isPlainTe
}
// WriteJSONResponse sets the content-type header in w.Header() to
// "application/json", encodes resp to w, calls Error on any returned error, and
// returns it as well.
// "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.
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 {

View File

@@ -78,18 +78,7 @@ func (s *server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) {
status.Leases = s.Leases(LeasesDynamic)
status.StaticLeases = s.Leases(LeasesStatic)
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,
)
}
_ = aghhttp.WriteJSONResponse(w, r, status)
}
func (s *server) enableDHCP(ifaceName string) (code int, err error) {
@@ -246,22 +235,7 @@ func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
return
}
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.setConfFromJSON(conf, srv4, srv6)
s.conf.ConfigModified()
err = s.dbLoad()
@@ -280,6 +254,26 @@ 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"`

View File

@@ -3,11 +3,10 @@
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.
@@ -25,15 +24,9 @@ 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) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotImplemented)
err := json.NewEncoder(w).Encode(&jsonError{
_ = aghhttp.WriteJSONResponseCode(w, r, http.StatusNotImplemented, &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

View File

@@ -183,15 +183,7 @@ func (s *Server) accessListJSON() (j accessListJSON) {
}
func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
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
}
_ = aghhttp.WriteJSONResponse(w, r, s.accessListJSON())
}
// validateAccessSet checks the internal accessListJSON lists. To search for

View File

@@ -123,7 +123,14 @@ type quicConnection interface {
func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string, err error) {
proto := pctx.Proto
if proto == proxy.ProtoHTTPS {
return clientIDFromDNSContextHTTPS(pctx)
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.
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
return "", nil
}
@@ -133,31 +140,9 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
return "", nil
}
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
cliSrvName, err := clientServerName(pctx, proto)
if err != nil {
return "", err
}
clientID, err = clientIDFromClientServerName(
@@ -171,3 +156,35 @@ 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
}

View File

@@ -160,6 +160,22 @@ 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 {
@@ -173,16 +189,32 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
conf: ServerConfig{TLSConfig: tlsConf},
}
var conn net.Conn
if tc.proto == proxy.ProtoTLS {
conn = testTLSConn{
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{
serverName: tc.cliSrvName,
}
}
var qconn quic.Connection
if tc.proto == proxy.ProtoQUIC {
qconn = testQUICConnection{
case proxy.ProtoTLS:
conn = testTLSConn{
serverName: tc.cliSrvName,
}
}
@@ -190,6 +222,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
pctx := &proxy.DNSContext{
Proto: tc.proto,
Conn: conn,
HTTPRequest: httpReq,
QUICConnection: qconn,
}
@@ -205,56 +238,76 @@ 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{

View File

@@ -201,6 +201,10 @@ 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
@@ -208,9 +212,12 @@ type ServerConfig struct {
// locally-served networks should be resolved via private PTR resolvers.
UsePrivateRDNS bool
// LocalPTRResolvers is a slice of addresses to be used as upstreams for
// resolving PTR queries for local addresses.
LocalPTRResolvers []string
// 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
}
// if any of ServerConfig values are zero, then default values from below are used
@@ -226,6 +233,7 @@ 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,
@@ -324,6 +332,20 @@ 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
@@ -353,12 +375,14 @@ 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,
Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
HTTPVersions: httpVersions,
},
)
if err != nil {
@@ -371,8 +395,9 @@ func (s *Server) prepareUpstreamSettings() error {
uc, err = proxy.ParseUpstreamsConfig(
defaultDNS,
&upstream.Options{
Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
HTTPVersions: httpVersions,
},
)
if err != nil {

View File

@@ -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 canonnical
// section from pctx and returns a non-nil res if at least one of canonical
// names or IP addresses in it matches the filtering rules.
func (s *Server) filterDNSResponse(
pctx *proxy.DNSContext,

View File

@@ -112,13 +112,7 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
DefautLocalPTRUpstreams: defLocalPTRUps,
}
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
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
func (req *jsonDNSConfig) checkBlockingMode() (err error) {
@@ -349,7 +343,10 @@ 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
@@ -412,7 +409,15 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet)
return nil
}
var protocols = []string{"udp://", "tcp://", "tls://", "https://", "sdns://", "quic://"}
var protocols = []string{
"h3://",
"https://",
"quic://",
"sdns://",
"tcp://",
"tls://",
"udp://",
}
// validateUpstream returns an error if u alongside with domains is not a valid
// upstream configuration. useDefault is true if the upstream is
@@ -659,24 +664,7 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
result[host] = "OK"
}
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)
}
_ = aghhttp.WriteJSONResponse(w, r, result)
}
// handleDoH is the DNS-over-HTTPs handler.
@@ -692,11 +680,13 @@ 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
}

View File

@@ -12,6 +12,7 @@ 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"
@@ -116,7 +117,8 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
s.conf = tc.conf()
s.handleGetConfig(w, nil)
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
cType := w.Header().Get(aghhttp.HdrNameContentType)
assert.Equal(t, aghhttp.HdrValApplicationJSON, cType)
assert.JSONEq(t, string(caseWant), w.Body.String())
})
}

View File

@@ -453,13 +453,7 @@ func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string) {
}
func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) {
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
}
_ = aghhttp.WriteJSONResponse(w, r, serviceIDs)
}
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
@@ -467,13 +461,7 @@ func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Req
list := d.Config.BlockedServices
d.confLock.RUnlock()
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
}
_ = aghhttp.WriteJSONResponse(w, r, list)
}
func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {

View File

@@ -301,14 +301,7 @@ func (d *DNSFilter) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
return
}
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
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
type filterJSON struct {
@@ -361,17 +354,7 @@ func (d *DNSFilter) handleFilteringStatus(w http.ResponseWriter, r *http.Request
resp.UserRules = d.UserRules
d.filtersMu.RUnlock()
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)
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// Set filtering configuration
@@ -473,11 +456,7 @@ func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
}
}
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)
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// RegisterFilteringHandlers - register handlers

View File

@@ -240,13 +240,7 @@ func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
}
d.confLock.Unlock()
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
}
_ = aghhttp.WriteJSONResponse(w, r, arr)
}
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {

View File

@@ -5,7 +5,6 @@ import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"net/http"
@@ -381,17 +380,13 @@ func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Req
}
func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(&struct {
resp := &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)
return
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
@@ -405,13 +400,11 @@ func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request
}
func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(&struct {
resp := &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)
}

View File

@@ -5,7 +5,6 @@ import (
"context"
"encoding/binary"
"encoding/gob"
"encoding/json"
"fmt"
"net"
"net/http"
@@ -146,21 +145,13 @@ func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Reque
}
func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(&struct {
resp := &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{

View File

@@ -12,16 +12,11 @@ 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()

View File

@@ -456,8 +456,9 @@ func (clients *clientsContainer) findUpstreams(
conf, err = proxy.ParseUpstreamsConfig(
upstreams,
&upstream.Options{
Bootstrap: config.DNS.BootstrapDNS,
Timeout: config.DNS.UpstreamTimeout.Duration,
Bootstrap: config.DNS.BootstrapDNS,
Timeout: config.DNS.UpstreamTimeout.Duration,
HTTPVersions: dnsforward.UpstreamHTTPVersions(config.DNS.UseHTTP3Upstreams),
},
)
if err != nil {

View File

@@ -166,6 +166,19 @@ 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 {

View File

@@ -1,7 +1,6 @@
package home
import (
"encoding/json"
"fmt"
"net"
"net/http"
@@ -155,19 +154,12 @@ type profileJSON struct {
}
func handleGetProfile(w http.ResponseWriter, r *http.Request) {
pj := profileJSON{}
u := Context.auth.getCurrentUser(r)
pj.Name = u.Name
data, err := json.Marshal(pj)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "json.Marshal: %s", err)
return
resp := &profileJSON{
Name: u.Name,
}
_, _ = w.Write(data)
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// ------------------------

View File

@@ -21,6 +21,7 @@ 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.
@@ -328,6 +329,7 @@ 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("")
@@ -336,13 +338,38 @@ func shutdownSrv(ctx context.Context, srv *http.Server) {
}
err := srv.Shutdown(ctx)
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)
}
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)
}
}
@@ -545,16 +572,11 @@ 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,
"Failed to encode 'check_config' JSON data: %s",
err,
)
aghhttp.Error(r, w, http.StatusBadRequest, "encoding check_config: %s", err)
return
}
body := nonBetaReqBody.String()
r.Body = io.NopCloser(strings.NewReader(body))
r.ContentLength = int64(len(body))
@@ -622,13 +644,7 @@ 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,
"Failed to encode 'check_config' JSON data: %s",
err,
)
aghhttp.Error(r, w, http.StatusBadRequest, "encoding configure: %s", err)
return
}

View File

@@ -246,11 +246,14 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
newConf.FilterHandler = applyAdditionalFiltering
newConf.GetCustomUpstreamByClient = Context.clients.findUpstreams
newConf.ResolveClients = config.Clients.Sources.RDNS
newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS
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
return newConf, nil
}
@@ -358,7 +361,13 @@ 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 {
Context.filters.ApplyBlockedServices(setts, c.BlockedServices)
// 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)
}
setts.ClientName = c.Name

View File

@@ -97,9 +97,15 @@ var Context homeContext
// Main is the entry point
func Main(clientBuildFS fs.FS) {
// 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()
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()
Context.appSignalChannel = make(chan os.Signal)
signal.Notify(Context.appSignalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
@@ -120,26 +126,18 @@ func Main(clientBuildFS fs.FS) {
}
}()
if args.serviceControlAction != "" {
handleServiceControlAction(args, clientBuildFS)
if opts.serviceControlAction != "" {
handleServiceControlAction(opts, clientBuildFS)
return
}
// run the protection
run(args, clientBuildFS)
run(opts, clientBuildFS)
}
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()
}
func setupContext(opts options) {
setupContextFlags(opts)
switch version.Channel() {
case version.ChannelEdge, version.ChannelDevelopment:
@@ -174,13 +172,13 @@ func setupContext(args options) {
os.Exit(1)
}
if args.checkConfig {
if opts.checkConfig {
log.Info("configuration file is ok")
os.Exit(0)
}
if !args.noEtcHosts && config.Clients.Sources.HostsFile {
if !opts.noEtcHosts && config.Clients.Sources.HostsFile {
err = setupHostsContainer()
fatalOnError(err)
}
@@ -189,6 +187,24 @@ func setupContext(args 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.
@@ -270,7 +286,7 @@ func setupHostsContainer() (err error) {
return nil
}
func setupConfig(args options) (err error) {
func setupConfig(opts options) (err error) {
config.DNS.DnsfilterConf.EtcHosts = Context.etcHosts
config.DNS.DnsfilterConf.ConfigModified = onConfigModified
config.DNS.DnsfilterConf.HTTPRegister = httpRegister
@@ -312,9 +328,9 @@ func setupConfig(args options) (err error) {
Context.clients.Init(config.Clients.Persistent, Context.dhcpServer, Context.etcHosts, arpdb)
if args.bindPort != 0 {
if opts.bindPort != 0 {
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(tcpPorts, tcpPort(args.bindPort), tcpPort(config.BetaBindPort))
addPorts(tcpPorts, tcpPort(opts.bindPort), tcpPort(config.BetaBindPort))
udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(config.DNS.Port))
@@ -336,23 +352,23 @@ func setupConfig(args options) (err error) {
return fmt.Errorf("validating udp ports: %w", err)
}
config.BindPort = args.bindPort
config.BindPort = opts.bindPort
}
// override bind host/port from the console
if args.bindHost != nil {
config.BindHost = args.bindHost
if opts.bindHost != nil {
config.BindHost = opts.bindHost
}
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
Context.pidFileName = args.pidFile
if len(opts.pidFile) != 0 && writePIDFile(opts.pidFile) {
Context.pidFileName = opts.pidFile
}
return nil
}
func initWeb(args options, clientBuildFS fs.FS) (web *Web, err error) {
func initWeb(opts options, clientBuildFS fs.FS) (web *Web, err error) {
var clientFS, clientBetaFS fs.FS
if args.localFrontend {
if opts.localFrontend {
log.Info("warning: using local frontend files")
clientFS = os.DirFS("build/static")
@@ -381,9 +397,11 @@ func initWeb(args options, clientBuildFS fs.FS) (web *Web, err error) {
clientFS: clientFS,
clientBetaFS: clientBetaFS,
serveHTTP3: config.DNS.ServeHTTP3,
}
web = CreateWeb(&webConf)
web = newWeb(&webConf)
if web == nil {
return nil, fmt.Errorf("initializing web: %w", err)
}
@@ -398,24 +416,24 @@ func fatalOnError(err error) {
}
// run configures and starts AdGuard Home.
func run(args options, clientBuildFS fs.FS) {
func run(opts options, clientBuildFS fs.FS) {
// configure config filename
initConfigFilename(args)
initConfigFilename(opts)
// configure working dir and config path
initWorkingDir(args)
initWorkingDir(opts)
// configure log level and output
configureLogger(args)
configureLogger(opts)
// Print the first message after logger is configured.
log.Info(version.Full())
log.Debug("current working directory is %s", Context.workDir)
if args.runningAsService {
if opts.runningAsService {
log.Info("AdGuard Home is running as a service")
}
setupContext(args)
setupContext(opts)
err := configureOS(config)
fatalOnError(err)
@@ -425,7 +443,7 @@ func run(args options, clientBuildFS fs.FS) {
// but also avoid relying on automatic Go init() function
filtering.InitModule()
err = setupConfig(args)
err = setupConfig(opts)
fatalOnError(err)
if !Context.firstRun {
@@ -454,7 +472,7 @@ func run(args options, clientBuildFS fs.FS) {
}
sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
GLMode = args.glinetMode
GLMode = opts.glinetMode
var rateLimiter *authRateLimiter
if config.AuthAttempts > 0 && config.AuthBlockMin > 0 {
rateLimiter = newAuthRateLimiter(
@@ -481,7 +499,7 @@ func run(args options, clientBuildFS fs.FS) {
log.Fatalf("Can't initialize TLS module")
}
Context.web, err = initWeb(args, clientBuildFS)
Context.web, err = initWeb(opts, clientBuildFS)
fatalOnError(err)
if !Context.firstRun {
@@ -573,10 +591,10 @@ func writePIDFile(fn string) bool {
return true
}
func initConfigFilename(args options) {
func initConfigFilename(opts options) {
// config file path can be overridden by command-line arguments:
if args.configFilename != "" {
Context.configFilename = args.configFilename
if opts.confFilename != "" {
Context.configFilename = opts.confFilename
} else {
// Default config file name
Context.configFilename = "AdGuardHome.yaml"
@@ -585,15 +603,15 @@ func initConfigFilename(args options) {
// initWorkingDir initializes the workDir
// if no command-line arguments specified, we use the directory where our binary file is located
func initWorkingDir(args options) {
func initWorkingDir(opts options) {
execPath, err := os.Executable()
if err != nil {
panic(err)
}
if args.workDir != "" {
if opts.workDir != "" {
// If there is a custom config file, use it's directory as our working dir
Context.workDir = args.workDir
Context.workDir = opts.workDir
} else {
Context.workDir = filepath.Dir(execPath)
}
@@ -607,15 +625,15 @@ func initWorkingDir(args options) {
}
// configureLogger configures logger level and output
func configureLogger(args options) {
func configureLogger(opts options) {
ls := getLogSettings()
// command-line arguments can override config settings
if args.verbose || config.Verbose {
if opts.verbose || config.Verbose {
ls.Verbose = true
}
if args.logFile != "" {
ls.File = args.logFile
if opts.logFile != "" {
ls.File = opts.logFile
} else if config.File != "" {
ls.File = config.File
}
@@ -636,7 +654,7 @@ func configureLogger(args options) {
// happen pretty quickly.
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
if args.runningAsService && ls.File == "" && runtime.GOOS == "windows" {
if opts.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
@@ -726,25 +744,29 @@ func exitWithError() {
os.Exit(64)
}
// loadOptions reads command line arguments and initializes configuration
func loadOptions() options {
o, f, err := parse(os.Args[0], os.Args[1:])
// 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:])
if err != nil {
log.Error(err.Error())
_ = printHelp(os.Args[0])
printHelp(os.Args[0])
exitWithError()
} else if f != nil {
err = f()
}
if eff != nil {
err = eff()
if err != nil {
log.Error(err.Error())
exitWithError()
} else {
os.Exit(0)
}
os.Exit(0)
}
return o
return opts
}
// printWebAddrs prints addresses built from proto, addr, and an appropriate

View File

@@ -0,0 +1,12 @@
package home
import (
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
)
func TestMain(m *testing.M) {
aghtest.DiscardLogOutput(m)
initCmdLineOpts()
}

View File

@@ -5,30 +5,60 @@ import (
"net"
"os"
"strconv"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
)
// options passed from command-line arguments
type options struct {
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
// TODO(a.garipov): Replace with package flag.
// service control action (see service.ControlAction array + "status" command)
// options represents the command-line options.
type options struct {
// confFilename is the path to the configuration file.
confFilename string
// 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].
serviceControlAction string
// runningAsService flag is set to true when options are passed from the service runner
// 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 bool
glinetMode bool // Activate GL-Inet compatibility mode
// glinetMode shows if the GL-Inet compatibility mode is enabled.
glinetMode bool
// noEtcHosts flag should be provided when /etc/hosts file shouldn't be
// used.
@@ -39,88 +69,85 @@ type options struct {
localFrontend bool
}
// 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)
// 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: "",
})
}
// {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
// effect is the type for functions used for their side-effects.
type effect func() (err error)
func ipSliceOrNil(ip net.IP) []string {
if ip == nil {
return nil
}
// 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)
return []string{ip.String()}
// 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
}
func stringSliceOrNil(s string) []string {
if s == "" {
return nil
}
// 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
}
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) {
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) {
var err error
var p int
minPort, maxPort := 0, 1<<16-1
@@ -131,108 +158,81 @@ var portArg = arg{
} else {
o.bindPort = p
}
return o, err
}, nil, nil,
func(o options) []string { return intSliceOrNil(o.bindPort) },
}
var serviceArg = arg{
"Service control action: status, install, uninstall, start, stop, restart, reload (configuration).",
"service", "s",
func(o options, v string) (options, error) {
return o, err
},
updateNoValue: nil,
effect: nil,
serialize: func(o options) (val string, ok bool) {
if o.bindPort == 0 {
return "", false
}
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) {
o.serviceControlAction = v
return o, nil
}, 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) {
},
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) {
log.Info("warning: using --no-mem-optimization flag has no effect and is deprecated")
return nil, nil
},
func(o options) []string { return nil },
}
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: nil,
effect: func(o options, exec string) (effect, error) {
return func() error {
if o.verbose {
fmt.Println(version.Verbose())
} else {
fmt.Println(version.Full())
}
os.Exit(0)
return nil
}, nil
},
serialize: func(o options) []string { return nil },
}
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 },
}
var noEtcHostsArg = arg{
description: "Deprecated. Do not use the OS-provided hosts.",
longName: "no-etc-hosts",
shortName: "",
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) {
@@ -242,146 +242,216 @@ var noEtcHostsArg = arg{
return nil, nil
},
serialize: func(o options) []string { return boolSliceOrNil(o.noEtcHosts) },
}
var localFrontendArg = arg{
description: "Use local frontend directories.",
longName: "local-frontend",
shortName: "",
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) []string { return boolSliceOrNil(o.localFrontend) },
}
serialize: func(o options) (val string, ok bool) { return "", o.localFrontend },
description: "Use local frontend directories.",
longName: "local-frontend",
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) {
return func() error {
if o.verbose {
fmt.Println(version.Verbose())
} else {
fmt.Println(version.Full())
}
func init() {
args = []arg{
configArg,
workDirArg,
hostArg,
portArg,
serviceArg,
logfileArg,
pidfileArg,
checkConfigArg,
noCheckUpdateArg,
disableMemoryOptimizationArg,
noEtcHostsArg,
localFrontendArg,
verboseArg,
glinetArg,
versionArg,
helpArg,
}
}
os.Exit(0)
func getUsageLines(exec string, args []arg) []string {
usage := []string{
"Usage:",
"",
fmt.Sprintf("%s [options]", exec),
"",
"Options:",
}
for _, arg := range args {
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: "",
}}
// 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{}
stringutil.WriteToBuilder(
b,
"Usage:\n\n",
fmt.Sprintf("%s [options]\n\n", exec),
"Options:\n",
)
var err error
for _, opt := range cmdLineOpts {
val := ""
if arg.updateWithValue != nil {
if opt.updateWithValue != nil {
val = " VALUE"
}
if arg.shortName != "" {
usage = append(usage, fmt.Sprintf(" -%s, %-30s %s",
arg.shortName,
"--"+arg.longName+val,
arg.description))
longDesc := opt.longName + val
if opt.shortName != "" {
_, err = fmt.Fprintf(b, " -%s, --%-28s %s\n", opt.shortName, longDesc, opt.description)
} else {
usage = append(usage, fmt.Sprintf(" %-34s %s",
"--"+arg.longName+val,
arg.description))
_, 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)
}
}
return usage
_, 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()
}
}
func printHelp(exec string) error {
for _, line := range getUsageLines(exec, args) {
_, err := fmt.Println(line)
// 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()
if err != nil {
return err
}
return next()
}
return nil
return eff
}
func argMatches(a arg, v string) bool {
return v == "--"+a.longName || (a.shortName != "" && v == "-"+a.shortName)
}
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
}
// 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
}
if !knownParam {
return o, f, fmt.Errorf("unknown option %v", v)
if opt.shortName != "" {
args = append(args, "-"+opt.shortName)
} else {
args = append(args, "--"+opt.longName)
}
if val != "" {
args = append(args, val)
}
}
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
return args
}

View File

@@ -12,7 +12,7 @@ import (
func testParseOK(t *testing.T, ss ...string) options {
t.Helper()
o, _, err := parse("", ss)
o, _, err := parseCmdOpts("", 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 := parse("", ss)
_, _, err := parseCmdOpts("", ss)
require.Error(t, err)
}
@@ -38,11 +38,11 @@ func TestParseVerbose(t *testing.T) {
}
func TestParseConfigFilename(t *testing.T) {
assert.Equal(t, "", testParseOK(t).configFilename, "empty is no config filename")
assert.Equal(t, "path", testParseOK(t, "-c", "path").configFilename, "-c is config filename")
assert.Equal(t, "", testParseOK(t).confFilename, "empty is no config filename")
assert.Equal(t, "path", testParseOK(t, "-c", "path").confFilename, "-c is config filename")
testParseParamMissing(t, "-c")
assert.Equal(t, "path", testParseOK(t, "--config", "path").configFilename, "--config is config filename")
assert.Equal(t, "path", testParseOK(t, "--config", "path").confFilename, "--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 := parse("", []string{"--no-mem-optimization"})
o, eff, err := parseCmdOpts("", []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 TestSerialize(t *testing.T) {
func TestOptsToArgs(t *testing.T) {
testCases := []struct {
name string
args []string
opts options
ss []string
}{{
name: "empty",
args: []string{},
opts: options{},
ss: []string{},
}, {
name: "config_filename",
opts: options{configFilename: "path"},
ss: []string{"-c", "path"},
args: []string{"-c", "path"},
opts: options{confFilename: "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",
opts: options{
serviceControlAction: "run",
configFilename: "config",
workDir: "work",
pidFile: "pid",
disableUpdate: true,
},
ss: []string{
args: []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 := serialize(tc.opts)
assert.ElementsMatch(t, tc.ss, result)
result := optsToArgs(tc.opts)
assert.ElementsMatch(t, tc.args, result)
})
}
}

View File

@@ -197,7 +197,7 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
DisplayName: serviceDisplayName,
Description: serviceDescription,
WorkingDirectory: pwd,
Arguments: serialize(runOpts),
Arguments: optsToArgs(runOpts),
}
configureService(svcConfig)

View File

@@ -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,

View File

@@ -16,6 +16,8 @@ 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"
)
@@ -53,40 +55,56 @@ type webConfig struct {
WriteTimeout time.Duration
firstRun bool
serveHTTP3 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
// 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
}
// Web - module object
// Web is the web UI and API server.
type Web struct {
conf *webConfig
forceHTTPS bool
httpServer *http.Server // HTTP module
httpsServer HTTPSServer // HTTPS module
conf *webConfig
// handlerBeta is the handler for new client.
handlerBeta http.Handler
// installerBeta is the pre-install handler for new client.
installerBeta http.Handler
// TODO(a.garipov): Refactor all these servers.
httpServer *http.Server
// httpServerBeta is a server for new client.
httpServerBeta *http.Server
// 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
}
// CreateWeb - create module
func CreateWeb(conf *webConfig) *Web {
log.Info("Initialize web module")
// newWeb creates a new instance of the web UI and API server.
func newWeb(conf *webConfig) (w *Web) {
log.Info("web: initializing")
w := Web{}
w.conf = conf
w = &Web{
conf: conf,
}
clientFS := http.FileServer(http.FS(conf.clientFS))
betaClientFS := http.FileServer(http.FS(conf.clientBetaFS))
@@ -108,12 +126,15 @@ func CreateWeb(conf *webConfig) *Web {
}
w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock)
return &w
return w
}
// WebCheckPortAvailable - check if port is available
// BUT: if we are already using this port, no need
func WebCheckPortAvailable(port int) bool {
// 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) {
return Context.web.httpsServer.server != nil ||
aghnet.CheckPort("tcp", config.BindHost, port) == nil
}
@@ -121,7 +142,7 @@ func WebCheckPortAvailable(port int) 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)
@@ -143,6 +164,8 @@ 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()
}
@@ -160,7 +183,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.shutdown {
for !web.httpsServer.inShutdown {
printHTTPAddresses(aghhttp.SchemeHTTP)
errs := make(chan error, 2)
@@ -231,7 +254,7 @@ func (web *Web) Close(ctx context.Context) {
log.Info("stopping http server...")
web.httpsServer.cond.L.Lock()
web.httpsServer.shutdown = true
web.httpsServer.inShutdown = true
web.httpsServer.cond.L.Unlock()
var cancel context.CancelFunc
@@ -239,6 +262,7 @@ 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)
@@ -248,7 +272,7 @@ func (web *Web) Close(ctx context.Context) {
func (web *Web) tlsServerLoop() {
for {
web.httpsServer.cond.L.Lock()
if web.httpsServer.shutdown {
if web.httpsServer.inShutdown {
web.httpsServer.cond.L.Unlock()
break
}
@@ -256,7 +280,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.shutdown {
if web.httpsServer.inShutdown {
web.httpsServer.cond.L.Unlock()
return
}
@@ -264,11 +288,10 @@ func (web *Web) tlsServerLoop() {
web.httpsServer.cond.L.Unlock()
// prepare HTTPS server
address := netutil.JoinHostPort(web.conf.BindHost.String(), web.conf.PortHTTPS)
addr := netutil.JoinHostPort(web.conf.BindHost.String(), web.conf.PortHTTPS)
web.httpsServer.server = &http.Server{
ErrorLog: log.StdLog("web: https", log.DEBUG),
Addr: address,
Addr: addr,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{web.httpsServer.cert},
RootCAs: Context.tlsRoots,
@@ -282,10 +305,40 @@ 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 err != http.ErrServerClosed {
if !errors.Is(err, http.ErrServerClosed) {
cleanupAlways()
log.Fatal(err)
log.Fatalf("web: https: %s", 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)
}
}

View File

@@ -1,7 +1,6 @@
package querylog
import (
"encoding/json"
"fmt"
"net"
"net/http"
@@ -48,24 +47,7 @@ func (l *queryLog) handleQueryLog(w http.ResponseWriter, r *http.Request) {
// convert log entries to JSON
data := l.entriesToJSON(entries, oldest)
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)
}
_ = aghhttp.WriteJSONResponse(w, r, data)
}
func (l *queryLog) handleQueryLogClear(_ http.ResponseWriter, _ *http.Request) {
@@ -74,23 +56,13 @@ func (l *queryLog) handleQueryLogClear(_ http.ResponseWriter, _ *http.Request) {
// Get configuration
func (l *queryLog) handleQueryLogInfo(w http.ResponseWriter, r *http.Request) {
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
resp := qlogConfig{
Enabled: l.conf.Enabled,
Interval: l.conf.RotationIvl.Hours() / 24,
AnonymizeClientIP: l.conf.AnonymizeClientIP,
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonVal)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "http write: %s", err)
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// AnonymizeIP masks ip to anonymize the client if the ip is a valid one.

View File

@@ -55,12 +55,7 @@ func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
return
}
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)
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// configResp is the response to the GET /control/stats_info.
@@ -71,13 +66,7 @@ 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}
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)
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// handleStatsConfig handles requests to the POST /control/stats_config

View File

@@ -223,8 +223,7 @@ govulncheck ./...
# Apply more lax standards to the code we haven't properly refactored yet.
gocyclo --over 17 ./internal/querylog/
gocyclo --over 15 ./internal/home/ ./internal/dhcpd
gocyclo --over 13 ./internal/filtering/
gocyclo --over 13 ./internal/dhcpd ./internal/filtering/ ./internal/home/
# Apply stricter standards to new or somewhat refactored code.
gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\