Compare commits

..

12 Commits

Author SHA1 Message Date
Ainar Garipov
7ade25d227 websvc: imp restart 2022-09-30 15:33:29 +03:00
Ainar Garipov
b448a3b5dc Merge branch 'master' into websvc-confin-manager 2022-09-30 15:27:54 +03:00
Ainar Garipov
c20ca9b85e all: add tests; imp errors 2022-09-12 19:28:26 +03:00
Ainar Garipov
dffffec9d4 all: imp response; fmt 2022-09-12 16:01:31 +03:00
Ainar Garipov
1989c91c07 websvc: imp tests 2022-09-09 15:05:33 +03:00
Ainar Garipov
dbfc8ae362 all: mv v1 to next; imp tests, docs 2022-09-09 14:25:48 +03:00
Ainar Garipov
d74ba3cb9d Merge branch 'master' into websvc-confin-manager 2022-09-09 13:44:01 +03:00
Ainar Garipov
abcbdbed29 websvc: add test; imp names, docs 2022-09-02 18:52:22 +03:00
Ainar Garipov
8a65848da4 Merge branch 'master' into websvc-confin-manager 2022-09-02 17:38:22 +03:00
Ainar Garipov
b018e150e7 websvc: add tests; imp names 2022-08-31 19:11:00 +03:00
Ainar Garipov
dbdae5b4fc Merge branch 'master' into websvc-confin-manager 2022-08-31 19:09:48 +03:00
Ainar Garipov
27bd8bc58b websvc: add dns and http apis 2022-08-31 17:53:45 +03:00
40 changed files with 745 additions and 988 deletions

View File

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

View File

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

View File

@@ -15,47 +15,6 @@ and this project adheres to
## [v0.108.0] - TBA (APPROX.) ## [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 ### Security
- As an additional CSRF protection measure, AdGuard Home now ensures that - As an additional CSRF protection measure, AdGuard Home now ensures that
@@ -63,34 +22,21 @@ See also the [v0.107.15 GitHub milestone][ms-v0.107.15].
/control/stats_reset` requests) do not have a `Content-Type` header set on /control/stats_reset` requests) do not have a `Content-Type` header set on
them ([#4970]). 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 ### Fixed
- User-specific blocked services not applying correctly ([#4945], [#4982],
[#4983]).
- `only application/json is allowed` errors in various APIs ([#4970]). - `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 [#4970]: https://github.com/AdguardTeam/AdGuardHome/issues/4970
[#4982]: https://github.com/AdguardTeam/AdGuardHome/issues/4982
[#4983]: https://github.com/AdguardTeam/AdGuardHome/issues/4983
<!--
## [v0.107.15] - 2022-10-26 (APPROX.)
See also the [v0.107.15 GitHub milestone][ms-v0.107.15].
[ms-v0.107.15]: https://github.com/AdguardTeam/AdGuardHome/milestone/51?closed=1 [ms-v0.107.15]: https://github.com/AdguardTeam/AdGuardHome/milestone/51?closed=1
-->
@@ -188,7 +134,7 @@ See also the [v0.107.12 GitHub milestone][ms-v0.107.12].
### Security ### Security
- Go version has been updated to prevent the possibility of exploiting the - Go version was updated to prevent the possibility of exploiting the
CVE-2022-27664 and CVE-2022-32190 Go vulnerabilities fixed in CVE-2022-27664 and CVE-2022-32190 Go vulnerabilities fixed in
[Go 1.18.6][go-1.18.6]. [Go 1.18.6][go-1.18.6].
@@ -309,7 +255,7 @@ See also the [v0.107.9 GitHub milestone][ms-v0.107.9].
### Security ### Security
- Go version has been updated to prevent the possibility of exploiting the - Go version was updated to prevent the possibility of exploiting the
CVE-2022-32189 Go vulnerability fixed in [Go 1.18.5][go-1.18.5]. Go 1.17 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 support has also been removed, as it has reached end of life and will not
receive security updates. receive security updates.
@@ -352,7 +298,7 @@ See also the [v0.107.8 GitHub milestone][ms-v0.107.8].
### Security ### Security
- Go version has been updated to prevent the possibility of exploiting the - Go version was updated to prevent the possibility of exploiting the
CVE-2022-1705, CVE-2022-32148, CVE-2022-30631, and other Go vulnerabilities CVE-2022-1705, CVE-2022-32148, CVE-2022-30631, and other Go vulnerabilities
fixed in [Go 1.17.12][go-1.17.12]. fixed in [Go 1.17.12][go-1.17.12].
@@ -388,7 +334,7 @@ See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
### Security ### Security
- Go version has been updated to prevent the possibility of exploiting the - Go version was updated to prevent the possibility of exploiting the
[CVE-2022-29526], [CVE-2022-30634], [CVE-2022-30629], [CVE-2022-30580], and [CVE-2022-29526], [CVE-2022-30634], [CVE-2022-30629], [CVE-2022-30580], and
[CVE-2022-29804] Go vulnerabilities. [CVE-2022-29804] Go vulnerabilities.
- Enforced password strength policy ([#3503]). - Enforced password strength policy ([#3503]).
@@ -545,7 +491,7 @@ See also the [v0.107.6 GitHub milestone][ms-v0.107.6].
### Security ### Security
- `User-Agent` HTTP header removed from outgoing DNS-over-HTTPS requests. - `User-Agent` HTTP header removed from outgoing DNS-over-HTTPS requests.
- Go version has been updated to prevent the possibility of exploiting the - Go version was updated to prevent the possibility of exploiting the
[CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] Go vulnerabilities. [CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] Go vulnerabilities.
### Added ### Added
@@ -600,7 +546,7 @@ were resolved.
### Security ### Security
- Go version has been updated to prevent the possibility of exploiting the - Go version was updated to prevent the possibility of exploiting the
[CVE-2022-24921] Go vulnerability. [CVE-2022-24921] Go vulnerability.
[CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921 [CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921
@@ -613,7 +559,7 @@ See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
### Security ### Security
- Go version has been updated to prevent the possibility of exploiting the - Go version was updated to prevent the possibility of exploiting the
[CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] Go vulnerabilities. [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] Go vulnerabilities.
### Fixed ### Fixed
@@ -1350,13 +1296,11 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!-- <!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.15...HEAD
[v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...v0.107.17 [v0.107.15]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...v0.107.15
--> -->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...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.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.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 [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. # Make sure to sync any changes with the branch overrides below.
'variables': 'variables':
'channel': 'edge' 'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:5.2' 'dockerGo': 'adguard/golang-ubuntu:5.1'
'stages': 'stages':
- 'Build frontend': - 'Build frontend':
@@ -322,7 +322,7 @@
# need to build a few of these. # need to build a few of these.
'variables': 'variables':
'channel': 'beta' 'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:5.2' 'dockerGo': 'adguard/golang-ubuntu:5.1'
# release-vX.Y.Z branches are the branches from which the actual final release # release-vX.Y.Z branches are the branches from which the actual final release
# is built. # is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+': - '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -337,4 +337,4 @@
# are the ones that actually get released. # are the ones that actually get released.
'variables': 'variables':
'channel': 'release' 'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:5.2' 'dockerGo': 'adguard/golang-ubuntu:5.1'

View File

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

View File

@@ -215,7 +215,6 @@
"example_upstream_udp": "regular DNS (over UDP, hostname);", "example_upstream_udp": "regular DNS (over UDP, hostname);",
"example_upstream_dot": "encrypted <0>DNS-over-TLS</0>;", "example_upstream_dot": "encrypted <0>DNS-over-TLS</0>;",
"example_upstream_doh": "encrypted <0>DNS-over-HTTPS</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_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_sdns": "<0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers;",
"example_upstream_tcp": "regular DNS (over TCP);", "example_upstream_tcp": "regular DNS (over TCP);",
@@ -606,7 +605,7 @@
"blocklist": "Blocklist", "blocklist": "Blocklist",
"milliseconds_abbreviation": "ms", "milliseconds_abbreviation": "ms",
"cache_size": "Cache size", "cache_size": "Cache size",
"cache_size_desc": "DNS cache size (in bytes). To disable caching, leave empty.", "cache_size_desc": "DNS cache size (in bytes).",
"cache_ttl_min_override": "Override minimum TTL", "cache_ttl_min_override": "Override minimum TTL",
"cache_ttl_max_override": "Override maximum TTL", "cache_ttl_max_override": "Override maximum TTL",
"enter_cache_size": "Enter cache size (bytes)", "enter_cache_size": "Enter cache size (bytes)",

View File

@@ -57,22 +57,6 @@ const Examples = (props) => (
example_upstream_doh example_upstream_doh
</Trans> </Trans>
</li> </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> <li>
<code>quic://unfiltered.adguard-dns.com</code>: <Trans <code>quic://unfiltered.adguard-dns.com</code>: <Trans
components={[ components={[

16
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
go 1.18 go 1.18
require ( require (
github.com/AdguardTeam/dnsproxy v0.45.2 github.com/AdguardTeam/dnsproxy v0.44.0
github.com/AdguardTeam/golibs v0.10.9 github.com/AdguardTeam/golibs v0.10.9
github.com/AdguardTeam/urlfilter v0.16.0 github.com/AdguardTeam/urlfilter v0.16.0
github.com/NYTimes/gziphandler v1.1.1 github.com/NYTimes/gziphandler v1.1.1
@@ -18,7 +18,7 @@ require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/insomniacslk/dhcp v0.0.0-20220822114210-de18a9d48e84 github.com/insomniacslk/dhcp v0.0.0-20220822114210-de18a9d48e84
github.com/kardianos/service v1.2.1 github.com/kardianos/service v1.2.1
github.com/lucas-clemente/quic-go v0.29.1 github.com/lucas-clemente/quic-go v0.29.0
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
github.com/mdlayher/netlink v1.6.0 github.com/mdlayher/netlink v1.6.0
// TODO(a.garipov): This package is deprecated; find a new one or use // 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/stretchr/testify v1.8.0
github.com/ti-mo/netfilter v0.4.0 github.com/ti-mo/netfilter v0.4.0
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9 golang.org/x/exp v0.0.0-20220827204233-334a2380cb91
golang.org/x/net v0.0.0-20220927171203-f486391704dc golang.org/x/net v0.0.0-20220906165146-f3363e06e74c
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.0 howett.net/plist v1.0.0
@@ -43,12 +43,10 @@ require (
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // 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/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/josharian/native v1.0.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-18 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
github.com/mdlayher/packet v1.0.0 // indirect github.com/mdlayher/packet v1.0.0 // indirect
@@ -59,7 +57,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
golang.org/x/mod v0.6.0-dev.0.20220922195421-2adab6b8c60e // indirect golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 // indirect
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.1.12 // indirect

35
go.sum
View File

@@ -1,5 +1,5 @@
github.com/AdguardTeam/dnsproxy v0.45.2 h1:K9BXkQAfAKjrzbWbczpA2IA1owLe/edv0nG0e2+Esko= github.com/AdguardTeam/dnsproxy v0.44.0 h1:JzIxEXF4OyJq4wZVHeZkM1af4VfuwcgrUzjgdBGljsE=
github.com/AdguardTeam/dnsproxy v0.45.2/go.mod h1:h+0r4GDvHHY2Wu6r7oqva+O37h00KofYysfzy1TEXFE= github.com/AdguardTeam/dnsproxy v0.44.0/go.mod h1:HsxYYW/bC8uo+4eX9pRW21hFD6gWZdrvcfBb1R6/AzU=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.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.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
github.com/AdguardTeam/golibs v0.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0= github.com/AdguardTeam/golibs v0.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0=
@@ -23,8 +23,6 @@ github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1O
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/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 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= 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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -90,10 +88,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucas-clemente/quic-go v0.29.1 h1:Z+WMJ++qMLhvpFkRZA+jl3BTxUjm415YBmWanXB8zP0= github.com/lucas-clemente/quic-go v0.29.0 h1:Vw0mGTfmWqGzh4jx/kMymsIkFK6rErFVmg+t9RLrnZE=
github.com/lucas-clemente/quic-go v0.29.1/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE= github.com/lucas-clemente/quic-go v0.29.0/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 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= 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= github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
@@ -129,7 +125,6 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/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.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.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 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 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= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -173,16 +168,16 @@ go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-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-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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/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-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/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.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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220922195421-2adab6b8c60e h1:WhB000cGjOfbJiedMGvJkMTclI18VD69w27k+sceql8= golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 h1:VtCrPQXM5Wo9l7XN64SjBMczl48j8mkP+2e3OhYlz+0=
golang.org/x/mod v0.6.0-dev.0.20220922195421-2adab6b8c60e/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/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-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-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -194,7 +189,6 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-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-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-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-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-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -206,8 +200,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-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-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-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ= golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo=
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -230,7 +224,6 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-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-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-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-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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -254,8 +247,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-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-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-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77 h1:C1tElbkWrsSkn3IRl1GCW/gETw1TywWIPgwZtXTZbYg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -62,16 +62,9 @@ func WriteTextPlainDeprecated(w http.ResponseWriter, r *http.Request) (isPlainTe
} }
// WriteJSONResponse sets the content-type header in w.Header() to // WriteJSONResponse sets the content-type header in w.Header() to
// "application/json", writes a header with a "200 OK" status, encodes resp to // "application/json", encodes resp to w, calls Error on any returned error, and
// w, calls [Error] on any returned error, and returns it as well. // returns it as well.
func WriteJSONResponse(w http.ResponseWriter, r *http.Request, resp any) (err error) { 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) w.Header().Set(HdrNameContentType, HdrValApplicationJSON)
err = json.NewEncoder(w).Encode(resp) err = json.NewEncoder(w).Encode(resp)
if err != nil { if err != nil {

View File

@@ -78,7 +78,18 @@ func (s *server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) {
status.Leases = s.Leases(LeasesDynamic) status.Leases = s.Leases(LeasesDynamic)
status.StaticLeases = s.Leases(LeasesStatic) status.StaticLeases = s.Leases(LeasesStatic)
_ = aghhttp.WriteJSONResponse(w, r, status) w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(status)
if err != nil {
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"Unable to marshal DHCP status json: %s",
err,
)
}
} }
func (s *server) enableDHCP(ifaceName string) (code int, err error) { func (s *server) enableDHCP(ifaceName string) (code int, err error) {
@@ -235,7 +246,22 @@ func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
return return
} }
s.setConfFromJSON(conf, srv4, srv6) if conf.Enabled != aghalg.NBNull {
s.conf.Enabled = conf.Enabled == aghalg.NBTrue
}
if conf.InterfaceName != "" {
s.conf.InterfaceName = conf.InterfaceName
}
if srv4 != nil {
s.srv4 = srv4
}
if srv6 != nil {
s.srv6 = srv6
}
s.conf.ConfigModified() s.conf.ConfigModified()
err = s.dbLoad() err = s.dbLoad()
@@ -254,26 +280,6 @@ func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
} }
} }
// setConfFromJSON sets configuration parameters in s from the new configuration
// decoded from JSON.
func (s *server) setConfFromJSON(conf *dhcpServerConfigJSON, srv4, srv6 DHCPServer) {
if conf.Enabled != aghalg.NBNull {
s.conf.Enabled = conf.Enabled == aghalg.NBTrue
}
if conf.InterfaceName != "" {
s.conf.InterfaceName = conf.InterfaceName
}
if srv4 != nil {
s.srv4 = srv4
}
if srv6 != nil {
s.srv6 = srv6
}
}
type netInterfaceJSON struct { type netInterfaceJSON struct {
Name string `json:"name"` Name string `json:"name"`
HardwareAddr string `json:"hardware_address"` HardwareAddr string `json:"hardware_address"`

View File

@@ -3,10 +3,11 @@
package dhcpd package dhcpd
import ( import (
"encoding/json"
"net/http" "net/http"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/log"
) )
// jsonError is a generic JSON error response. // jsonError is a generic JSON error response.
@@ -24,9 +25,15 @@ type jsonError struct {
// TODO(a.garipov): Either take the logger from the server after we've // TODO(a.garipov): Either take the logger from the server after we've
// refactored logging or make this not a method of *Server. // refactored logging or make this not a method of *Server.
func (s *server) notImplemented(w http.ResponseWriter, r *http.Request) { func (s *server) notImplemented(w http.ResponseWriter, r *http.Request) {
_ = aghhttp.WriteJSONResponseCode(w, r, http.StatusNotImplemented, &jsonError{ w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotImplemented)
err := json.NewEncoder(w).Encode(&jsonError{
Message: aghos.Unsupported("dhcp").Error(), 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 // registerHandlers sets the handlers for DHCP HTTP API that always respond with

View File

@@ -183,7 +183,15 @@ func (s *Server) accessListJSON() (j accessListJSON) {
} }
func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) { func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
_ = aghhttp.WriteJSONResponse(w, r, s.accessListJSON()) j := s.accessListJSON()
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(j)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "encoding response: %s", err)
return
}
} }
// validateAccessSet checks the internal accessListJSON lists. To search for // validateAccessSet checks the internal accessListJSON lists. To search for

View File

@@ -123,14 +123,7 @@ type quicConnection interface {
func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string, err error) { func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string, err error) {
proto := pctx.Proto proto := pctx.Proto
if proto == proxy.ProtoHTTPS { if proto == proxy.ProtoHTTPS {
clientID, err = clientIDFromDNSContextHTTPS(pctx) return 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 { } else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
return "", nil return "", nil
} }
@@ -140,9 +133,31 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
return "", nil return "", nil
} }
cliSrvName, err := clientServerName(pctx, proto) cliSrvName := ""
if err != nil { switch proto {
return "", err case proxy.ProtoTLS:
conn := pctx.Conn
tc, ok := conn.(tlsConn)
if !ok {
return "", fmt.Errorf(
"proxy ctx conn of proto %s is %T, want *tls.Conn",
proto,
conn,
)
}
cliSrvName = tc.ConnectionState().ServerName
case proxy.ProtoQUIC:
conn, ok := pctx.QUICConnection.(quicConnection)
if !ok {
return "", fmt.Errorf(
"proxy ctx quic conn of proto %s is %T, want quic.Connection",
proto,
pctx.QUICConnection,
)
}
cliSrvName = conn.ConnectionState().TLS.ServerName
} }
clientID, err = clientIDFromClientServerName( clientID, err = clientIDFromClientServerName(
@@ -156,35 +171,3 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
return clientID, nil 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,22 +160,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "insensitive", wantClientID: "insensitive",
wantErrMsg: ``, wantErrMsg: ``,
strictSNI: true, 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 { for _, tc := range testCases {
@@ -189,32 +173,16 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
conf: ServerConfig{TLSConfig: tlsConf}, conf: ServerConfig{TLSConfig: tlsConf},
} }
var ( var conn net.Conn
conn net.Conn if tc.proto == proxy.ProtoTLS {
qconn quic.Connection conn = testTLSConn{
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, serverName: tc.cliSrvName,
} }
case proxy.ProtoTLS: }
conn = testTLSConn{
var qconn quic.Connection
if tc.proto == proxy.ProtoQUIC {
qconn = testQUICConnection{
serverName: tc.cliSrvName, serverName: tc.cliSrvName,
} }
} }
@@ -222,7 +190,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
pctx := &proxy.DNSContext{ pctx := &proxy.DNSContext{
Proto: tc.proto, Proto: tc.proto,
Conn: conn, Conn: conn,
HTTPRequest: httpReq,
QUICConnection: qconn, QUICConnection: qconn,
} }
@@ -238,76 +205,56 @@ func TestClientIDFromDNSContextHTTPS(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
path string path string
cliSrvName string
wantClientID string wantClientID string
wantErrMsg string wantErrMsg string
}{{ }{{
name: "no_clientid", name: "no_clientid",
path: "/dns-query", path: "/dns-query",
cliSrvName: "example.com",
wantClientID: "", wantClientID: "",
wantErrMsg: "", wantErrMsg: "",
}, { }, {
name: "no_clientid_slash", name: "no_clientid_slash",
path: "/dns-query/", path: "/dns-query/",
cliSrvName: "example.com",
wantClientID: "", wantClientID: "",
wantErrMsg: "", wantErrMsg: "",
}, { }, {
name: "clientid", name: "clientid",
path: "/dns-query/cli", path: "/dns-query/cli",
cliSrvName: "example.com",
wantClientID: "cli", wantClientID: "cli",
wantErrMsg: "", wantErrMsg: "",
}, { }, {
name: "clientid_slash", name: "clientid_slash",
path: "/dns-query/cli/", path: "/dns-query/cli/",
cliSrvName: "example.com",
wantClientID: "cli", wantClientID: "cli",
wantErrMsg: "", wantErrMsg: "",
}, { }, {
name: "clientid_case", name: "clientid_case",
path: "/dns-query/InSeNsItIvE", path: "/dns-query/InSeNsItIvE",
cliSrvName: "example.com",
wantClientID: "insensitive", wantClientID: "insensitive",
wantErrMsg: ``, wantErrMsg: ``,
}, { }, {
name: "bad_url", name: "bad_url",
path: "/foo", path: "/foo",
cliSrvName: "example.com",
wantClientID: "", wantClientID: "",
wantErrMsg: `clientid check: invalid path "/foo"`, wantErrMsg: `clientid check: invalid path "/foo"`,
}, { }, {
name: "extra", name: "extra",
path: "/dns-query/cli/foo", path: "/dns-query/cli/foo",
cliSrvName: "example.com",
wantClientID: "", wantClientID: "",
wantErrMsg: `clientid check: invalid path "/dns-query/cli/foo": extra parts`, wantErrMsg: `clientid check: invalid path "/dns-query/cli/foo": extra parts`,
}, { }, {
name: "invalid_clientid", name: "invalid_clientid",
path: "/dns-query/!!!", path: "/dns-query/!!!",
cliSrvName: "example.com",
wantClientID: "", wantClientID: "",
wantErrMsg: `clientid check: invalid clientid "!!!": bad domain name label rune '!'`, 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 { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
connState := &tls.ConnectionState{
ServerName: tc.cliSrvName,
}
r := &http.Request{ r := &http.Request{
URL: &url.URL{ URL: &url.URL{
Path: tc.path, Path: tc.path,
}, },
TLS: connState,
} }
pctx := &proxy.DNSContext{ pctx := &proxy.DNSContext{

View File

@@ -201,10 +201,6 @@ type ServerConfig struct {
// Register an HTTP handler // Register an HTTP handler
HTTPRegister aghhttp.RegisterFunc 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 signals if the RDNS should resolve clients' addresses.
ResolveClients bool ResolveClients bool
@@ -212,12 +208,9 @@ type ServerConfig struct {
// locally-served networks should be resolved via private PTR resolvers. // locally-served networks should be resolved via private PTR resolvers.
UsePrivateRDNS bool UsePrivateRDNS bool
// ServeHTTP3 defines if HTTP/3 is be allowed for incoming requests. // LocalPTRResolvers is a slice of addresses to be used as upstreams for
ServeHTTP3 bool // resolving PTR queries for local addresses.
LocalPTRResolvers []string
// 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 // if any of ServerConfig values are zero, then default values from below are used
@@ -233,7 +226,6 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
conf = proxy.Config{ conf = proxy.Config{
UDPListenAddr: srvConf.UDPListenAddrs, UDPListenAddr: srvConf.UDPListenAddrs,
TCPListenAddr: srvConf.TCPListenAddrs, TCPListenAddr: srvConf.TCPListenAddrs,
HTTP3: srvConf.ServeHTTP3,
Ratelimit: int(srvConf.Ratelimit), Ratelimit: int(srvConf.Ratelimit),
RatelimitWhitelist: srvConf.RatelimitWhitelist, RatelimitWhitelist: srvConf.RatelimitWhitelist,
RefuseAny: srvConf.RefuseAny, RefuseAny: srvConf.RefuseAny,
@@ -332,20 +324,6 @@ func (s *Server) initDefaultSettings() {
} }
} }
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
// depending on configuration.
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
if !http3 {
return upstream.DefaultHTTPVersions
}
return []upstream.HTTPVersion{
upstream.HTTPVersion3,
upstream.HTTPVersion2,
upstream.HTTPVersion11,
}
}
// prepareUpstreamSettings - prepares upstream DNS server settings // prepareUpstreamSettings - prepares upstream DNS server settings
func (s *Server) prepareUpstreamSettings() error { func (s *Server) prepareUpstreamSettings() error {
// We're setting a customized set of RootCAs // We're setting a customized set of RootCAs
@@ -375,14 +353,12 @@ func (s *Server) prepareUpstreamSettings() error {
upstreams = s.conf.UpstreamDNS upstreams = s.conf.UpstreamDNS
} }
httpVersions := UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams)
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty) upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
upstreamConfig, err := proxy.ParseUpstreamsConfig( upstreamConfig, err := proxy.ParseUpstreamsConfig(
upstreams, upstreams,
&upstream.Options{ &upstream.Options{
Bootstrap: s.conf.BootstrapDNS, Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout, Timeout: s.conf.UpstreamTimeout,
HTTPVersions: httpVersions,
}, },
) )
if err != nil { if err != nil {
@@ -397,7 +373,6 @@ func (s *Server) prepareUpstreamSettings() error {
&upstream.Options{ &upstream.Options{
Bootstrap: s.conf.BootstrapDNS, Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout, Timeout: s.conf.UpstreamTimeout,
HTTPVersions: httpVersions,
}, },
) )
if err != nil { 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 // filterDNSResponse checks each resource record of the response's answer
// section from pctx and returns a non-nil res if at least one of canonical // section from pctx and returns a non-nil res if at least one of canonnical
// names or IP addresses in it matches the filtering rules. // names or IP addresses in it matches the filtering rules.
func (s *Server) filterDNSResponse( func (s *Server) filterDNSResponse(
pctx *proxy.DNSContext, pctx *proxy.DNSContext,

View File

@@ -112,7 +112,13 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
DefautLocalPTRUpstreams: defLocalPTRUps, DefautLocalPTRUpstreams: defLocalPTRUps,
} }
_ = aghhttp.WriteJSONResponse(w, r, resp) w.Header().Set("Content-Type", "application/json")
if err = json.NewEncoder(w).Encode(resp); err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "json.Encoder: %s", err)
return
}
} }
func (req *jsonDNSConfig) checkBlockingMode() (err error) { func (req *jsonDNSConfig) checkBlockingMode() (err error) {
@@ -343,10 +349,7 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro
conf, err = proxy.ParseUpstreamsConfig( conf, err = proxy.ParseUpstreamsConfig(
upstreams, upstreams,
&upstream.Options{ &upstream.Options{Bootstrap: []string{}, Timeout: DefaultTimeout},
Bootstrap: []string{},
Timeout: DefaultTimeout,
},
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -409,15 +412,7 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet)
return nil return nil
} }
var protocols = []string{ var protocols = []string{"udp://", "tcp://", "tls://", "https://", "sdns://", "quic://"}
"h3://",
"https://",
"quic://",
"sdns://",
"tcp://",
"tls://",
"udp://",
}
// validateUpstream returns an error if u alongside with domains is not a valid // validateUpstream returns an error if u alongside with domains is not a valid
// upstream configuration. useDefault is true if the upstream is // upstream configuration. useDefault is true if the upstream is
@@ -664,7 +659,24 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
result[host] = "OK" result[host] = "OK"
} }
_ = aghhttp.WriteJSONResponse(w, r, result) jsonVal, err := json.Marshal(result)
if err != nil {
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"Unable to marshal status json: %s",
err,
)
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonVal)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
}
} }
// handleDoH is the DNS-over-HTTPs handler. // handleDoH is the DNS-over-HTTPs handler.
@@ -680,13 +692,11 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleDoH(w http.ResponseWriter, r *http.Request) { func (s *Server) handleDoH(w http.ResponseWriter, r *http.Request) {
if !s.conf.TLSAllowUnencryptedDoH && r.TLS == nil { if !s.conf.TLSAllowUnencryptedDoH && r.TLS == nil {
aghhttp.Error(r, w, http.StatusNotFound, "Not Found") aghhttp.Error(r, w, http.StatusNotFound, "Not Found")
return return
} }
if !s.IsRunning() { if !s.IsRunning() {
aghhttp.Error(r, w, http.StatusInternalServerError, "dns server is not running") aghhttp.Error(r, w, http.StatusInternalServerError, "dns server is not running")
return return
} }

View File

@@ -12,7 +12,6 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
@@ -117,8 +116,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
s.conf = tc.conf() s.conf = tc.conf()
s.handleGetConfig(w, nil) s.handleGetConfig(w, nil)
cType := w.Header().Get(aghhttp.HdrNameContentType) assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
assert.Equal(t, aghhttp.HdrValApplicationJSON, cType)
assert.JSONEq(t, string(caseWant), w.Body.String()) assert.JSONEq(t, string(caseWant), w.Body.String())
}) })
} }

View File

@@ -453,7 +453,13 @@ func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string) {
} }
func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) {
_ = aghhttp.WriteJSONResponse(w, r, serviceIDs) w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(serviceIDs)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "encoding available services: %s", err)
return
}
} }
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
@@ -461,7 +467,13 @@ func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Req
list := d.Config.BlockedServices list := d.Config.BlockedServices
d.confLock.RUnlock() d.confLock.RUnlock()
_ = aghhttp.WriteJSONResponse(w, r, list) w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(list)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "encoding services: %s", err)
return
}
} }
func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {

View File

@@ -301,7 +301,14 @@ func (d *DNSFilter) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
return return
} }
_ = aghhttp.WriteJSONResponse(w, r, resp) w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(resp)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
return
}
} }
type filterJSON struct { type filterJSON struct {
@@ -354,7 +361,17 @@ func (d *DNSFilter) handleFilteringStatus(w http.ResponseWriter, r *http.Request
resp.UserRules = d.UserRules resp.UserRules = d.UserRules
d.filtersMu.RUnlock() d.filtersMu.RUnlock()
_ = aghhttp.WriteJSONResponse(w, r, resp) jsonVal, err := json.Marshal(resp)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonVal)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "http write: %s", err)
}
} }
// Set filtering configuration // Set filtering configuration
@@ -456,7 +473,11 @@ func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
} }
} }
_ = aghhttp.WriteJSONResponse(w, r, resp) w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(resp)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "encoding response: %s", err)
}
} }
// RegisterFilteringHandlers - register handlers // RegisterFilteringHandlers - register handlers

View File

@@ -240,7 +240,13 @@ func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
} }
d.confLock.Unlock() d.confLock.Unlock()
_ = aghhttp.WriteJSONResponse(w, r, arr) w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(arr)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "json.Encode: %s", err)
return
}
} }
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {

View File

@@ -5,6 +5,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@@ -380,13 +381,17 @@ func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Req
} }
func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
resp := &struct { w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(&struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
}{ }{
Enabled: d.Config.SafeBrowsingEnabled, Enabled: d.Config.SafeBrowsingEnabled,
} })
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
_ = aghhttp.WriteJSONResponse(w, r, resp) return
}
} }
func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
@@ -400,11 +405,13 @@ func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request
} }
func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
resp := &struct { w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(&struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
}{ }{
Enabled: d.Config.ParentalEnabled, 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,6 +5,7 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"encoding/gob" "encoding/gob"
"encoding/json"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@@ -145,13 +146,21 @@ func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Reque
} }
func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
resp := &struct { w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(&struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
}{ }{
Enabled: d.Config.SafeSearchEnabled, 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{ var safeSearchDomains = map[string]string{

View File

@@ -12,11 +12,16 @@ import (
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestMain(m *testing.M) {
aghtest.DiscardLogOutput(m)
}
func TestNewSessionToken(t *testing.T) { func TestNewSessionToken(t *testing.T) {
// Successful case. // Successful case.
token, err := newSessionToken() token, err := newSessionToken()

View File

@@ -458,7 +458,6 @@ func (clients *clientsContainer) findUpstreams(
&upstream.Options{ &upstream.Options{
Bootstrap: config.DNS.BootstrapDNS, Bootstrap: config.DNS.BootstrapDNS,
Timeout: config.DNS.UpstreamTimeout.Duration, Timeout: config.DNS.UpstreamTimeout.Duration,
HTTPVersions: dnsforward.UpstreamHTTPVersions(config.DNS.UseHTTP3Upstreams),
}, },
) )
if err != nil { if err != nil {

View File

@@ -166,19 +166,6 @@ type dnsConfig struct {
// LocalPTRResolvers is the slice of addresses to be used as upstreams // LocalPTRResolvers is the slice of addresses to be used as upstreams
// for PTR queries for locally-served networks. // for PTR queries for locally-served networks.
LocalPTRResolvers []string `yaml:"local_ptr_upstreams"` 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 { type tlsConfigSettings struct {

View File

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

View File

@@ -21,7 +21,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/lucas-clemente/quic-go/http3"
) )
// getAddrsResponse is the response for /install/get_addresses endpoint. // getAddrsResponse is the response for /install/get_addresses endpoint.
@@ -329,7 +328,6 @@ func copyInstallSettings(dst, src *configuration) {
// shutdownTimeout is the timeout for shutting HTTP server down operation. // shutdownTimeout is the timeout for shutting HTTP server down operation.
const shutdownTimeout = 5 * time.Second const shutdownTimeout = 5 * time.Second
// shutdownSrv shuts srv down and prints error messages to the log.
func shutdownSrv(ctx context.Context, srv *http.Server) { func shutdownSrv(ctx context.Context, srv *http.Server) {
defer log.OnPanic("") defer log.OnPanic("")
@@ -338,38 +336,13 @@ func shutdownSrv(ctx context.Context, srv *http.Server) {
} }
err := srv.Shutdown(ctx) err := srv.Shutdown(ctx)
if err == nil { if err != nil {
return
}
const msgFmt = "shutting down http server %q: %s" const msgFmt = "shutting down http server %q: %s"
if errors.Is(err, context.Canceled) { if errors.Is(err, context.Canceled) {
log.Debug(msgFmt, srv.Addr, err) log.Debug(msgFmt, srv.Addr, err)
} else { } else {
log.Error(msgFmt, srv.Addr, err) 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)
} }
} }
@@ -572,11 +545,16 @@ func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Requ
err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData) err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "encoding check_config: %s", err) aghhttp.Error(
r,
w,
http.StatusBadRequest,
"Failed to encode 'check_config' JSON data: %s",
err,
)
return return
} }
body := nonBetaReqBody.String() body := nonBetaReqBody.String()
r.Body = io.NopCloser(strings.NewReader(body)) r.Body = io.NopCloser(strings.NewReader(body))
r.ContentLength = int64(len(body)) r.ContentLength = int64(len(body))
@@ -644,7 +622,13 @@ func (web *Web) handleInstallConfigureBeta(w http.ResponseWriter, r *http.Reques
err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData) err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "encoding configure: %s", err) aghhttp.Error(
r,
w,
http.StatusBadRequest,
"Failed to encode 'check_config' JSON data: %s",
err,
)
return return
} }

View File

@@ -246,13 +246,10 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
newConf.FilterHandler = applyAdditionalFiltering newConf.FilterHandler = applyAdditionalFiltering
newConf.GetCustomUpstreamByClient = Context.clients.findUpstreams newConf.GetCustomUpstreamByClient = Context.clients.findUpstreams
newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
newConf.UpstreamTimeout = dnsConf.UpstreamTimeout.Duration
newConf.ResolveClients = config.Clients.Sources.RDNS newConf.ResolveClients = config.Clients.Sources.RDNS
newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS
newConf.ServeHTTP3 = dnsConf.ServeHTTP3 newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
newConf.UseHTTP3Upstreams = dnsConf.UseHTTP3Upstreams newConf.UpstreamTimeout = dnsConf.UpstreamTimeout.Duration
return newConf, nil return newConf, nil
} }
@@ -361,13 +358,7 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering
log.Debug("%s: using settings for client %q (%s; %q)", pref, c.Name, clientIP, clientID) log.Debug("%s: using settings for client %q (%s; %q)", pref, c.Name, clientIP, clientID)
if c.UseOwnBlockedServices { if c.UseOwnBlockedServices {
// TODO(e.burkov): Get rid of this crutch. Context.filters.ApplyBlockedServices(setts, c.BlockedServices)
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 setts.ClientName = c.Name

View File

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

View File

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

View File

@@ -5,60 +5,30 @@ import (
"net" "net"
"os" "os"
"strconv" "strconv"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
) )
// TODO(a.garipov): Replace with package flag. // options passed from command-line arguments
// options represents the command-line options.
type options struct { type options struct {
// confFilename is the path to the configuration file. verbose bool // is verbose logging enabled
confFilename string configFilename string // path to the config file
workDir string // path to the working directory where we will store the filters data and the querylog
bindHost net.IP // host address to bind HTTP server on
bindPort int // port to serve HTTP pages on
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
pidFile string // File name to save PID to
checkConfig bool // Check configuration and exit
disableUpdate bool // If set, don't check for updates
// workDir is the path to the working directory where AdGuard Home stores // service control action (see service.ControlAction array + "status" command)
// 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 serviceControlAction string
// bindHost is the address on which to serve the HTTP UI. // runningAsService flag is set to true when options are passed from the service runner
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 runningAsService bool
// glinetMode shows if the GL-Inet compatibility mode is enabled. glinetMode bool // Activate GL-Inet compatibility mode
glinetMode bool
// noEtcHosts flag should be provided when /etc/hosts file shouldn't be // noEtcHosts flag should be provided when /etc/hosts file shouldn't be
// used. // used.
@@ -69,85 +39,88 @@ type options struct {
localFrontend bool localFrontend bool
} }
// initCmdLineOpts completes initialization of the global command-line option // functions used for their side-effects
// slice. It must only be called once. type effect func() error
func initCmdLineOpts() {
// The --help option cannot be put directly into cmdLineOpts, because that type arg struct {
// causes initialization cycle due to printHelp referencing cmdLineOpts. description string // a short, English description of the argument
cmdLineOpts = append(cmdLineOpts, cmdLineOpt{ longName string // the name of the argument used after '--'
updateWithValue: nil, shortName string // the name of the argument used after '-'
updateNoValue: nil,
effect: func(o options, exec string) (effect, error) { // only one of updateWithValue, updateNoValue, and effect should be present
return func() error { printHelp(exec); exitWithError(); return nil }, nil
}, updateWithValue func(o options, v string) (options, error) // the mutator for arguments with parameters
serialize: func(o options) (val string, ok bool) { return "", false }, updateNoValue func(o options) (options, error) // the mutator for arguments without parameters
description: "Print this help.", effect func(o options, exec string) (f effect, err error) // the side-effect closure generator
longName: "help",
shortName: "", serialize func(o options) []string // the re-serialization function back to arguments (return nil for omit)
})
} }
// effect is the type for functions used for their side-effects. // {type}SliceOrNil functions check their parameter of type {type}
type effect func() (err error) // against its zero value and return nil if the parameter value is
// zero otherwise they return a string slice of the parameter
// cmdLineOpt contains the data for a single command-line option. Only one of func ipSliceOrNil(ip net.IP) []string {
// updateWithValue, updateNoValue, and effect must be present. if ip == nil {
type cmdLineOpt struct { return nil
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)
// 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
}
// 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 o.bindHost.String(), true return []string{ip.String()}
}, }
description: "Host address to bind HTTP server on.",
longName: "host", func stringSliceOrNil(s string) []string {
shortName: "h", if s == "" {
}, { return nil
updateWithValue: func(o options, v string) (options, error) { }
return []string{s}
}
func intSliceOrNil(i int) []string {
if i == 0 {
return nil
}
return []string{strconv.Itoa(i)}
}
func boolSliceOrNil(b bool) []string {
if b {
return []string{}
}
return nil
}
var args []arg
var configArg = arg{
"Path to the config file.",
"config", "c",
func(o options, v string) (options, error) { o.configFilename = v; return o, nil },
nil,
nil,
func(o options) []string { return stringSliceOrNil(o.configFilename) },
}
var workDirArg = arg{
"Path to the working directory.",
"work-dir", "w",
func(o options, v string) (options, error) { o.workDir = v; return o, nil }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.workDir) },
}
var hostArg = arg{
"Host address to bind HTTP server on.",
"host", "h",
func(o options, v string) (options, error) { o.bindHost = net.ParseIP(v); return o, nil }, nil, nil,
func(o options) []string { return ipSliceOrNil(o.bindHost) },
}
var portArg = arg{
"Port to serve HTTP pages on.",
"port", "p",
func(o options, v string) (options, error) {
var err error var err error
var p int var p int
minPort, maxPort := 0, 1<<16-1 minPort, maxPort := 0, 1<<16-1
@@ -158,119 +131,78 @@ var cmdLineOpts = []cmdLineOpt{{
} else { } else {
o.bindPort = p o.bindPort = p
} }
return o, err return o, err
}, }, nil, nil,
updateNoValue: nil, func(o options) []string { return intSliceOrNil(o.bindPort) },
effect: nil, }
serialize: func(o options) (val string, ok bool) {
if o.bindPort == 0 {
return "", false
}
return strconv.Itoa(o.bindPort), true var serviceArg = arg{
}, "Service control action: status, install, uninstall, start, stop, restart, reload (configuration).",
description: "Port to serve HTTP pages on.", "service", "s",
longName: "port", func(o options, v string) (options, error) {
shortName: "p",
}, {
updateWithValue: func(o options, v string) (options, error) {
o.serviceControlAction = v o.serviceControlAction = v
return o, nil return o, nil
}, }, nil, nil,
updateNoValue: nil, func(o options) []string { return stringSliceOrNil(o.serviceControlAction) },
effect: nil, }
serialize: func(o options) (val string, ok bool) {
return o.serviceControlAction, o.serviceControlAction != "" var logfileArg = arg{
}, "Path to log file. If empty: write to stdout; if 'syslog': write to system log.",
description: `Service control action: status, install (as a service), ` + "logfile", "l",
`uninstall (as a service), start, stop, restart, reload (configuration).`, func(o options, v string) (options, error) { o.logFile = v; return o, nil }, nil, nil,
longName: "service", func(o options) []string { return stringSliceOrNil(o.logFile) },
shortName: "s", }
}, {
updateWithValue: func(o options, v string) (options, error) { o.logFile = v; return o, nil }, var pidfileArg = arg{
updateNoValue: nil, "Path to a file where PID is stored.",
effect: nil, "pidfile", "",
serialize: func(o options) (val string, ok bool) { return o.logFile, o.logFile != "" }, func(o options, v string) (options, error) { o.pidFile = v; return o, nil }, nil, nil,
description: `Path to log file. If empty, write to stdout; ` + func(o options) []string { return stringSliceOrNil(o.pidFile) },
`if "syslog", write to system log.`, }
longName: "logfile",
shortName: "l", var checkConfigArg = arg{
}, { "Check configuration and exit.",
updateWithValue: func(o options, v string) (options, error) { o.pidFile = v; return o, nil }, "check-config", "",
updateNoValue: nil, nil, func(o options) (options, error) { o.checkConfig = true; return o, nil }, nil,
effect: nil, func(o options) []string { return boolSliceOrNil(o.checkConfig) },
serialize: func(o options) (val string, ok bool) { return o.pidFile, o.pidFile != "" }, }
description: "Path to a file where PID is stored.",
longName: "pidfile", var noCheckUpdateArg = arg{
shortName: "", "Don't check for updates.",
}, { "no-check-update", "",
updateWithValue: nil, nil, func(o options) (options, error) { o.disableUpdate = true; return o, nil }, nil,
updateNoValue: func(o options) (options, error) { o.checkConfig = true; return o, nil }, func(o options) []string { return boolSliceOrNil(o.disableUpdate) },
effect: nil, }
serialize: func(o options) (val string, ok bool) { return "", o.checkConfig },
description: "Check configuration and exit.", var disableMemoryOptimizationArg = arg{
longName: "check-config", "Deprecated. Disable memory optimization.",
shortName: "", "no-mem-optimization", "",
}, { nil, nil, func(_ options, _ string) (f effect, err error) {
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") log.Info("warning: using --no-mem-optimization flag has no effect and is deprecated")
return nil, nil return nil, nil
}, },
serialize: func(o options) (val string, ok bool) { return "", false }, func(o options) []string { return nil },
description: "Deprecated. Disable memory optimization.", }
longName: "no-mem-optimization",
shortName: "",
}, {
updateWithValue: nil,
updateNoValue: func(o options) (options, error) { o.noEtcHosts = true; return o, nil },
effect: func(_ options, _ string) (f effect, err error) {
log.Info(
"warning: --no-etc-hosts flag is deprecated and will be removed in the future versions",
)
return nil, nil var verboseArg = arg{
}, "Enable verbose output.",
serialize: func(o options) (val string, ok bool) { return "", o.noEtcHosts }, "verbose", "v",
description: "Deprecated. Do not use the OS-provided hosts.", nil, func(o options) (options, error) { o.verbose = true; return o, nil }, nil,
longName: "no-etc-hosts", 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: "", shortName: "",
}, {
updateWithValue: nil,
updateNoValue: func(o options) (options, error) { o.localFrontend = true; return o, nil },
effect: nil,
serialize: func(o options) (val string, ok bool) { return "", o.localFrontend },
description: "Use local frontend directories.",
longName: "local-frontend",
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, updateWithValue: nil,
updateNoValue: nil, updateNoValue: nil,
effect: func(o options, exec string) (effect, error) { effect: func(o options, exec string) (effect, error) {
@@ -280,178 +212,176 @@ var cmdLineOpts = []cmdLineOpt{{
} else { } else {
fmt.Println(version.Full()) fmt.Println(version.Full())
} }
os.Exit(0) os.Exit(0)
return nil return nil
}, nil }, nil
}, },
serialize: func(o options) (val string, ok bool) { return "", false }, serialize: func(o options) []string { return nil },
description: "Show the version and exit. Show more detailed version description with -v.", }
longName: "version",
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: "", shortName: "",
}} updateWithValue: nil,
updateNoValue: func(o options) (options, error) { o.noEtcHosts = true; return o, nil },
// printHelp prints the entire help message. It exits with an error code if effect: func(_ options, _ string) (f effect, err error) {
// there are any I/O errors. log.Info(
func printHelp(exec string) { "warning: --no-etc-hosts flag is deprecated and will be removed in the future versions",
b := &strings.Builder{}
stringutil.WriteToBuilder(
b,
"Usage:\n\n",
fmt.Sprintf("%s [options]\n\n", exec),
"Options:\n",
) )
var err error return nil, nil
for _, opt := range cmdLineOpts { },
serialize: func(o options) []string { return boolSliceOrNil(o.noEtcHosts) },
}
var localFrontendArg = arg{
description: "Use local frontend directories.",
longName: "local-frontend",
shortName: "",
updateWithValue: nil,
updateNoValue: func(o options) (options, error) { o.localFrontend = true; return o, nil },
effect: nil,
serialize: func(o options) []string { return boolSliceOrNil(o.localFrontend) },
}
func init() {
args = []arg{
configArg,
workDirArg,
hostArg,
portArg,
serviceArg,
logfileArg,
pidfileArg,
checkConfigArg,
noCheckUpdateArg,
disableMemoryOptimizationArg,
noEtcHostsArg,
localFrontendArg,
verboseArg,
glinetArg,
versionArg,
helpArg,
}
}
func getUsageLines(exec string, args []arg) []string {
usage := []string{
"Usage:",
"",
fmt.Sprintf("%s [options]", exec),
"",
"Options:",
}
for _, arg := range args {
val := "" val := ""
if opt.updateWithValue != nil { if arg.updateWithValue != nil {
val = " VALUE" val = " VALUE"
} }
if arg.shortName != "" {
longDesc := opt.longName + val usage = append(usage, fmt.Sprintf(" -%s, %-30s %s",
if opt.shortName != "" { arg.shortName,
_, err = fmt.Fprintf(b, " -%s, --%-28s %s\n", opt.shortName, longDesc, opt.description) "--"+arg.longName+val,
arg.description))
} else { } else {
_, err = fmt.Fprintf(b, " --%-32s %s\n", longDesc, opt.description) usage = append(usage, fmt.Sprintf(" %-34s %s",
} "--"+arg.longName+val,
arg.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()
}
} }
// parseCmdOpts parses the command-line arguments into options and effects. func printHelp(exec string) error {
func parseCmdOpts(cmdName string, args []string) (o options, eff effect, err error) { for _, line := range getUsageLines(exec, args) {
// Don't use range since the loop changes the loop variable. _, err := fmt.Println(line)
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 { if err != nil {
return err return err
} }
return next()
} }
return nil
return eff
} }
// optsToArgs converts command line options into a list of arguments. func argMatches(a arg, v string) bool {
func optsToArgs(o options) (args []string) { return v == "--"+a.longName || (a.shortName != "" && v == "-"+a.shortName)
for _, opt := range cmdLineOpts { }
val, ok := opt.serialize(o)
if !ok { func parse(exec string, ss []string) (o options, f effect, err error) {
continue for i := 0; i < len(ss); i++ {
} v := ss[i]
knownParam := false
if opt.shortName != "" { for _, arg := range args {
args = append(args, "-"+opt.shortName) if argMatches(arg, v) {
} else { if arg.updateWithValue != nil {
args = append(args, "--"+opt.longName) if i+1 >= len(ss) {
} return o, f, fmt.Errorf("got %s without argument", v)
}
if val != "" { i++
args = append(args, val) o, err = arg.updateWithValue(o, ss[i])
} if err != nil {
} return
}
return args } else if arg.updateNoValue != nil {
o, err = arg.updateNoValue(o)
if err != nil {
return
}
} else if arg.effect != nil {
var eff effect
eff, err = arg.effect(o, exec)
if err != nil {
return
}
if eff != nil {
prevf := f
f = func() (ferr error) {
if prevf != nil {
ferr = prevf()
}
if ferr == nil {
ferr = eff()
}
return ferr
}
}
}
knownParam = true
break
}
}
if !knownParam {
return o, f, fmt.Errorf("unknown option %v", v)
}
}
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
} }

View File

@@ -12,7 +12,7 @@ import (
func testParseOK(t *testing.T, ss ...string) options { func testParseOK(t *testing.T, ss ...string) options {
t.Helper() t.Helper()
o, _, err := parseCmdOpts("", ss) o, _, err := parse("", ss)
require.NoError(t, err) require.NoError(t, err)
return o return o
@@ -21,7 +21,7 @@ func testParseOK(t *testing.T, ss ...string) options {
func testParseErr(t *testing.T, descr string, ss ...string) { func testParseErr(t *testing.T, descr string, ss ...string) {
t.Helper() t.Helper()
_, _, err := parseCmdOpts("", ss) _, _, err := parse("", ss)
require.Error(t, err) require.Error(t, err)
} }
@@ -38,11 +38,11 @@ func TestParseVerbose(t *testing.T) {
} }
func TestParseConfigFilename(t *testing.T) { func TestParseConfigFilename(t *testing.T) {
assert.Equal(t, "", testParseOK(t).confFilename, "empty is no config filename") assert.Equal(t, "", testParseOK(t).configFilename, "empty is no config filename")
assert.Equal(t, "path", testParseOK(t, "-c", "path").confFilename, "-c is config filename") assert.Equal(t, "path", testParseOK(t, "-c", "path").configFilename, "-c is config filename")
testParseParamMissing(t, "-c") testParseParamMissing(t, "-c")
assert.Equal(t, "path", testParseOK(t, "--config", "path").confFilename, "--config is config filename") assert.Equal(t, "path", testParseOK(t, "--config", "path").configFilename, "--config is config filename")
testParseParamMissing(t, "--config") testParseParamMissing(t, "--config")
} }
@@ -103,7 +103,7 @@ func TestParseDisableUpdate(t *testing.T) {
// TODO(e.burkov): Remove after v0.108.0. // TODO(e.burkov): Remove after v0.108.0.
func TestParseDisableMemoryOptimization(t *testing.T) { func TestParseDisableMemoryOptimization(t *testing.T) {
o, eff, err := parseCmdOpts("", []string{"--no-mem-optimization"}) o, eff, err := parse("", []string{"--no-mem-optimization"})
require.NoError(t, err) require.NoError(t, err)
assert.Nil(t, eff) assert.Nil(t, eff)
@@ -130,73 +130,73 @@ func TestParseUnknown(t *testing.T) {
testParseErr(t, "unknown dash", "-") testParseErr(t, "unknown dash", "-")
} }
func TestOptsToArgs(t *testing.T) { func TestSerialize(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
args []string
opts options opts options
ss []string
}{{ }{{
name: "empty", name: "empty",
args: []string{},
opts: options{}, opts: options{},
ss: []string{},
}, { }, {
name: "config_filename", name: "config_filename",
args: []string{"-c", "path"}, opts: options{configFilename: "path"},
opts: options{confFilename: "path"}, ss: []string{"-c", "path"},
}, { }, {
name: "work_dir", name: "work_dir",
args: []string{"-w", "path"},
opts: options{workDir: "path"}, opts: options{workDir: "path"},
ss: []string{"-w", "path"},
}, { }, {
name: "bind_host", name: "bind_host",
args: []string{"-h", "1.2.3.4"},
opts: options{bindHost: net.IP{1, 2, 3, 4}}, opts: options{bindHost: net.IP{1, 2, 3, 4}},
ss: []string{"-h", "1.2.3.4"},
}, { }, {
name: "bind_port", name: "bind_port",
args: []string{"-p", "666"},
opts: options{bindPort: 666}, opts: options{bindPort: 666},
ss: []string{"-p", "666"},
}, { }, {
name: "log_file", name: "log_file",
args: []string{"-l", "path"},
opts: options{logFile: "path"}, opts: options{logFile: "path"},
ss: []string{"-l", "path"},
}, { }, {
name: "pid_file", name: "pid_file",
args: []string{"--pidfile", "path"},
opts: options{pidFile: "path"}, opts: options{pidFile: "path"},
ss: []string{"--pidfile", "path"},
}, { }, {
name: "disable_update", name: "disable_update",
args: []string{"--no-check-update"},
opts: options{disableUpdate: true}, opts: options{disableUpdate: true},
ss: []string{"--no-check-update"},
}, { }, {
name: "control_action", name: "control_action",
args: []string{"-s", "run"},
opts: options{serviceControlAction: "run"}, opts: options{serviceControlAction: "run"},
ss: []string{"-s", "run"},
}, { }, {
name: "glinet_mode", name: "glinet_mode",
args: []string{"--glinet"},
opts: options{glinetMode: true}, opts: options{glinetMode: true},
ss: []string{"--glinet"},
}, { }, {
name: "multiple", name: "multiple",
args: []string{ opts: options{
serviceControlAction: "run",
configFilename: "config",
workDir: "work",
pidFile: "pid",
disableUpdate: true,
},
ss: []string{
"-c", "config", "-c", "config",
"-w", "work", "-w", "work",
"-s", "run", "-s", "run",
"--pidfile", "pid", "--pidfile", "pid",
"--no-check-update", "--no-check-update",
}, },
opts: options{
serviceControlAction: "run",
confFilename: "config",
workDir: "work",
pidFile: "pid",
disableUpdate: true,
},
}} }}
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
result := optsToArgs(tc.opts) result := serialize(tc.opts)
assert.ElementsMatch(t, tc.args, result) assert.ElementsMatch(t, tc.ss, result)
}) })
} }
} }

View File

@@ -197,7 +197,7 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
DisplayName: serviceDisplayName, DisplayName: serviceDisplayName,
Description: serviceDescription, Description: serviceDescription,
WorkingDirectory: pwd, WorkingDirectory: pwd,
Arguments: optsToArgs(runOpts), Arguments: serialize(runOpts),
} }
configureService(svcConfig) 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( aghhttp.Error(
r, r,
w, w,
@@ -356,7 +356,7 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
} }
// TODO(e.burkov): Investigate and perhaps check other ports. // TODO(e.burkov): Investigate and perhaps check other ports.
if !webCheckPortAvailable(data.PortHTTPS) { if !WebCheckPortAvailable(data.PortHTTPS) {
aghhttp.Error( aghhttp.Error(
r, r,
w, w,

View File

@@ -16,8 +16,6 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/NYTimes/gziphandler" "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"
"golang.org/x/net/http2/h2c" "golang.org/x/net/http2/h2c"
) )
@@ -55,56 +53,40 @@ type webConfig struct {
WriteTimeout time.Duration WriteTimeout time.Duration
firstRun bool firstRun bool
serveHTTP3 bool
} }
// httpsServer contains the data for the HTTPS server. // HTTPSServer - HTTPS Server
type httpsServer struct { type HTTPSServer struct {
// server is the pre-HTTP/3 HTTPS server.
server *http.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 cond *sync.Cond
condLock sync.Mutex condLock sync.Mutex
cert tls.Certificate shutdown bool // if TRUE, don't restart the server
inShutdown bool
enabled bool enabled bool
cert tls.Certificate
} }
// Web is the web UI and API server. // Web - module object
type Web struct { type Web struct {
conf *webConfig conf *webConfig
forceHTTPS bool
// TODO(a.garipov): Refactor all these servers. httpServer *http.Server // HTTP module
httpServer *http.Server httpsServer HTTPSServer // HTTPS module
// httpServerBeta is a server for new client.
httpServerBeta *http.Server
// handlerBeta is the handler for new client. // handlerBeta is the handler for new client.
handlerBeta http.Handler handlerBeta http.Handler
// installerBeta is the pre-install handler for new client. // installerBeta is the pre-install handler for new client.
installerBeta http.Handler installerBeta http.Handler
// httpsServer is the server that handles HTTPS traffic. If it is not nil, // httpServerBeta is a server for new client.
// [Web.http3Server] must also not be nil. httpServerBeta *http.Server
httpsServer httpsServer
forceHTTPS bool
} }
// newWeb creates a new instance of the web UI and API server. // CreateWeb - create module
func newWeb(conf *webConfig) (w *Web) { func CreateWeb(conf *webConfig) *Web {
log.Info("web: initializing") log.Info("Initialize web module")
w = &Web{ w := Web{}
conf: conf, w.conf = conf
}
clientFS := http.FileServer(http.FS(conf.clientFS)) clientFS := http.FileServer(http.FS(conf.clientFS))
betaClientFS := http.FileServer(http.FS(conf.clientBetaFS)) betaClientFS := http.FileServer(http.FS(conf.clientBetaFS))
@@ -126,15 +108,12 @@ func newWeb(conf *webConfig) (w *Web) {
} }
w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock) w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock)
return &w
return w
} }
// webCheckPortAvailable checks if port, which is considered an HTTPS port, is // WebCheckPortAvailable - check if port is available
// available, unless the HTTPS server isn't active. // BUT: if we are already using this port, no need
// func WebCheckPortAvailable(port int) bool {
// TODO(a.garipov): Adapt for HTTP/3.
func webCheckPortAvailable(port int) (ok bool) {
return Context.web.httpsServer.server != nil || return Context.web.httpsServer.server != nil ||
aghnet.CheckPort("tcp", config.BindHost, port) == nil aghnet.CheckPort("tcp", config.BindHost, port) == nil
} }
@@ -142,7 +121,7 @@ func webCheckPortAvailable(port int) (ok bool) {
// TLSConfigChanged updates the TLS configuration and restarts the HTTPS server // TLSConfigChanged updates the TLS configuration and restarts the HTTPS server
// if necessary. // if necessary.
func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf tlsConfigSettings) { 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.conf.PortHTTPS = tlsConf.PortHTTPS
web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0) web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0)
@@ -164,8 +143,6 @@ func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf tlsConfigSettings)
var cancel context.CancelFunc var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, shutdownTimeout) ctx, cancel = context.WithTimeout(ctx, shutdownTimeout)
shutdownSrv(ctx, web.httpsServer.server) shutdownSrv(ctx, web.httpsServer.server)
shutdownSrv3(web.httpsServer.server3)
cancel() cancel()
} }
@@ -183,7 +160,7 @@ func (web *Web) Start() {
go web.tlsServerLoop() go web.tlsServerLoop()
// this loop is used as an ability to change listening host and/or port // this loop is used as an ability to change listening host and/or port
for !web.httpsServer.inShutdown { for !web.httpsServer.shutdown {
printHTTPAddresses(aghhttp.SchemeHTTP) printHTTPAddresses(aghhttp.SchemeHTTP)
errs := make(chan error, 2) errs := make(chan error, 2)
@@ -254,7 +231,7 @@ func (web *Web) Close(ctx context.Context) {
log.Info("stopping http server...") log.Info("stopping http server...")
web.httpsServer.cond.L.Lock() web.httpsServer.cond.L.Lock()
web.httpsServer.inShutdown = true web.httpsServer.shutdown = true
web.httpsServer.cond.L.Unlock() web.httpsServer.cond.L.Unlock()
var cancel context.CancelFunc var cancel context.CancelFunc
@@ -262,7 +239,6 @@ func (web *Web) Close(ctx context.Context) {
defer cancel() defer cancel()
shutdownSrv(ctx, web.httpsServer.server) shutdownSrv(ctx, web.httpsServer.server)
shutdownSrv3(web.httpsServer.server3)
shutdownSrv(ctx, web.httpServer) shutdownSrv(ctx, web.httpServer)
shutdownSrv(ctx, web.httpServerBeta) shutdownSrv(ctx, web.httpServerBeta)
@@ -272,7 +248,7 @@ func (web *Web) Close(ctx context.Context) {
func (web *Web) tlsServerLoop() { func (web *Web) tlsServerLoop() {
for { for {
web.httpsServer.cond.L.Lock() web.httpsServer.cond.L.Lock()
if web.httpsServer.inShutdown { if web.httpsServer.shutdown {
web.httpsServer.cond.L.Unlock() web.httpsServer.cond.L.Unlock()
break break
} }
@@ -280,7 +256,7 @@ func (web *Web) tlsServerLoop() {
// this mechanism doesn't let us through until all conditions are met // this mechanism doesn't let us through until all conditions are met
for !web.httpsServer.enabled { // sleep until necessary data is supplied for !web.httpsServer.enabled { // sleep until necessary data is supplied
web.httpsServer.cond.Wait() web.httpsServer.cond.Wait()
if web.httpsServer.inShutdown { if web.httpsServer.shutdown {
web.httpsServer.cond.L.Unlock() web.httpsServer.cond.L.Unlock()
return return
} }
@@ -288,10 +264,11 @@ func (web *Web) tlsServerLoop() {
web.httpsServer.cond.L.Unlock() web.httpsServer.cond.L.Unlock()
addr := netutil.JoinHostPort(web.conf.BindHost.String(), web.conf.PortHTTPS) // prepare HTTPS server
address := netutil.JoinHostPort(web.conf.BindHost.String(), web.conf.PortHTTPS)
web.httpsServer.server = &http.Server{ web.httpsServer.server = &http.Server{
ErrorLog: log.StdLog("web: https", log.DEBUG), ErrorLog: log.StdLog("web: https", log.DEBUG),
Addr: addr, Addr: address,
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
Certificates: []tls.Certificate{web.httpsServer.cert}, Certificates: []tls.Certificate{web.httpsServer.cert},
RootCAs: Context.tlsRoots, RootCAs: Context.tlsRoots,
@@ -305,40 +282,10 @@ func (web *Web) tlsServerLoop() {
} }
printHTTPAddresses(aghhttp.SchemeHTTPS) printHTTPAddresses(aghhttp.SchemeHTTPS)
if web.conf.serveHTTP3 {
go web.mustStartHTTP3(addr)
}
log.Debug("web: starting https server")
err := web.httpsServer.server.ListenAndServeTLS("", "") err := web.httpsServer.server.ListenAndServeTLS("", "")
if !errors.Is(err, http.ErrServerClosed) { if err != http.ErrServerClosed {
cleanupAlways() cleanupAlways()
log.Fatalf("web: https: %s", err) log.Fatal(err)
} }
} }
} }
func (web *Web) mustStartHTTP3(address string) {
defer log.OnPanic("web: http3")
web.httpsServer.server3 = &http3.Server{
// TODO(a.garipov): See if there is a way to use the error log as
// well as timeouts here.
Addr: address,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{web.httpsServer.cert},
RootCAs: Context.tlsRoots,
CipherSuites: aghtls.SaferCipherSuites(),
MinVersion: tls.VersionTLS12,
},
Handler: withMiddlewares(Context.mux, limitRequestBody),
}
log.Debug("web: starting http/3 server")
err := web.httpsServer.server3.ListenAndServe()
if !errors.Is(err, quic.ErrServerClosed) {
cleanupAlways()
log.Fatalf("web: http3: %s", err)
}
}

View File

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

View File

@@ -55,7 +55,12 @@ func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
return return
} }
_ = aghhttp.WriteJSONResponse(w, r, resp) w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(resp)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
}
} }
// configResp is the response to the GET /control/stats_info. // configResp is the response to the GET /control/stats_info.
@@ -66,7 +71,13 @@ type configResp struct {
// handleStatsInfo handles requests to the GET /control/stats_info endpoint. // handleStatsInfo handles requests to the GET /control/stats_info endpoint.
func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) { func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
resp := configResp{IntervalDays: atomic.LoadUint32(&s.limitHours) / 24} resp := configResp{IntervalDays: atomic.LoadUint32(&s.limitHours) / 24}
_ = aghhttp.WriteJSONResponse(w, r, resp)
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(resp)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
}
} }
// handleStatsConfig handles requests to the POST /control/stats_config // handleStatsConfig handles requests to the POST /control/stats_config

View File

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