Compare commits

..

10 Commits

Author SHA1 Message Date
Ainar Garipov
0c7d56dca3 Merge branch 'master' into 4927-refactor-tls 2022-11-22 17:10:40 +03:00
Ainar Garipov
08282dc4d9 Pull request: 4927-imp-ui
Updates #4927.

Squashed commit of the following:

commit 510143325805133e379ebc207cdc6bff59c94ade
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Nov 22 15:00:13 2022 +0300

    home: imp err

commit fd65a9914494b6dccdee7c0f0aa08bce80ce0945
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Nov 21 18:53:39 2022 +0300

    client: imp validation ui
2022-11-22 17:07:49 +03:00
Ainar Garipov
f36efa26a4 home: refactor more 2022-11-21 19:45:18 +03:00
Ainar Garipov
a8850059db home: refactor tls 2022-11-21 19:05:49 +03:00
Dimitry Kolyshev
93882d6860 Pull request: 4223 home: cmd update
Merge in DNS/adguard-home from 4223-cmd-update to master

Squashed commit of the following:

commit ffda71246f37eaba0cb190840f1370ba65099d7c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Nov 15 16:32:10 2022 +0200

    home: cmd update

commit 9c4e1c33da78952a2b1477ac380a0cf042a8990f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Nov 15 13:51:33 2022 +0200

    home: cmd update

commit 6a564dc30771b3675e8861ca3befaaee15d83026
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Nov 14 11:05:06 2022 +0200

    all: docs

commit a546bdbdb6f3f78c40908bc1864f2a1ae1c9071f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Nov 14 10:55:16 2022 +0200

    home: cmd update

commit cbbb594980d3d163fe0489494b0ddca5f679d6e6
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Nov 14 10:16:09 2022 +0200

    home: imp code

commit 677f8a7ca0f47da0ac636e5bab9db24506cf5041
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Sun Nov 13 14:12:48 2022 +0200

    home: cmd update
2022-11-15 17:44:50 +03:00
Ainar Garipov
167b112511 Pull request: 5035-more-clients-netip-addr
Updates #5035.

Squashed commit of the following:

commit 1934ea14299921760e9fcf6dd9053bd3155cb40e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Nov 9 14:19:54 2022 +0300

    all: move more client code to netip.Addr
2022-11-09 14:37:07 +03:00
Ainar Garipov
98af0e000e Pull request: upd-chlog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit efe2d95b870e413af4f91be5de7c7b97970e1dd5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Nov 8 18:08:03 2022 +0300

    all: upd chlog
2022-11-08 18:15:01 +03:00
Ainar Garipov
2bfdcbbc10 Pull request: upd-before-release
Merge in DNS/adguard-home from upd-before-release to master

Squashed commit of the following:

commit 71f36273a55f63d389188fd7df2950a6207549a9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Nov 8 14:35:18 2022 +0300

    all: upd deps, tools, filters
2022-11-08 16:24:44 +03:00
Ainar Garipov
8fdbcc005c Pull request: imp-pprof
Merge in DNS/adguard-home from imp-pprof to master

Squashed commit of the following:

commit cd6c2ec15342ef55957ab65e1599733c2ee57b7b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Nov 7 19:45:12 2022 +0300

    home: imp pprof handling, field alignment
2022-11-08 11:36:42 +03:00
Ainar Garipov
464fbf0b54 Pull request: 5089-windows-hosts-crash
Updates #5089.

Squashed commit of the following:

commit dd3ce763326ad3207de111bb911e0a2665bcebba
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Nov 7 16:21:43 2022 +0300

    aghnet: fix comparison

commit 0d736fb7fc5cb2e77fd533cd95fdf3fbc6dc86d1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Nov 7 14:13:05 2022 +0300

    aghnet: fix crash
2022-11-07 16:51:07 +03:00
37 changed files with 859 additions and 645 deletions

View File

@@ -18,12 +18,34 @@ and this project adheres to
<!-- <!--
## [v0.107.18] - 2022-11-16 (APPROX.) ## [v0.107.19] - 2022-11-23 (APPROX.)
See also the [v0.107.19 GitHub milestone][ms-v0.107.19].
[ms-v0.107.19]: https://github.com/AdguardTeam/AdGuardHome/milestone/55?closed=1
-->
### Added
- The new `--update` command-line option, which allows updating AdGuard Home
silently ([#4223]).
[#4223]: https://github.com/AdguardTeam/AdGuardHome/issues/4223
## [v0.107.18] - 2022-11-08
See also the [v0.107.18 GitHub milestone][ms-v0.107.18]. See also the [v0.107.18 GitHub milestone][ms-v0.107.18].
[ms-v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/milestone/52?closed=1 ### Fixed
-->
- Crash on some systems when domains from system hosts files are processed
([#5089]).
[#5089]: https://github.com/AdguardTeam/AdGuardHome/issues/5089
[ms-v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/milestone/54?closed=1
@@ -830,7 +852,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
- Query log search now supports internationalized domains ([#3012]). - Query log search now supports internationalized domains ([#3012]).
- Internationalized domains are now shown decoded in the query log with the - Internationalized domains are now shown decoded in the query log with the
original encoded version shown in request details ([#3013]). original encoded version shown in request details ([#3013]).
- When /etc/hosts-type rules have several IPs for one host, all IPs are now - When `/etc/hosts`-type rules have several IPs for one host, all IPs are now
returned instead of only the first one ([#1381]). returned instead of only the first one ([#1381]).
- Property `rlimit_nofile` is now in the `os` object of the configuration - Property `rlimit_nofile` is now in the `os` object of the configuration
file, together with the new `group` and `user` properties ([#2763]). file, together with the new `group` and `user` properties ([#2763]).
@@ -1088,7 +1110,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
- Hostname uniqueness validation in the DHCP server ([#2952]). - Hostname uniqueness validation in the DHCP server ([#2952]).
- Hostname generating for DHCP clients which don't provide their own ([#2723]). - Hostname generating for DHCP clients which don't provide their own ([#2723]).
- New flag `--no-etc-hosts` to disable client domain name lookups in the - New flag `--no-etc-hosts` to disable client domain name lookups in the
operating system's /etc/hosts files ([#1947]). operating system's `/etc/hosts` files ([#1947]).
- The ability to set up custom upstreams to resolve PTR queries for local - The ability to set up custom upstreams to resolve PTR queries for local
addresses and to disable the automatic resolving of clients' addresses addresses and to disable the automatic resolving of clients' addresses
([#2704]). ([#2704]).
@@ -1405,11 +1427,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!-- <!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.18...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.19...HEAD
[v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...v0.107.18 [v0.107.19]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.18...v0.107.19
--> -->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.18...HEAD
[v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...v0.107.18
[v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...v0.107.17 [v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...v0.107.17
[v0.107.16]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.15...v0.107.16 [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.15]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...v0.107.15

View File

@@ -393,6 +393,7 @@
"encryption_issuer": "Issuer", "encryption_issuer": "Issuer",
"encryption_hostnames": "Hostnames", "encryption_hostnames": "Hostnames",
"encryption_reset": "Are you sure you want to reset encryption settings?", "encryption_reset": "Are you sure you want to reset encryption settings?",
"encryption_warning": "Warning",
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings</0>.", "topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings</0>.",
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings</0>.", "topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings</0>.",
"form_error_port_range": "Enter port number in the range of 80-65535", "form_error_port_range": "Enter port number in the range of 80-65535",

View File

@@ -56,6 +56,26 @@ const clearFields = (change, setTlsConfig, t) => {
} }
}; };
const validationMessage = (warningValidation, isWarning) => {
if (!warningValidation) {
return null;
}
if (isWarning) {
return (
<div className="col-12">
<p><Trans>encryption_warning</Trans>: {warningValidation}</p>
</div>
);
}
return (
<div className="col-12">
<p className="text-danger">{warningValidation}</p>
</div>
);
};
let Form = (props) => { let Form = (props) => {
const { const {
t, t,
@@ -95,6 +115,8 @@ let Form = (props) => {
|| !valid_cert || !valid_cert
|| !valid_pair; || !valid_pair;
const isWarning = valid_key && valid_cert && valid_pair;
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="row"> <div className="row">
@@ -382,11 +404,7 @@ let Form = (props) => {
)} )}
</div> </div>
</div> </div>
{warning_validation && ( {validationMessage(warning_validation, isWarning)}
<div className="col-12">
<p className="text-danger">{warning_validation}</p>
</div>
)}
</div> </div>
<div className="btn-list mt-2"> <div className="btn-list mt-2">

10
go.mod
View File

@@ -4,7 +4,7 @@ go 1.18
require ( require (
github.com/AdguardTeam/dnsproxy v0.46.2 github.com/AdguardTeam/dnsproxy v0.46.2
github.com/AdguardTeam/golibs v0.11.2 github.com/AdguardTeam/golibs v0.11.3
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
github.com/ameshkov/dnscrypt/v2 v2.2.5 github.com/ameshkov/dnscrypt/v2 v2.2.5
@@ -29,9 +29,9 @@ require (
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.1.0 golang.org/x/crypto v0.1.0
golang.org/x/exp v0.0.0-20221026153819-32f3d567a233 golang.org/x/exp v0.0.0-20221106115401-f9659909a136
golang.org/x/net v0.1.0 golang.org/x/net v0.1.0
golang.org/x/sys v0.1.0 golang.org/x/sys v0.2.0
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
@@ -55,8 +55,8 @@ require (
github.com/mdlayher/socket v0.2.3 // indirect github.com/mdlayher/socket v0.2.3 // indirect
github.com/nxadm/tail v1.4.8 // indirect github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/ginkgo/v2 v2.4.0 // indirect github.com/onsi/ginkgo/v2 v2.5.0 // indirect
github.com/onsi/gomega v1.22.1 // indirect github.com/onsi/gomega v1.24.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
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

20
go.sum
View File

@@ -2,8 +2,8 @@ github.com/AdguardTeam/dnsproxy v0.46.2 h1:ZUKM713Ts5meYQqk6cJkUBMCFSWqFPXTgjXkN
github.com/AdguardTeam/dnsproxy v0.46.2/go.mod h1:PAmRzFqls0E92XTglyY2ESAqMAzZJhHKErG1ZpRnpjA= github.com/AdguardTeam/dnsproxy v0.46.2/go.mod h1:PAmRzFqls0E92XTglyY2ESAqMAzZJhHKErG1ZpRnpjA=
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.11.2 h1:JbQB1Dg2JWStXgHh1QqBbOLWnP4t9oDjppoBH6TVXSE= github.com/AdguardTeam/golibs v0.11.3 h1:Oif+REq2WLycQ2Xm3ZPmJdfftptss0HbGWbxdFaC310=
github.com/AdguardTeam/golibs v0.11.2/go.mod h1:87bN2x4VsTritptE3XZg9l8T6gznWsIxHBcQ1DeRIXA= github.com/AdguardTeam/golibs v0.11.3/go.mod h1:87bN2x4VsTritptE3XZg9l8T6gznWsIxHBcQ1DeRIXA=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.16.0 h1:IO29m+ZyQuuOnPLTzHuXj35V1DZOp1Dcryl576P2syg= github.com/AdguardTeam/urlfilter v0.16.0 h1:IO29m+ZyQuuOnPLTzHuXj35V1DZOp1Dcryl576P2syg=
github.com/AdguardTeam/urlfilter v0.16.0/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= github.com/AdguardTeam/urlfilter v0.16.0/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
@@ -131,12 +131,12 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
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.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/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls=
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI= github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg=
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -177,8 +177,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
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.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20221026153819-32f3d567a233 h1:9bNbSKT4RPLEzne0Xh1v3NaNecsa1DKjkOuTbY6V9rI= golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
golang.org/x/exp v0.0.0-20221026153819-32f3d567a233/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
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=
@@ -254,8 +254,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/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

@@ -139,6 +139,8 @@ type HostsRecord struct {
func (rec *HostsRecord) equal(other *HostsRecord) (ok bool) { func (rec *HostsRecord) equal(other *HostsRecord) (ok bool) {
if rec == nil { if rec == nil {
return other == nil return other == nil
} else if other == nil {
return false
} }
return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases) return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases)
@@ -478,7 +480,11 @@ func (hc *HostsContainer) refresh() (err error) {
return fmt.Errorf("refreshing : %w", err) return fmt.Errorf("refreshing : %w", err)
} }
if maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) { // hc.last is nil on the first refresh, so let that one through.
//
// TODO(a.garipov): Once https://github.com/golang/go/issues/56621 is
// resolved, remove the first condition.
if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) {
log.Debug("%s: no changes detected", hostsContainerPref) log.Debug("%s: no changes detected", hostsContainerPref)
return nil return nil

View File

@@ -31,12 +31,6 @@ var (
// the IP being static is available. // the IP being static is available.
const ErrNoStaticIPInfo errors.Error = "no information about static ip" const ErrNoStaticIPInfo errors.Error = "no information about static ip"
// IPv4Localhost returns 127.0.0.1, which returns true for [netip.Addr.Is4].
func IPv4Localhost() (ip netip.Addr) { return netip.AddrFrom4([4]byte{127, 0, 0, 1}) }
// IPv6Localhost returns ::1, which returns true for [netip.Addr.Is6].
func IPv6Localhost() (ip netip.Addr) { return netip.AddrFrom16([16]byte{15: 1}) }
// IfaceHasStaticIP checks if interface is configured to have static IP address. // IfaceHasStaticIP checks if interface is configured to have static IP address.
// If it can't give a definitive answer, it returns false and an error for which // If it can't give a definitive answer, it returns false and an error for which
// errors.Is(err, ErrNoStaticIPInfo) is true. // errors.Is(err, ErrNoStaticIPInfo) is true.

View File

@@ -188,7 +188,7 @@ func TestBroadcastFromIPNet(t *testing.T) {
} }
func TestCheckPort(t *testing.T) { func TestCheckPort(t *testing.T) {
laddr := netip.AddrPortFrom(IPv4Localhost(), 0) laddr := netip.AddrPortFrom(netutil.IPv4Localhost(), 0)
t.Run("tcp_bound", func(t *testing.T) { t.Run("tcp_bound", func(t *testing.T) {
l, err := net.Listen("tcp", laddr.String()) l, err := net.Listen("tcp", laddr.String())

View File

@@ -23,16 +23,6 @@ func ValidateClientID(id string) (err error) {
return nil return nil
} }
// hasLabelSuffix returns true if s ends with suffix preceded by a dot. It's
// a helper function to prevent unnecessary allocations in code like:
//
// if strings.HasSuffix(s, "." + suffix) { /* … */ }
//
// s must be longer than suffix.
func hasLabelSuffix(s, suffix string) (ok bool) {
return strings.HasSuffix(s, suffix) && s[len(s)-len(suffix)-1] == '.'
}
// clientIDFromClientServerName extracts and validates a ClientID. hostSrvName // clientIDFromClientServerName extracts and validates a ClientID. hostSrvName
// is the server name of the host. cliSrvName is the server name as sent by the // is the server name of the host. cliSrvName is the server name as sent by the
// client. When strict is true, and client and host server name don't match, // client. When strict is true, and client and host server name don't match,
@@ -46,7 +36,7 @@ func clientIDFromClientServerName(
return "", nil return "", nil
} }
if !hasLabelSuffix(cliSrvName, hostSrvName) { if !netutil.IsImmediateSubdomain(cliSrvName, hostSrvName) {
if !strict { if !strict {
return "", nil return "", nil
} }

View File

@@ -145,7 +145,8 @@ type FilteringConfig struct {
IpsetListFileName string `yaml:"ipset_file"` IpsetListFileName string `yaml:"ipset_file"`
} }
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS // TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, DNS-over-TLS,
// and DNS-over-QUIC.
type TLSConfig struct { type TLSConfig struct {
cert tls.Certificate cert tls.Certificate

View File

@@ -246,6 +246,7 @@ type RDNSExchanger interface {
// Exchange tries to resolve the ip in a suitable way, e.g. either as // Exchange tries to resolve the ip in a suitable way, e.g. either as
// local or as external. // local or as external.
Exchange(ip net.IP) (host string, err error) Exchange(ip net.IP) (host string, err error)
// ResolvesPrivatePTR returns true if the RDNSExchanger is able to // ResolvesPrivatePTR returns true if the RDNSExchanger is able to
// resolve PTR requests for locally-served addresses. // resolve PTR requests for locally-served addresses.
ResolvesPrivatePTR() (ok bool) ResolvesPrivatePTR() (ok bool)
@@ -261,6 +262,9 @@ const (
rDNSNotPTRErr errors.Error = "the response is not a ptr" rDNSNotPTRErr errors.Error = "the response is not a ptr"
) )
// type check
var _ RDNSExchanger = (*Server)(nil)
// Exchange implements the RDNSExchanger interface for *Server. // Exchange implements the RDNSExchanger interface for *Server.
func (s *Server) Exchange(ip net.IP) (host string, err error) { func (s *Server) Exchange(ip net.IP) (host string, err error) {
s.serverLock.RLock() s.serverLock.RLock()
@@ -675,21 +679,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// IsBlockedClient returns true if the client is blocked by the current access // IsBlockedClient returns true if the client is blocked by the current access
// settings. // settings.
func (s *Server) IsBlockedClient(ip net.IP, clientID string) (blocked bool, rule string) { func (s *Server) IsBlockedClient(ip netip.Addr, clientID string) (blocked bool, rule string) {
s.serverLock.RLock() s.serverLock.RLock()
defer s.serverLock.RUnlock() defer s.serverLock.RUnlock()
blockedByIP := false blockedByIP := false
if ip != nil { if ip != (netip.Addr{}) {
// TODO(a.garipov): Remove once we switch to netip.Addr more fully. blockedByIP, rule = s.access.isBlockedIP(ip)
ipAddr, err := netutil.IPToAddrNoMapped(ip)
if err != nil {
log.Error("dnsforward: bad client ip %v: %s", ip, err)
return false, ""
}
blockedByIP, rule = s.access.isBlockedIP(ipAddr)
} }
allowlistMode := s.access.allowlistMode() allowlistMode := s.access.allowlistMode()

View File

@@ -19,13 +19,13 @@ func (s *Server) beforeRequestHandler(
_ *proxy.Proxy, _ *proxy.Proxy,
pctx *proxy.DNSContext, pctx *proxy.DNSContext,
) (reply bool, err error) { ) (reply bool, err error) {
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
clientID, err := s.clientIDFromDNSContext(pctx) clientID, err := s.clientIDFromDNSContext(pctx)
if err != nil { if err != nil {
return false, fmt.Errorf("getting clientid: %w", err) return false, fmt.Errorf("getting clientid: %w", err)
} }
blocked, _ := s.IsBlockedClient(ip, clientID) addrPort := netutil.NetAddrToAddrPort(pctx.Addr)
blocked, _ := s.IsBlockedClient(addrPort.Addr(), clientID)
if blocked { if blocked {
return s.preBlockedResponse(pctx) return s.preBlockedResponse(pctx)
} }

View File

@@ -11,7 +11,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/golibs/netutil"
"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"
@@ -40,7 +40,7 @@ func serveFiltersLocally(t *testing.T, fltContent []byte) (ipp netip.AddrPort) {
addr := l.Addr() addr := l.Addr()
require.IsType(t, new(net.TCPAddr), addr) require.IsType(t, new(net.TCPAddr), addr)
return netip.AddrPortFrom(aghnet.IPv4Localhost(), uint16(addr.(*net.TCPAddr).Port)) return netip.AddrPortFrom(netutil.IPv4Localhost(), uint16(addr.(*net.TCPAddr).Port))
} }
func TestFilters(t *testing.T) { func TestFilters(t *testing.T) {

View File

@@ -34,6 +34,7 @@ var blockedServices = []blockedService{{
"||amazon.com.au^", "||amazon.com.au^",
"||amazon.com.br^", "||amazon.com.br^",
"||amazon.com.mx^", "||amazon.com.mx^",
"||amazon.com.tr^",
"||amazon.com^", "||amazon.com^",
"||amazon.de^", "||amazon.de^",
"||amazon.es^", "||amazon.es^",
@@ -73,19 +74,29 @@ var blockedServices = []blockedService{{
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M38 10.813l-.906 3.78-1.907-3.405v1.718c2.899 2.301 4.926 5.79 5.126 9.688.699-.2 1.3-.188 2-.188 1.374 0 2.667.297 3.812.875l-1.031-.593 3.812-.875-3.812-.907L48.5 19h-3.813l2.813-2.688-3.688 1.094 2-3.312-3.312 2 1.094-3.688-2.688 2.781.094-3.874-2 3.28zM27 11c-5 0-9.414 2.992-11.313 7.594-.699-.399-1.687-.688-2.687-.688-3.2 0-5.906 2.606-5.906 5.907v.5c-3.899.3-7.094 3.68-7.094 7.78 0 .802.113 1.52.313 2.22.101.398.5.687 1 .687h47c.398 0 .675-.195.874-.594.5-1.101.813-2.207.813-3.406 0-4.2-3.488-7.594-7.688-7.594-.8 0-1.511.082-2.312.282l4.906 6.625-5.5-4.5L22 29.593l15.094-4.905L28.5 21.5l10.688 1.813v-.125C39.188 16.488 33.699 11 27 11zm19.781 12.656c.434.274.844.586 1.219.938h.5z\" /></svg>"), IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M38 10.813l-.906 3.78-1.907-3.405v1.718c2.899 2.301 4.926 5.79 5.126 9.688.699-.2 1.3-.188 2-.188 1.374 0 2.667.297 3.812.875l-1.031-.593 3.812-.875-3.812-.907L48.5 19h-3.813l2.813-2.688-3.688 1.094 2-3.312-3.312 2 1.094-3.688-2.688 2.781.094-3.874-2 3.28zM27 11c-5 0-9.414 2.992-11.313 7.594-.699-.399-1.687-.688-2.687-.688-3.2 0-5.906 2.606-5.906 5.907v.5c-3.899.3-7.094 3.68-7.094 7.78 0 .802.113 1.52.313 2.22.101.398.5.687 1 .687h47c.398 0 .675-.195.874-.594.5-1.101.813-2.207.813-3.406 0-4.2-3.488-7.594-7.688-7.594-.8 0-1.511.082-2.312.282l4.906 6.625-5.5-4.5L22 29.593l15.094-4.905L28.5 21.5l10.688 1.813v-.125C39.188 16.488 33.699 11 27 11zm19.781 12.656c.434.274.844.586 1.219.938h.5z\" /></svg>"),
Rules: []string{ Rules: []string{
"||1.1.1.1^", "||1.1.1.1^",
"||argotunnel.com^",
"||cloudflare-dns.com^", "||cloudflare-dns.com^",
"||cloudflare-ipfs.com^",
"||cloudflare-quic.com^",
"||cloudflare.cn^", "||cloudflare.cn^",
"||cloudflare.com^", "||cloudflare.com^",
"||cloudflare.net^", "||cloudflare.net^",
"||cloudflareaccess.com^",
"||cloudflareapps.com^",
"||cloudflarebolt.com^", "||cloudflarebolt.com^",
"||cloudflareclient.com^", "||cloudflareclient.com^",
"||cloudflareinsights.com^", "||cloudflareinsights.com^",
"||cloudflareresolve.com^", "||cloudflareresolve.com^",
"||cloudflarestatus.com^", "||cloudflarestatus.com^",
"||cloudflarestream.com^", "||cloudflarestream.com^",
"||cloudflarewarp.com^",
"||dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion^", "||dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion^",
"||one.one^", "||one.one^",
"||pages.dev^",
"||trycloudflare.com^",
"||videodelivery.net^",
"||warp.plus^", "||warp.plus^",
"||workers.dev^",
}, },
}, { }, {
ID: "dailymotion", ID: "dailymotion",
@@ -111,6 +122,7 @@ var blockedServices = []blockedService{{
Rules: []string{ Rules: []string{
"||discord.com^", "||discord.com^",
"||discord.gg^", "||discord.gg^",
"||discord.gift",
"||discord.media^", "||discord.media^",
"||discordapp.com^", "||discordapp.com^",
"||discordapp.net^", "||discordapp.net^",
@@ -122,8 +134,20 @@ var blockedServices = []blockedService{{
Rules: []string{ Rules: []string{
"||disney-plus.net^", "||disney-plus.net^",
"||disney.playback.edge.bamgrid.com^", "||disney.playback.edge.bamgrid.com^",
"||disneynow.com^",
"||disneyplus.com^", "||disneyplus.com^",
"||hotstar.com^",
"||media.dssott.com^", "||media.dssott.com^",
"||star.playback.edge.bamgrid.com^",
"||starplus.com^",
},
}, {
ID: "douban",
Name: "Douban",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M36.54 18.51H13.315v8.012H36.54zM8.58 14.32h32.643v16.39H8.581zM5.146 5.754h39.516v4.193H5.145zm11.051 25.652c1.73 2.416 3.28 5.336 4.647 8.748h8.263c1.637-2.632 3.076-5.552 4.31-8.748l4.75 1.631c-1.241 2.694-2.581 5.07-4.003 7.117h11.615v4.147H4.028v-4.147h12.168c-1.117-2.203-2.568-4.574-4.371-7.117z\" /></svg>"),
Rules: []string{
"||douban.com^",
"||doubanio.com^",
}, },
}, { }, {
ID: "ebay", ID: "ebay",
@@ -176,11 +200,13 @@ var blockedServices = []blockedService{{
"||facebook.net^", "||facebook.net^",
"||facebookcorewwwi.onion^", "||facebookcorewwwi.onion^",
"||fb.com^", "||fb.com^",
"||fb.gg^",
"||fb.me^", "||fb.me^",
"||fb.watch^", "||fb.watch^",
"||fbcdn.com^", "||fbcdn.com^",
"||fbcdn.net^", "||fbcdn.net^",
"||fbsbx.com^", "||fbsbx.com^",
"||fbwat.ch^",
"||messenger.com^", "||messenger.com^",
}, },
}, { }, {
@@ -269,8 +295,9 @@ var blockedServices = []blockedService{{
Name: "QQ", Name: "QQ",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 32 32\"><g fill=\"none\" fillRule=\"evenodd\"><path d=\"M0 0h32v32H0z\" /><g fill=\"currentColor\" fillRule=\"nonzero\"><path d=\"M11.25 32C8.342 32 6 30.74 6 29.242c0-1.497 2.342-2.757 5.25-2.757s5.25 1.26 5.25 2.757S14.158 32 11.25 32zM27 29.242c0-1.497-2.342-2.757-5.25-2.757s-5.25 1.26-5.25 2.757S18.842 32 21.75 32 27 30.74 27 29.242zM14.885 7.182c0 .63-.323 1.182-.808 1.182-.485 0-.808-.552-.808-1.182 0-.63.323-1.182.808-1.182.485 0 .808.552.808 1.182zM18.923 6c-.485 0-.808.552-.808 1.182 0 .63.323-.394.808-.394.485 0 .808 1.024.808.394S19.408 6 18.923 6z\" /><path d=\"M6.653 12.638s4.685 2.465 9.926 2.465c5.242 0 9.927-2.465 9.927-2.465.112-.09.217-.161.316-.212-.002-1.088-.078-2.026-.078-2.808C26.744 4.292 22.138 0 16.5 0S6.176 4.292 6.176 9.618v2.78c.146.042.3.113.477.24zm12.626-8.664c1.112 0 1.986 1.272 1.986 2.782s-.874 2.782-1.986 2.782c-1.111 0-1.985-1.271-1.985-2.782 0-1.51.874-2.782 1.985-2.782zm-5.558 0c1.111 0 1.985 1.272 1.985 2.782s-.874 2.782-1.985 2.782c-1.112 0-1.986-1.271-1.986-2.782 0-1.51.874-2.782 1.986-2.782zm2.779 6.624c2.912 0 5.294.464 5.294.994s-2.382 1.656-5.294 1.656c-2.912 0-5.294-1.126-5.294-1.656s2.382-.994 5.294-.994zm11.374 5.182c-.058.038-.108.076-.177.117-.159.08-5.241 3.18-11.038 3.18-1.43 0-2.7-.239-3.97-.477-.239 1.67-.239 3.259-.239 3.974 0 1.272-1.032 1.193-2.303 1.272-1.27 0-2.223.16-2.303-1.033 0-.16-.08-2.782.397-5.564-1.588-.716-2.62-1.272-2.7-1.352a3.293 3.293 0 01-.335-.216C4.012 17.55 3 19.598 3 21.223c0 3.815 1.112 3.418 1.112 3.418.476 0 1.27-.795 1.985-1.67C7.765 27.662 11.735 31 16.5 31c4.765 0 8.735-3.338 10.403-8.028.715.874 1.509 1.669 1.985 1.669 0 0 1.112.397 1.112-3.418 0-1.588-.968-3.631-2.126-5.443z\" /></g></g></svg>"), IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 32 32\"><g fill=\"none\" fillRule=\"evenodd\"><path d=\"M0 0h32v32H0z\" /><g fill=\"currentColor\" fillRule=\"nonzero\"><path d=\"M11.25 32C8.342 32 6 30.74 6 29.242c0-1.497 2.342-2.757 5.25-2.757s5.25 1.26 5.25 2.757S14.158 32 11.25 32zM27 29.242c0-1.497-2.342-2.757-5.25-2.757s-5.25 1.26-5.25 2.757S18.842 32 21.75 32 27 30.74 27 29.242zM14.885 7.182c0 .63-.323 1.182-.808 1.182-.485 0-.808-.552-.808-1.182 0-.63.323-1.182.808-1.182.485 0 .808.552.808 1.182zM18.923 6c-.485 0-.808.552-.808 1.182 0 .63.323-.394.808-.394.485 0 .808 1.024.808.394S19.408 6 18.923 6z\" /><path d=\"M6.653 12.638s4.685 2.465 9.926 2.465c5.242 0 9.927-2.465 9.927-2.465.112-.09.217-.161.316-.212-.002-1.088-.078-2.026-.078-2.808C26.744 4.292 22.138 0 16.5 0S6.176 4.292 6.176 9.618v2.78c.146.042.3.113.477.24zm12.626-8.664c1.112 0 1.986 1.272 1.986 2.782s-.874 2.782-1.986 2.782c-1.111 0-1.985-1.271-1.985-2.782 0-1.51.874-2.782 1.985-2.782zm-5.558 0c1.111 0 1.985 1.272 1.985 2.782s-.874 2.782-1.985 2.782c-1.112 0-1.986-1.271-1.986-2.782 0-1.51.874-2.782 1.986-2.782zm2.779 6.624c2.912 0 5.294.464 5.294.994s-2.382 1.656-5.294 1.656c-2.912 0-5.294-1.126-5.294-1.656s2.382-.994 5.294-.994zm11.374 5.182c-.058.038-.108.076-.177.117-.159.08-5.241 3.18-11.038 3.18-1.43 0-2.7-.239-3.97-.477-.239 1.67-.239 3.259-.239 3.974 0 1.272-1.032 1.193-2.303 1.272-1.27 0-2.223.16-2.303-1.033 0-.16-.08-2.782.397-5.564-1.588-.716-2.62-1.272-2.7-1.352a3.293 3.293 0 01-.335-.216C4.012 17.55 3 19.598 3 21.223c0 3.815 1.112 3.418 1.112 3.418.476 0 1.27-.795 1.985-1.67C7.765 27.662 11.735 31 16.5 31c4.765 0 8.735-3.338 10.403-8.028.715.874 1.509 1.669 1.985 1.669 0 0 1.112.397 1.112-3.418 0-1.588-.968-3.631-2.126-5.443z\" /></g></g></svg>"),
Rules: []string{ Rules: []string{
"^(?!weixin|wx)([^.]+\\.)?qq\\.com$", "||qq-video.cdn-go.cn^",
"||qqzaixian.com^", "||qq.com^$denyallow=wx.qq.com|weixin.qq.com",
"||url.cn^",
}, },
}, { }, {
ID: "reddit", ID: "reddit",
@@ -297,8 +324,11 @@ var blockedServices = []blockedService{{
Name: "Skype", Name: "Skype",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 26 26\"><path d=\"M23.363 14.387c.153-.739.23-1.5.23-2.266C23.594 5.883 18.45.805 12.122.805c-.594 0-1.191.047-1.781.136A6.891 6.891 0 0 0 6.852 0C3.074 0 0 3.035 0 6.762c0 1.144.293 2.27.852 3.265-.133.688-.2 1.391-.2 2.094 0 6.238 5.149 11.316 11.47 11.316.648 0 1.3-.054 1.94-.164.95.477 2.012.727 3.086.727C20.926 24 24 20.969 24 17.238c0-1.004-.215-1.96-.637-2.851zM17.758 17.3c-.508.707-1.258 1.27-2.23 1.668-.966.394-2.122.593-3.434.593-1.578 0-2.903-.273-3.934-.812a5.074 5.074 0 0 1-1.808-1.582c-.47-.664-.707-1.324-.707-1.961 0-.395.156-.738.457-1.023.304-.278.687-.418 1.148-.418.379 0 .703.109.969.332.254.21.469.523.644.93.192.437.407.808.633 1.1.211.282.524.52.918.704.399.188.938.281 1.598.281.91 0 1.652-.191 2.215-.57.546-.367.812-.813.812-1.352 0-.43-.14-.765-.422-1.027-.3-.277-.699-.492-1.176-.637-.5-.152-1.18-.32-2.015-.496-1.14-.238-2.11-.523-2.88-.847-.788-.332-1.425-.79-1.89-1.364-.472-.582-.71-1.312-.71-2.172 0-.816.253-1.554.75-2.191.488-.633 1.206-1.125 2.132-1.46.91-.333 1.996-.5 3.223-.5.98 0 1.844.108 2.566.331.723.223 1.336.524 1.813.89.484.376.843.774 1.07 1.188.227.418.344.832.344 1.235 0 .386-.153.738-.453 1.046-.297.31-.68.465-1.125.465-.41 0-.727-.097-.95-.289-.207-.18-.418-.46-.656-.863-.273-.516-.605-.918-.984-1.203-.371-.277-.989-.418-1.836-.418-.79 0-1.43.156-1.902.465-.461.293-.684.633-.684 1.039 0 .246.07.449.219.629.156.187.379.351.656.488.289.145.586.258.883.34.308.082.82.207 1.523.367.887.191 1.707.398 2.43.625.73.234 1.363.516 1.879.848.527.34.941.773 1.238 1.293.297.52.445 1.16.445 1.91a4.07 4.07 0 0 1-.77 2.418zm0 0\" /></svg>"), IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 26 26\"><path d=\"M23.363 14.387c.153-.739.23-1.5.23-2.266C23.594 5.883 18.45.805 12.122.805c-.594 0-1.191.047-1.781.136A6.891 6.891 0 0 0 6.852 0C3.074 0 0 3.035 0 6.762c0 1.144.293 2.27.852 3.265-.133.688-.2 1.391-.2 2.094 0 6.238 5.149 11.316 11.47 11.316.648 0 1.3-.054 1.94-.164.95.477 2.012.727 3.086.727C20.926 24 24 20.969 24 17.238c0-1.004-.215-1.96-.637-2.851zM17.758 17.3c-.508.707-1.258 1.27-2.23 1.668-.966.394-2.122.593-3.434.593-1.578 0-2.903-.273-3.934-.812a5.074 5.074 0 0 1-1.808-1.582c-.47-.664-.707-1.324-.707-1.961 0-.395.156-.738.457-1.023.304-.278.687-.418 1.148-.418.379 0 .703.109.969.332.254.21.469.523.644.93.192.437.407.808.633 1.1.211.282.524.52.918.704.399.188.938.281 1.598.281.91 0 1.652-.191 2.215-.57.546-.367.812-.813.812-1.352 0-.43-.14-.765-.422-1.027-.3-.277-.699-.492-1.176-.637-.5-.152-1.18-.32-2.015-.496-1.14-.238-2.11-.523-2.88-.847-.788-.332-1.425-.79-1.89-1.364-.472-.582-.71-1.312-.71-2.172 0-.816.253-1.554.75-2.191.488-.633 1.206-1.125 2.132-1.46.91-.333 1.996-.5 3.223-.5.98 0 1.844.108 2.566.331.723.223 1.336.524 1.813.89.484.376.843.774 1.07 1.188.227.418.344.832.344 1.235 0 .386-.153.738-.453 1.046-.297.31-.68.465-1.125.465-.41 0-.727-.097-.95-.289-.207-.18-.418-.46-.656-.863-.273-.516-.605-.918-.984-1.203-.371-.277-.989-.418-1.836-.418-.79 0-1.43.156-1.902.465-.461.293-.684.633-.684 1.039 0 .246.07.449.219.629.156.187.379.351.656.488.289.145.586.258.883.34.308.082.82.207 1.523.367.887.191 1.707.398 2.43.625.73.234 1.363.516 1.879.848.527.34.941.773 1.238 1.293.297.52.445 1.16.445 1.91a4.07 4.07 0 0 1-.77 2.418zm0 0\" /></svg>"),
Rules: []string{ Rules: []string{
"||edge-skype-com.s-0001.s-msedge.net^",
"||skype-edf.akadns.net^",
"||skype.com^", "||skype.com^",
"||skypeassets.com^", "||skypeassets.com^",
"||skypedata.akadns.net^",
}, },
}, { }, {
ID: "snapchat", ID: "snapchat",
@@ -359,10 +389,14 @@ var blockedServices = []blockedService{{
"||bdurl.com^", "||bdurl.com^",
"||bytecdn.cn^", "||bytecdn.cn^",
"||bytedance.map.fastly.net^", "||bytedance.map.fastly.net^",
"||bytedapm.com^",
"||byteimg.com^", "||byteimg.com^",
"||byteoversea.com^",
"||douyin.com^", "||douyin.com^",
"||douyincdn.com^", "||douyincdn.com^",
"||ixigua.com^", "||douyinpic.com^",
"||douyinstatic.com^",
"||douyinvod.com^",
"||ixigua.com^", "||ixigua.com^",
"||ixiguavideo.com^", "||ixiguavideo.com^",
"||muscdn.com^", "||muscdn.com^",
@@ -375,6 +409,7 @@ var blockedServices = []blockedService{{
"||toutiao.com^", "||toutiao.com^",
"||toutiaocloud.com^", "||toutiaocloud.com^",
"||toutiaocloud.net^", "||toutiaocloud.net^",
"||toutiaovod.com^",
}, },
}, { }, {
ID: "tinder", ID: "tinder",
@@ -437,7 +472,9 @@ var blockedServices = []blockedService{{
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 19 6 C 9.625 6 2 12.503906 2 20.5 C 2 24.769531 4.058594 28.609375 7.816406 31.390625 L 5.179688 39.304688 L 13.425781 34.199219 C 15.714844 34.917969 18.507813 35.171875 21.203125 34.875 C 23.390625 39.109375 28.332031 42 34 42 C 35.722656 42 37.316406 41.675781 38.796875 41.234375 L 45.644531 45.066406 L 43.734375 38.515625 C 46.3125 36.375 48 33.394531 48 30 C 48 23.789063 42.597656 18.835938 35.75 18.105469 C 34.40625 11.152344 27.367188 6 19 6 Z M 13 14 C 14.101563 14 15 14.898438 15 16 C 15 17.101563 14.101563 18 13 18 C 11.898438 18 11 17.101563 11 16 C 11 14.898438 11.898438 14 13 14 Z M 25 14 C 26.101563 14 27 14.898438 27 16 C 27 17.101563 26.101563 18 25 18 C 23.898438 18 23 17.101563 23 16 C 23 14.898438 23.898438 14 25 14 Z M 34 20 C 40.746094 20 46 24.535156 46 30 C 46 32.957031 44.492188 35.550781 42.003906 37.394531 L 41.445313 37.8125 L 42.355469 40.933594 L 39.105469 39.109375 L 38.683594 39.25 C 37.285156 39.71875 35.6875 40 34 40 C 27.253906 40 22 35.464844 22 30 C 22 24.535156 27.253906 20 34 20 Z M 29.5 26 C 28.699219 26 28 26.699219 28 27.5 C 28 28.300781 28.699219 29 29.5 29 C 30.300781 29 31 28.300781 31 27.5 C 31 26.699219 30.300781 26 29.5 26 Z M 38.5 26 C 37.699219 26 37 26.699219 37 27.5 C 37 28.300781 37.699219 29 38.5 29 C 39.300781 29 40 28.300781 40 27.5 C 40 26.699219 39.300781 26 38.5 26 Z\" /></svg>"), IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 19 6 C 9.625 6 2 12.503906 2 20.5 C 2 24.769531 4.058594 28.609375 7.816406 31.390625 L 5.179688 39.304688 L 13.425781 34.199219 C 15.714844 34.917969 18.507813 35.171875 21.203125 34.875 C 23.390625 39.109375 28.332031 42 34 42 C 35.722656 42 37.316406 41.675781 38.796875 41.234375 L 45.644531 45.066406 L 43.734375 38.515625 C 46.3125 36.375 48 33.394531 48 30 C 48 23.789063 42.597656 18.835938 35.75 18.105469 C 34.40625 11.152344 27.367188 6 19 6 Z M 13 14 C 14.101563 14 15 14.898438 15 16 C 15 17.101563 14.101563 18 13 18 C 11.898438 18 11 17.101563 11 16 C 11 14.898438 11.898438 14 13 14 Z M 25 14 C 26.101563 14 27 14.898438 27 16 C 27 17.101563 26.101563 18 25 18 C 23.898438 18 23 17.101563 23 16 C 23 14.898438 23.898438 14 25 14 Z M 34 20 C 40.746094 20 46 24.535156 46 30 C 46 32.957031 44.492188 35.550781 42.003906 37.394531 L 41.445313 37.8125 L 42.355469 40.933594 L 39.105469 39.109375 L 38.683594 39.25 C 37.285156 39.71875 35.6875 40 34 40 C 27.253906 40 22 35.464844 22 30 C 22 24.535156 27.253906 20 34 20 Z M 29.5 26 C 28.699219 26 28 26.699219 28 27.5 C 28 28.300781 28.699219 29 29.5 29 C 30.300781 29 31 28.300781 31 27.5 C 31 26.699219 30.300781 26 29.5 26 Z M 38.5 26 C 37.699219 26 37 26.699219 37 27.5 C 37 28.300781 37.699219 29 38.5 29 C 39.300781 29 40 28.300781 40 27.5 C 40 26.699219 39.300781 26 38.5 26 Z\" /></svg>"),
Rules: []string{ Rules: []string{
"||wechat.com^", "||wechat.com^",
"||weixin.qq.com.cn^",
"||weixin.qq.com^", "||weixin.qq.com^",
"||weixinbridge.com^",
"||wx.qq.com^", "||wx.qq.com^",
}, },
}, { }, {
@@ -445,7 +482,9 @@ var blockedServices = []blockedService{{
Name: "Weibo", Name: "Weibo",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 35 6 C 34.222656 6 33.472656 6.078125 32.75 6.207031 C 32.207031 6.300781 31.84375 6.820313 31.9375 7.363281 C 32.03125 7.910156 32.550781 8.273438 33.09375 8.179688 C 33.726563 8.066406 34.359375 8 35 8 C 41.085938 8 46 12.914063 46 19 C 46 20.316406 45.757813 21.574219 45.328125 22.753906 C 45.195313 23.09375 45.253906 23.476563 45.484375 23.757813 C 45.71875 24.039063 46.082031 24.171875 46.441406 24.105469 C 46.800781 24.039063 47.09375 23.78125 47.207031 23.4375 C 47.710938 22.054688 48 20.566406 48 19 C 48 11.832031 42.167969 6 35 6 Z M 35 12 C 34.574219 12 34.171875 12.042969 33.789063 12.109375 C 33.246094 12.207031 32.878906 12.722656 32.976563 13.269531 C 33.070313 13.8125 33.589844 14.175781 34.132813 14.082031 C 34.425781 14.03125 34.714844 14 35 14 C 37.773438 14 40 16.226563 40 19 C 40 19.597656 39.890625 20.167969 39.691406 20.707031 C 39.503906 21.226563 39.773438 21.800781 40.292969 21.988281 C 40.8125 22.175781 41.386719 21.910156 41.574219 21.390625 C 41.84375 20.648438 42 19.84375 42 19 C 42 15.144531 38.855469 12 35 12 Z M 21.175781 12.40625 C 17.964844 12.34375 13.121094 14.878906 8.804688 19.113281 C 4.511719 23.40625 2 27.90625 2 31.78125 C 2 39.3125 11.628906 43.8125 21.152344 43.8125 C 33.5 43.8125 41.765625 36.699219 41.765625 31.046875 C 41.765625 27.59375 38.835938 25.707031 36.21875 24.871094 C 35.59375 24.660156 35.175781 24.558594 35.488281 23.71875 C 35.695313 23.21875 36 22.265625 36 21 C 36 19.5625 35 18.316406 33 18.09375 C 32.007813 17.984375 28 18 25.339844 19.113281 C 25.339844 19.113281 23.871094 19.746094 24.289063 18.59375 C 25.023438 16.292969 24.917969 14.40625 23.765625 13.359375 C 23.140625 12.730469 22.25 12.425781 21.175781 12.40625 Z M 20.3125 23.933594 C 28.117188 23.933594 34.441406 27.914063 34.441406 32.828125 C 34.441406 37.738281 28.117188 41.71875 20.3125 41.71875 C 12.511719 41.71875 6.1875 37.738281 6.1875 32.828125 C 6.1875 27.914063 12.511719 23.933594 20.3125 23.933594 Z M 19.265625 26.023438 C 16.246094 26.046875 13.3125 27.699219 12.039063 30.246094 C 10.46875 33.484375 11.933594 37.042969 15.699219 38.191406 C 19.464844 39.445313 23.960938 37.5625 25.53125 34.113281 C 27.097656 30.769531 25.113281 27.214844 21.347656 26.277344 C 20.660156 26.097656 19.960938 26.019531 19.265625 26.023438 Z M 20.824219 30.25 C 21.402344 30.25 21.871094 30.714844 21.871094 31.292969 C 21.871094 31.871094 21.402344 32.339844 20.824219 32.339844 C 20.246094 32.339844 19.777344 31.871094 19.777344 31.292969 C 19.777344 30.714844 20.246094 30.25 20.824219 30.25 Z M 16.417969 31.292969 C 16.746094 31.296875 17.074219 31.347656 17.382813 31.453125 C 18.722656 31.878906 19.132813 33.148438 18.308594 34.207031 C 17.589844 35.265625 15.945313 35.792969 14.707031 35.265625 C 13.476563 34.738281 13.167969 33.464844 13.886719 32.515625 C 14.425781 31.71875 15.429688 31.28125 16.417969 31.292969 Z\" /></svg>"), IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 35 6 C 34.222656 6 33.472656 6.078125 32.75 6.207031 C 32.207031 6.300781 31.84375 6.820313 31.9375 7.363281 C 32.03125 7.910156 32.550781 8.273438 33.09375 8.179688 C 33.726563 8.066406 34.359375 8 35 8 C 41.085938 8 46 12.914063 46 19 C 46 20.316406 45.757813 21.574219 45.328125 22.753906 C 45.195313 23.09375 45.253906 23.476563 45.484375 23.757813 C 45.71875 24.039063 46.082031 24.171875 46.441406 24.105469 C 46.800781 24.039063 47.09375 23.78125 47.207031 23.4375 C 47.710938 22.054688 48 20.566406 48 19 C 48 11.832031 42.167969 6 35 6 Z M 35 12 C 34.574219 12 34.171875 12.042969 33.789063 12.109375 C 33.246094 12.207031 32.878906 12.722656 32.976563 13.269531 C 33.070313 13.8125 33.589844 14.175781 34.132813 14.082031 C 34.425781 14.03125 34.714844 14 35 14 C 37.773438 14 40 16.226563 40 19 C 40 19.597656 39.890625 20.167969 39.691406 20.707031 C 39.503906 21.226563 39.773438 21.800781 40.292969 21.988281 C 40.8125 22.175781 41.386719 21.910156 41.574219 21.390625 C 41.84375 20.648438 42 19.84375 42 19 C 42 15.144531 38.855469 12 35 12 Z M 21.175781 12.40625 C 17.964844 12.34375 13.121094 14.878906 8.804688 19.113281 C 4.511719 23.40625 2 27.90625 2 31.78125 C 2 39.3125 11.628906 43.8125 21.152344 43.8125 C 33.5 43.8125 41.765625 36.699219 41.765625 31.046875 C 41.765625 27.59375 38.835938 25.707031 36.21875 24.871094 C 35.59375 24.660156 35.175781 24.558594 35.488281 23.71875 C 35.695313 23.21875 36 22.265625 36 21 C 36 19.5625 35 18.316406 33 18.09375 C 32.007813 17.984375 28 18 25.339844 19.113281 C 25.339844 19.113281 23.871094 19.746094 24.289063 18.59375 C 25.023438 16.292969 24.917969 14.40625 23.765625 13.359375 C 23.140625 12.730469 22.25 12.425781 21.175781 12.40625 Z M 20.3125 23.933594 C 28.117188 23.933594 34.441406 27.914063 34.441406 32.828125 C 34.441406 37.738281 28.117188 41.71875 20.3125 41.71875 C 12.511719 41.71875 6.1875 37.738281 6.1875 32.828125 C 6.1875 27.914063 12.511719 23.933594 20.3125 23.933594 Z M 19.265625 26.023438 C 16.246094 26.046875 13.3125 27.699219 12.039063 30.246094 C 10.46875 33.484375 11.933594 37.042969 15.699219 38.191406 C 19.464844 39.445313 23.960938 37.5625 25.53125 34.113281 C 27.097656 30.769531 25.113281 27.214844 21.347656 26.277344 C 20.660156 26.097656 19.960938 26.019531 19.265625 26.023438 Z M 20.824219 30.25 C 21.402344 30.25 21.871094 30.714844 21.871094 31.292969 C 21.871094 31.871094 21.402344 32.339844 20.824219 32.339844 C 20.246094 32.339844 19.777344 31.871094 19.777344 31.292969 C 19.777344 30.714844 20.246094 30.25 20.824219 30.25 Z M 16.417969 31.292969 C 16.746094 31.296875 17.074219 31.347656 17.382813 31.453125 C 18.722656 31.878906 19.132813 33.148438 18.308594 34.207031 C 17.589844 35.265625 15.945313 35.792969 14.707031 35.265625 C 13.476563 34.738281 13.167969 33.464844 13.886719 32.515625 C 14.425781 31.71875 15.429688 31.28125 16.417969 31.292969 Z\" /></svg>"),
Rules: []string{ Rules: []string{
"||weibo.cn^",
"||weibo.com^", "||weibo.com^",
"||weibocdn.com^",
}, },
}, { }, {
ID: "whatsapp", ID: "whatsapp",
@@ -461,11 +500,21 @@ var blockedServices = []blockedService{{
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M19.695 4.04S15.348 3.2 12 3.2s-7.695.84-7.695.84L1.602 7.2v9.6l2.703 3.16s4.347.84 7.695.84 7.695-.84 7.695-.84l2.703-3.16V12 7.2zM9.602 15.68V8.32L16 12zm0 0\" /><path d=\"M19.2 4a3.198 3.198 0 1 0 0 6.398c1.769 0 3.198-1.43 3.198-3.199C22.398 5.434 20.968 4 19.2 4zm0 9.602a3.198 3.198 0 1 0 0 6.398c1.769 0 3.198-1.434 3.198-3.2 0-1.769-1.43-3.198-3.199-3.198zM1.601 7.199c0 1.77 1.43 3.2 3.199 3.2 1.765 0 2.398-1.43 2.398-3.2C7.2 5.434 6.566 4 4.801 4 3.03 4 1.6 5.434 1.6 7.2zM4.8 13.602c-1.77 0-3.2 1.43-3.2 3.199A3.198 3.198 0 1 0 8 16.8c0-1.77-1.434-3.2-3.2-3.2zm0 0\" /></svg>"), IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M19.695 4.04S15.348 3.2 12 3.2s-7.695.84-7.695.84L1.602 7.2v9.6l2.703 3.16s4.347.84 7.695.84 7.695-.84 7.695-.84l2.703-3.16V12 7.2zM9.602 15.68V8.32L16 12zm0 0\" /><path d=\"M19.2 4a3.198 3.198 0 1 0 0 6.398c1.769 0 3.198-1.43 3.198-3.199C22.398 5.434 20.968 4 19.2 4zm0 9.602a3.198 3.198 0 1 0 0 6.398c1.769 0 3.198-1.434 3.198-3.2 0-1.769-1.43-3.198-3.199-3.198zM1.601 7.199c0 1.77 1.43 3.2 3.199 3.2 1.765 0 2.398-1.43 2.398-3.2C7.2 5.434 6.566 4 4.801 4 3.03 4 1.6 5.434 1.6 7.2zM4.8 13.602c-1.77 0-3.2 1.43-3.2 3.199A3.198 3.198 0 1 0 8 16.8c0-1.77-1.434-3.2-3.2-3.2zm0 0\" /></svg>"),
Rules: []string{ Rules: []string{
"||googlevideo.com^", "||googlevideo.com^",
"||wide-youtube.l.google.com^",
"||youtu.be^", "||youtu.be^",
"||youtube", "||youtube",
"||youtube-nocookie.com^", "||youtube-nocookie.com^",
"||youtube.com^", "||youtube.com^",
"||youtubei.googleapis.com^", "||youtubei.googleapis.com^",
"||youtubekids.com^",
"||ytimg.com^", "||ytimg.com^",
}, },
}, {
ID: "zhihu",
Name: "Zhihu",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 30 30\"><path d=\"M14.46 14.191H9.982c0-.471.033-.954.039-1.458v-5.5h5.106V5.935a1.352 1.352 0 00-.404-.957 1.378 1.378 0 00-.968-.396H5.783c.028-.088.056-.177.084-.255.274-.82 1.153-3.326 1.153-3.326a4.262 4.262 0 00-2.413.698c-.57.4-.912.682-1.371 1.946-.532 1.453-.997 2.856-1.31 3.693C1.444 8.674.28 11.025.28 11.025a5.85 5.85 0 002.52-.61c1.119-.593 1.679-1.502 2.054-2.883l.09-.3h2.334v5.5c0 .5-.045.982-.073 1.46h-4.12c-.71 0-1.39.278-1.893.775a2.638 2.638 0 00-.783 1.874h6.527a17.717 17.717 0 01-.778 3.649 16.796 16.796 0 01-3.012 5.273A33.104 33.104 0 010 28.74s3.13 1.175 5.425-.954c1.388-1.292 2.631-3.814 3.23-5.727a28.09 28.09 0 001.12-5.229h5.967v-1.37a1.254 1.254 0 00-.373-.899 1.279 1.279 0 00-.909-.37zm-3.19 5.484l-2.312 1.491 5.038 7.458a6.905 6.905 0 00.672-2.218 3.15 3.15 0 00-.28-2.168l-3.118-4.563zM29.05 4.582H16.733V25.94h3.018l.403 2.572 4.081-2.572h4.815V4.582zm-5.207 18.69l-2.396 1.509-.235-1.508h-1.724V7.233h6.78v16.04h-2.425z\" /></svg>"),
Rules: []string{
"||zhihu.com^",
"||zhimg.com^",
},
}} }}

View File

@@ -129,7 +129,7 @@ type RuntimeClientWHOISInfo struct {
type clientsContainer struct { type clientsContainer struct {
// TODO(a.garipov): Perhaps use a number of separate indices for // TODO(a.garipov): Perhaps use a number of separate indices for
// different types (string, net.IP, and so on). // different types (string, netip.Addr, and so on).
list map[string]*Client // name -> client list map[string]*Client // name -> client
idIndex map[string]*Client // ID -> client idIndex map[string]*Client // ID -> client
@@ -333,7 +333,7 @@ func (clients *clientsContainer) onDHCPLeaseChanged(flags int) {
} }
// exists checks if client with this IP address already exists. // exists checks if client with this IP address already exists.
func (clients *clientsContainer) exists(ip net.IP, source clientSource) (ok bool) { func (clients *clientsContainer) exists(ip netip.Addr, source clientSource) (ok bool) {
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
@@ -342,7 +342,7 @@ func (clients *clientsContainer) exists(ip net.IP, source clientSource) (ok bool
return true return true
} }
rc, ok := clients.findRuntimeClientLocked(ip) rc, ok := clients.ipToRC[ip]
if !ok { if !ok {
return false return false
} }
@@ -371,7 +371,8 @@ func (clients *clientsContainer) findMultiple(ids []string) (c *querylog.Client,
var artClient *querylog.Client var artClient *querylog.Client
var art bool var art bool
for _, id := range ids { for _, id := range ids {
c, art = clients.clientOrArtificial(net.ParseIP(id), id) ip, _ := netip.ParseAddr(id)
c, art = clients.clientOrArtificial(ip, id)
if art { if art {
artClient = c artClient = c
@@ -389,7 +390,7 @@ func (clients *clientsContainer) findMultiple(ids []string) (c *querylog.Client,
// records about this client besides maybe whether or not it is blocked. c is // records about this client besides maybe whether or not it is blocked. c is
// never nil. // never nil.
func (clients *clientsContainer) clientOrArtificial( func (clients *clientsContainer) clientOrArtificial(
ip net.IP, ip netip.Addr,
id string, id string,
) (c *querylog.Client, art bool) { ) (c *querylog.Client, art bool) {
defer func() { defer func() {
@@ -406,13 +407,6 @@ func (clients *clientsContainer) clientOrArtificial(
}, false }, false
} }
if ip == nil {
// Technically should never happen, but still.
return &querylog.Client{
Name: "",
}, true
}
var rc *RuntimeClient var rc *RuntimeClient
rc, ok = clients.findRuntimeClient(ip) rc, ok = clients.findRuntimeClient(ip)
if ok { if ok {
@@ -492,19 +486,20 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
return c, true return c, true
} }
ip := net.ParseIP(id) ip, err := netip.ParseAddr(id)
if ip == nil { if err != nil {
return nil, false return nil, false
} }
for _, c = range clients.list { for _, c = range clients.list {
for _, id := range c.IDs { for _, id := range c.IDs {
_, ipnet, err := net.ParseCIDR(id) var n netip.Prefix
n, err = netip.ParsePrefix(id)
if err != nil { if err != nil {
continue continue
} }
if ipnet.Contains(ip) { if n.Contains(ip) {
return c, true return c, true
} }
} }
@@ -514,19 +509,20 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
return nil, false return nil, false
} }
macFound := clients.dhcpServer.FindMACbyIP(ip) macFound := clients.dhcpServer.FindMACbyIP(ip.AsSlice())
if macFound == nil { if macFound == nil {
return nil, false return nil, false
} }
for _, c = range clients.list { for _, c = range clients.list {
for _, id := range c.IDs { for _, id := range c.IDs {
hwAddr, err := net.ParseMAC(id) var mac net.HardwareAddr
mac, err = net.ParseMAC(id)
if err != nil { if err != nil {
continue continue
} }
if bytes.Equal(hwAddr, macFound) { if bytes.Equal(mac, macFound) {
return c, true return c, true
} }
} }
@@ -535,32 +531,18 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
return nil, false return nil, false
} }
// findRuntimeClientLocked finds a runtime client by their IP address. For
// internal use only.
func (clients *clientsContainer) findRuntimeClientLocked(ip net.IP) (rc *RuntimeClient, ok bool) {
// TODO(a.garipov): Remove once we switch to netip.Addr more fully.
ipAddr, err := netutil.IPToAddrNoMapped(ip)
if err != nil {
log.Error("clients: bad client ip %v: %s", ip, err)
return nil, false
}
rc, ok = clients.ipToRC[ipAddr]
return rc, ok
}
// findRuntimeClient finds a runtime client by their IP. // findRuntimeClient finds a runtime client by their IP.
func (clients *clientsContainer) findRuntimeClient(ip net.IP) (rc *RuntimeClient, ok bool) { func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) {
if ip == nil { if ip == (netip.Addr{}) {
return nil, false return nil, false
} }
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
return clients.findRuntimeClientLocked(ip) rc, ok = clients.ipToRC[ip]
return rc, ok
} }
// check validates the client. // check validates the client.
@@ -578,14 +560,16 @@ func (clients *clientsContainer) check(c *Client) (err error) {
for i, id := range c.IDs { for i, id := range c.IDs {
// Normalize structured data. // Normalize structured data.
var ip net.IP var (
var ipnet *net.IPNet ip netip.Addr
var mac net.HardwareAddr n netip.Prefix
if ip = net.ParseIP(id); ip != nil { mac net.HardwareAddr
)
if ip, err = netip.ParseAddr(id); err == nil {
c.IDs[i] = ip.String() c.IDs[i] = ip.String()
} else if ip, ipnet, err = net.ParseCIDR(id); err == nil { } else if n, err = netip.ParsePrefix(id); err == nil {
ipnet.IP = ip c.IDs[i] = n.String()
c.IDs[i] = ipnet.String()
} else if mac, err = net.ParseMAC(id); err == nil { } else if mac, err = net.ParseMAC(id); err == nil {
c.IDs[i] = mac.String() c.IDs[i] = mac.String()
} else if err = dnsforward.ValidateClientID(id); err == nil { } else if err = dnsforward.ValidateClientID(id); err == nil {
@@ -750,7 +734,7 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
} }
// setWHOISInfo sets the WHOIS information for a client. // setWHOISInfo sets the WHOIS information for a client.
func (clients *clientsContainer) setWHOISInfo(ip net.IP, wi *RuntimeClientWHOISInfo) { func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) {
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
@@ -760,7 +744,7 @@ func (clients *clientsContainer) setWHOISInfo(ip net.IP, wi *RuntimeClientWHOISI
return return
} }
rc, ok := clients.findRuntimeClientLocked(ip) rc, ok := clients.ipToRC[ip]
if ok { if ok {
rc.WHOISInfo = wi rc.WHOISInfo = wi
log.Debug("clients: set whois info for runtime client %s: %+v", rc.Host, wi) log.Debug("clients: set whois info for runtime client %s: %+v", rc.Host, wi)
@@ -776,32 +760,22 @@ func (clients *clientsContainer) setWHOISInfo(ip net.IP, wi *RuntimeClientWHOISI
rc.WHOISInfo = wi rc.WHOISInfo = wi
// TODO(a.garipov): Remove once we switch to netip.Addr more fully. clients.ipToRC[ip] = rc
ipAddr, err := netutil.IPToAddrNoMapped(ip)
if err != nil {
log.Error("clients: bad client ip %v: %s", ip, err)
return
}
clients.ipToRC[ipAddr] = rc
log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi) log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi)
} }
// AddHost adds a new IP-hostname pairing. The priorities of the sources are // AddHost adds a new IP-hostname pairing. The priorities of the sources are
// taken into account. ok is true if the pairing was added. // taken into account. ok is true if the pairing was added.
func (clients *clientsContainer) AddHost(ip net.IP, host string, src clientSource) (ok bool, err error) { func (clients *clientsContainer) AddHost(
ip netip.Addr,
host string,
src clientSource,
) (ok bool) {
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
// TODO(a.garipov): Remove once we switch to netip.Addr more fully. return clients.addHostLocked(ip, host, src)
ipAddr, err := netutil.IPToAddrNoMapped(ip)
if err != nil {
return false, fmt.Errorf("adding host: %w", err)
}
return clients.addHostLocked(ipAddr, host, src), nil
} }
// addHostLocked adds a new IP-hostname pairing. clients.lock is expected to be // addHostLocked adds a new IP-hostname pairing. clients.lock is expected to be

View File

@@ -22,8 +22,18 @@ func TestClients(t *testing.T) {
clients.Init(nil, nil, nil, nil) clients.Init(nil, nil, nil, nil)
t.Run("add_success", func(t *testing.T) { t.Run("add_success", func(t *testing.T) {
var (
cliNone = "1.2.3.4"
cli1 = "1.1.1.1"
cli2 = "2.2.2.2"
cliNoneIP = netip.MustParseAddr(cliNone)
cli1IP = netip.MustParseAddr(cli1)
cli2IP = netip.MustParseAddr(cli2)
)
c := &Client{ c := &Client{
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"}, IDs: []string{cli1, "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
Name: "client1", Name: "client1",
} }
@@ -33,7 +43,7 @@ func TestClients(t *testing.T) {
assert.True(t, ok) assert.True(t, ok)
c = &Client{ c = &Client{
IDs: []string{"2.2.2.2"}, IDs: []string{cli2},
Name: "client2", Name: "client2",
} }
@@ -42,7 +52,7 @@ func TestClients(t *testing.T) {
assert.True(t, ok) assert.True(t, ok)
c, ok = clients.Find("1.1.1.1") c, ok = clients.Find(cli1)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "client1", c.Name) assert.Equal(t, "client1", c.Name)
@@ -52,14 +62,14 @@ func TestClients(t *testing.T) {
assert.Equal(t, "client1", c.Name) assert.Equal(t, "client1", c.Name)
c, ok = clients.Find("2.2.2.2") c, ok = clients.Find(cli2)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "client2", c.Name) assert.Equal(t, "client2", c.Name)
assert.False(t, clients.exists(net.IP{1, 2, 3, 4}, ClientSourceHostsFile)) assert.False(t, clients.exists(cliNoneIP, ClientSourceHostsFile))
assert.True(t, clients.exists(net.IP{1, 1, 1, 1}, ClientSourceHostsFile)) assert.True(t, clients.exists(cli1IP, ClientSourceHostsFile))
assert.True(t, clients.exists(net.IP{2, 2, 2, 2}, ClientSourceHostsFile)) assert.True(t, clients.exists(cli2IP, ClientSourceHostsFile))
}) })
t.Run("add_fail_name", func(t *testing.T) { t.Run("add_fail_name", func(t *testing.T) {
@@ -103,23 +113,31 @@ func TestClients(t *testing.T) {
}) })
t.Run("update_success", func(t *testing.T) { t.Run("update_success", func(t *testing.T) {
var (
cliOld = "1.1.1.1"
cliNew = "1.1.1.2"
cliOldIP = netip.MustParseAddr(cliOld)
cliNewIP = netip.MustParseAddr(cliNew)
)
err := clients.Update("client1", &Client{ err := clients.Update("client1", &Client{
IDs: []string{"1.1.1.2"}, IDs: []string{cliNew},
Name: "client1", Name: "client1",
}) })
require.NoError(t, err) require.NoError(t, err)
assert.False(t, clients.exists(net.IP{1, 1, 1, 1}, ClientSourceHostsFile)) assert.False(t, clients.exists(cliOldIP, ClientSourceHostsFile))
assert.True(t, clients.exists(net.IP{1, 1, 1, 2}, ClientSourceHostsFile)) assert.True(t, clients.exists(cliNewIP, ClientSourceHostsFile))
err = clients.Update("client1", &Client{ err = clients.Update("client1", &Client{
IDs: []string{"1.1.1.2"}, IDs: []string{cliNew},
Name: "client1-renamed", Name: "client1-renamed",
UseOwnSettings: true, UseOwnSettings: true,
}) })
require.NoError(t, err) require.NoError(t, err)
c, ok := clients.Find("1.1.1.2") c, ok := clients.Find(cliNew)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "client1-renamed", c.Name) assert.Equal(t, "client1-renamed", c.Name)
@@ -132,14 +150,14 @@ func TestClients(t *testing.T) {
require.Len(t, c.IDs, 1) require.Len(t, c.IDs, 1)
assert.Equal(t, "1.1.1.2", c.IDs[0]) assert.Equal(t, cliNew, c.IDs[0])
}) })
t.Run("del_success", func(t *testing.T) { t.Run("del_success", func(t *testing.T) {
ok := clients.Del("client1-renamed") ok := clients.Del("client1-renamed")
require.True(t, ok) require.True(t, ok)
assert.False(t, clients.exists(net.IP{1, 1, 1, 2}, ClientSourceHostsFile)) assert.False(t, clients.exists(netip.MustParseAddr("1.1.1.2"), ClientSourceHostsFile))
}) })
t.Run("del_fail", func(t *testing.T) { t.Run("del_fail", func(t *testing.T) {
@@ -148,45 +166,33 @@ func TestClients(t *testing.T) {
}) })
t.Run("addhost_success", func(t *testing.T) { t.Run("addhost_success", func(t *testing.T) {
ip := net.IP{1, 1, 1, 1} ip := netip.MustParseAddr("1.1.1.1")
ok := clients.AddHost(ip, "host", ClientSourceARP)
ok, err := clients.AddHost(ip, "host", ClientSourceARP)
require.NoError(t, err)
assert.True(t, ok) assert.True(t, ok)
ok, err = clients.AddHost(ip, "host2", ClientSourceARP) ok = clients.AddHost(ip, "host2", ClientSourceARP)
require.NoError(t, err)
assert.True(t, ok) assert.True(t, ok)
ok, err = clients.AddHost(ip, "host3", ClientSourceHostsFile) ok = clients.AddHost(ip, "host3", ClientSourceHostsFile)
require.NoError(t, err)
assert.True(t, ok) assert.True(t, ok)
assert.True(t, clients.exists(ip, ClientSourceHostsFile)) assert.True(t, clients.exists(ip, ClientSourceHostsFile))
}) })
t.Run("dhcp_replaces_arp", func(t *testing.T) { t.Run("dhcp_replaces_arp", func(t *testing.T) {
ip := net.IP{1, 2, 3, 4} ip := netip.MustParseAddr("1.2.3.4")
ok := clients.AddHost(ip, "from_arp", ClientSourceARP)
ok, err := clients.AddHost(ip, "from_arp", ClientSourceARP)
require.NoError(t, err)
assert.True(t, ok) assert.True(t, ok)
assert.True(t, clients.exists(ip, ClientSourceARP)) assert.True(t, clients.exists(ip, ClientSourceARP))
ok, err = clients.AddHost(ip, "from_dhcp", ClientSourceDHCP) ok = clients.AddHost(ip, "from_dhcp", ClientSourceDHCP)
require.NoError(t, err)
assert.True(t, ok) assert.True(t, ok)
assert.True(t, clients.exists(ip, ClientSourceDHCP)) assert.True(t, clients.exists(ip, ClientSourceDHCP))
}) })
t.Run("addhost_fail", func(t *testing.T) { t.Run("addhost_fail", func(t *testing.T) {
ok, err := clients.AddHost(net.IP{1, 1, 1, 1}, "host1", ClientSourceRDNS) ip := netip.MustParseAddr("1.1.1.1")
require.NoError(t, err) ok := clients.AddHost(ip, "host1", ClientSourceRDNS)
assert.False(t, ok) assert.False(t, ok)
}) })
} }
@@ -203,7 +209,7 @@ func TestClientsWHOIS(t *testing.T) {
t.Run("new_client", func(t *testing.T) { t.Run("new_client", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.255") ip := netip.MustParseAddr("1.1.1.255")
clients.setWHOISInfo(ip.AsSlice(), whois) clients.setWHOISInfo(ip, whois)
rc := clients.ipToRC[ip] rc := clients.ipToRC[ip]
require.NotNil(t, rc) require.NotNil(t, rc)
@@ -212,12 +218,10 @@ func TestClientsWHOIS(t *testing.T) {
t.Run("existing_auto-client", func(t *testing.T) { t.Run("existing_auto-client", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1") ip := netip.MustParseAddr("1.1.1.1")
ok, err := clients.AddHost(ip.AsSlice(), "host", ClientSourceRDNS) ok := clients.AddHost(ip, "host", ClientSourceRDNS)
require.NoError(t, err)
assert.True(t, ok) assert.True(t, ok)
clients.setWHOISInfo(ip.AsSlice(), whois) clients.setWHOISInfo(ip, whois)
rc := clients.ipToRC[ip] rc := clients.ipToRC[ip]
require.NotNil(t, rc) require.NotNil(t, rc)
@@ -234,7 +238,7 @@ func TestClientsWHOIS(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.True(t, ok) assert.True(t, ok)
clients.setWHOISInfo(ip.AsSlice(), whois) clients.setWHOISInfo(ip, whois)
rc := clients.ipToRC[ip] rc := clients.ipToRC[ip]
require.Nil(t, rc) require.Nil(t, rc)
@@ -249,7 +253,7 @@ func TestClientsAddExisting(t *testing.T) {
clients.Init(nil, nil, nil, nil) clients.Init(nil, nil, nil, nil)
t.Run("simple", func(t *testing.T) { t.Run("simple", func(t *testing.T) {
ip := net.IP{1, 1, 1, 1} ip := netip.MustParseAddr("1.1.1.1")
// Add a client. // Add a client.
ok, err := clients.Add(&Client{ ok, err := clients.Add(&Client{
@@ -260,8 +264,7 @@ func TestClientsAddExisting(t *testing.T) {
assert.True(t, ok) assert.True(t, ok)
// Now add an auto-client with the same IP. // Now add an auto-client with the same IP.
ok, err = clients.AddHost(ip, "test", ClientSourceRDNS) ok = clients.AddHost(ip, "test", ClientSourceRDNS)
require.NoError(t, err)
assert.True(t, ok) assert.True(t, ok)
}) })

View File

@@ -3,8 +3,8 @@ package home
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net"
"net/http" "net/http"
"net/netip"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
) )
@@ -47,8 +47,8 @@ type runtimeClientJSON struct {
WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info"` WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info"`
Name string `json:"name"` Name string `json:"name"`
IP netip.Addr `json:"ip"`
Source clientSource `json:"source"` Source clientSource `json:"source"`
IP net.IP `json:"ip"`
} }
type clientListJSON struct { type clientListJSON struct {
@@ -75,7 +75,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
Name: rc.Host, Name: rc.Host,
Source: rc.Source, Source: rc.Source,
IP: ip.AsSlice(), IP: ip,
} }
data.RuntimeClients = append(data.RuntimeClients, cj) data.RuntimeClients = append(data.RuntimeClients, cj)
@@ -218,7 +218,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
break break
} }
ip := net.ParseIP(idStr) ip, _ := netip.ParseAddr(idStr)
c, ok := clients.Find(idStr) c, ok := clients.Find(idStr)
var cj *clientJSON var cj *clientJSON
if !ok { if !ok {
@@ -240,7 +240,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
// findRuntime looks up the IP in runtime and temporary storages, like // findRuntime looks up the IP in runtime and temporary storages, like
// /etc/hosts tables, DHCP leases, or blocklists. cj is guaranteed to be // /etc/hosts tables, DHCP leases, or blocklists. cj is guaranteed to be
// non-nil. // non-nil.
func (clients *clientsContainer) findRuntime(ip net.IP, idStr string) (cj *clientJSON) { func (clients *clientsContainer) findRuntime(ip netip.Addr, idStr string) (cj *clientJSON) {
rc, ok := clients.findRuntimeClient(ip) rc, ok := clients.findRuntimeClient(ip)
if !ok { if !ok {
// It is still possible that the IP used to be in the runtime clients // It is still possible that the IP used to be in the runtime clients

View File

@@ -20,6 +20,7 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
"github.com/google/renameio/maybe" "github.com/google/renameio/maybe"
"golang.org/x/exp/slices"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
@@ -113,8 +114,8 @@ type configuration struct {
// An active session is automatically refreshed once a day. // An active session is automatically refreshed once a day.
WebSessionTTLHours uint32 `yaml:"web_session_ttl"` WebSessionTTLHours uint32 `yaml:"web_session_ttl"`
DNS dnsConfig `yaml:"dns"` DNS dnsConfig `yaml:"dns"`
TLS tlsConfigSettings `yaml:"tls"` TLS tlsConfiguration `yaml:"tls"`
// Filters reflects the filters from [filtering.Config]. It's cloned to the // Filters reflects the filters from [filtering.Config]. It's cloned to the
// config used in the filtering module at the startup. Afterwards it's // config used in the filtering module at the startup. Afterwards it's
@@ -199,7 +200,8 @@ type dnsConfig struct {
UseHTTP3Upstreams bool `yaml:"use_http3_upstreams"` UseHTTP3Upstreams bool `yaml:"use_http3_upstreams"`
} }
type tlsConfigSettings struct { // tlsConfiguration is the on-disk TLS configuration.
type tlsConfiguration struct {
Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DoT/DoH/HTTPS) status Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DoT/DoH/HTTPS) status
ServerName string `yaml:"server_name" json:"server_name,omitempty"` // ServerName is the hostname of your HTTPS/TLS server ServerName string `yaml:"server_name" json:"server_name,omitempty"` // ServerName is the hostname of your HTTPS/TLS server
ForceHTTPS bool `yaml:"force_https" json:"force_https"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect ForceHTTPS bool `yaml:"force_https" json:"force_https"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect
@@ -223,6 +225,29 @@ type tlsConfigSettings struct {
dnsforward.TLSConfig `yaml:",inline" json:",inline"` dnsforward.TLSConfig `yaml:",inline" json:",inline"`
} }
// cloneForEncoding returns a clone of c with all top-level fields of c and all
// exported and YAML-encoded fields of c.TLSConfig cloned.
//
// TODO(a.garipov): This is better than races, but still not good enough.
func (c *tlsConfiguration) cloneForEncoding() (cloned *tlsConfiguration) {
if c == nil {
return nil
}
v := *c
cloned = &v
cloned.TLSConfig = dnsforward.TLSConfig{
CertificateChain: c.CertificateChain,
PrivateKey: c.PrivateKey,
CertificatePath: c.CertificatePath,
PrivateKeyPath: c.PrivateKeyPath,
OverrideTLSCiphers: slices.Clone(c.OverrideTLSCiphers),
StrictSNICheck: c.StrictSNICheck,
}
return cloned
}
// config is the global configuration structure. // config is the global configuration structure.
// //
// TODO(a.garipov, e.burkov): This global is awful and must be removed. // TODO(a.garipov, e.burkov): This global is awful and must be removed.
@@ -273,7 +298,7 @@ var config = &configuration{
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout}, UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
UsePrivateRDNS: true, UsePrivateRDNS: true,
}, },
TLS: tlsConfigSettings{ TLS: tlsConfiguration{
PortHTTPS: defaultPortHTTPS, PortHTTPS: defaultPortHTTPS,
PortDNSOverTLS: defaultPortTLS, // needs to be passed through to dnsproxy PortDNSOverTLS: defaultPortTLS, // needs to be passed through to dnsproxy
PortDNSOverQUIC: defaultPortQUIC, PortDNSOverQUIC: defaultPortQUIC,
@@ -442,7 +467,7 @@ func (c *configuration) write() (err error) {
} }
if Context.tls != nil { if Context.tls != nil {
tlsConf := tlsConfigSettings{} tlsConf := tlsConfiguration{}
Context.tls.WriteDiskConfig(&tlsConf) Context.tls.WriteDiskConfig(&tlsConf)
config.TLS = tlsConf config.TLS = tlsConf
} }

View File

@@ -71,9 +71,7 @@ func appendDNSAddrsWithIfaces(dst []string, src []netip.Addr) (res []string, err
// on, including the addresses on all interfaces in cases of unspecified IPs. // on, including the addresses on all interfaces in cases of unspecified IPs.
func collectDNSAddresses() (addrs []string, err error) { func collectDNSAddresses() (addrs []string, err error) {
if hosts := config.DNS.BindHosts; len(hosts) == 0 { if hosts := config.DNS.BindHosts; len(hosts) == 0 {
addr := aghnet.IPv4Localhost() addrs = appendDNSAddrs(addrs, netutil.IPv4Localhost())
addrs = appendDNSAddrs(addrs, addr)
} else { } else {
addrs, err = appendDNSAddrsWithIfaces(addrs, hosts) addrs, err = appendDNSAddrsWithIfaces(addrs, hosts)
if err != nil { if err != nil {

View File

@@ -154,7 +154,7 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
return nil return nil
} }
tlsConf := &tlsConfigSettings{} tlsConf := &tlsConfiguration{}
Context.tls.WriteDiskConfig(tlsConf) Context.tls.WriteDiskConfig(tlsConf)
canUpdate := true canUpdate := true
@@ -172,7 +172,7 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
// tlsConfUsesPrivilegedPorts returns true if the provided TLS configuration // tlsConfUsesPrivilegedPorts returns true if the provided TLS configuration
// indicates that privileged ports are used. // indicates that privileged ports are used.
func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) { func tlsConfUsesPrivilegedPorts(c *tlsConfiguration) (ok bool) {
return c.Enabled && (c.PortHTTPS < 1024 || c.PortDNSOverTLS < 1024 || c.PortDNSOverQUIC < 1024) return c.Enabled && (c.PortHTTPS < 1024 || c.PortDNSOverTLS < 1024 || c.PortDNSOverQUIC < 1024)
} }

View File

@@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
@@ -150,8 +151,8 @@ func isRunning() bool {
} }
func onDNSRequest(pctx *proxy.DNSContext) { func onDNSRequest(pctx *proxy.DNSContext) {
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr) ip := netutil.NetAddrToAddrPort(pctx.Addr).Addr()
if ip == nil { if ip == (netip.Addr{}) {
// This would be quite weird if we get here. // This would be quite weird if we get here.
return return
} }
@@ -160,7 +161,8 @@ func onDNSRequest(pctx *proxy.DNSContext) {
if srcs.RDNS && !ip.IsLoopback() { if srcs.RDNS && !ip.IsLoopback() {
Context.rdns.Begin(ip) Context.rdns.Begin(ip)
} }
if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) {
if srcs.WHOIS && !netutil.IsSpecialPurposeAddr(ip) {
Context.whois.Begin(ip) Context.whois.Begin(ip)
} }
} }
@@ -193,11 +195,7 @@ func ipsToUDPAddrs(ips []netip.Addr, port int) (udpAddrs []*net.UDPAddr) {
func generateServerConfig() (newConf dnsforward.ServerConfig, err error) { func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
dnsConf := config.DNS dnsConf := config.DNS
hosts := dnsConf.BindHosts hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
if len(hosts) == 0 {
hosts = []netip.Addr{aghnet.IPv4Localhost()}
}
newConf = dnsforward.ServerConfig{ newConf = dnsforward.ServerConfig{
UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port), UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port),
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port), TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
@@ -207,7 +205,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
OnDNSRequest: onDNSRequest, OnDNSRequest: onDNSRequest,
} }
tlsConf := tlsConfigSettings{} tlsConf := tlsConfiguration{}
Context.tls.WriteDiskConfig(&tlsConf) Context.tls.WriteDiskConfig(&tlsConf)
if tlsConf.Enabled { if tlsConf.Enabled {
newConf.TLSConfig = tlsConf.TLSConfig newConf.TLSConfig = tlsConf.TLSConfig
@@ -252,7 +250,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
return newConf, nil return newConf, nil
} }
func newDNSCrypt(hosts []netip.Addr, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) { func newDNSCrypt(hosts []netip.Addr, tlsConf tlsConfiguration) (dnscc dnsforward.DNSCryptConfig, err error) {
if tlsConf.DNSCryptConfigFile == "" { if tlsConf.DNSCryptConfigFile == "" {
return dnscc, errors.Error("no dnscrypt_config_file") return dnscc, errors.Error("no dnscrypt_config_file")
} }
@@ -290,7 +288,7 @@ type dnsEncryption struct {
} }
func getDNSEncryption() (de dnsEncryption) { func getDNSEncryption() (de dnsEncryption) {
tlsConf := tlsConfigSettings{} tlsConf := tlsConfiguration{}
Context.tls.WriteDiskConfig(&tlsConf) Context.tls.WriteDiskConfig(&tlsConf)
@@ -400,15 +398,12 @@ func startDNSServer() error {
const topClientsNumber = 100 // the number of clients to get const topClientsNumber = 100 // the number of clients to get
for _, ip := range Context.stats.TopClientsIP(topClientsNumber) { for _, ip := range Context.stats.TopClientsIP(topClientsNumber) {
if ip == nil {
continue
}
srcs := config.Clients.Sources srcs := config.Clients.Sources
if srcs.RDNS && !ip.IsLoopback() { if srcs.RDNS && !ip.IsLoopback() {
Context.rdns.Begin(ip) Context.rdns.Begin(ip)
} }
if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) {
if srcs.WHOIS && !netutil.IsSpecialPurposeAddr(ip) {
Context.whois.Begin(ip) Context.whois.Begin(ip)
} }
} }

View File

@@ -76,9 +76,7 @@ type homeContext struct {
configFilename string // Config filename (can be overridden via the command line arguments) configFilename string // Config filename (can be overridden via the command line arguments)
workDir string // Location of our directory, used to protect against CWD being somewhere else workDir string // Location of our directory, used to protect against CWD being somewhere else
firstRun bool // if set to true, don't run any services except HTTP web interface, and serve only first-run html
pidFileName string // PID file name. Empty if no PID file was created. pidFileName string // PID file name. Empty if no PID file was created.
disableUpdate bool // If set, don't check for updates
controlLock sync.Mutex controlLock sync.Mutex
tlsRoots *x509.CertPool // list of root CAs for TLSv1.2 tlsRoots *x509.CertPool // list of root CAs for TLSv1.2
transport *http.Transport transport *http.Transport
@@ -88,6 +86,13 @@ type homeContext struct {
// tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use. // tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use.
tlsCipherIDs []uint16 tlsCipherIDs []uint16
// disableUpdate, if true, tells AdGuard Home to not check for updates.
disableUpdate bool
// firstRun, if true, tells AdGuard Home to only start the web interface
// service, and only serve the first-run APIs.
firstRun bool
// runningAsService flag is set to true when options are passed from the service runner // runningAsService flag is set to true when options are passed from the service runner
runningAsService bool runningAsService bool
} }
@@ -462,6 +467,15 @@ func run(opts options, clientBuildFS fs.FS) {
mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace) mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
// See profileSupportsDelta in src/net/http/pprof/pprof.go.
mux.Handle("/debug/pprof/allocs", pprof.Handler("allocs"))
mux.Handle("/debug/pprof/block", pprof.Handler("block"))
mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
go func() { go func() {
log.Info("pprof: listening on localhost:6060") log.Info("pprof: listening on localhost:6060")
lerr := http.ListenAndServe("localhost:6060", mux) lerr := http.ListenAndServe("localhost:6060", mux)
@@ -498,7 +512,7 @@ func run(opts options, clientBuildFS fs.FS) {
} }
config.Users = nil config.Users = nil
Context.tls, err = newTLSManager(config.TLS) Context.tls, err = newTLSManager(&config.TLS)
if err != nil { if err != nil {
log.Fatalf("initializing tls: %s", err) log.Fatalf("initializing tls: %s", err)
} }
@@ -528,6 +542,11 @@ func run(opts options, clientBuildFS fs.FS) {
} }
} }
// TODO(a.garipov): This could be made much earlier and could be done on
// the first run as well, but to achieve this we need to bypass requests
// over dnsforward resolver.
cmdlineUpdate(opts)
Context.web.Start() Context.web.Start()
// wait indefinitely for other go-routines to complete their job // wait indefinitely for other go-routines to complete their job
@@ -562,7 +581,7 @@ func checkPermissions() {
} }
// We should check if AdGuard Home is able to bind to port 53 // We should check if AdGuard Home is able to bind to port 53
err := aghnet.CheckPort("tcp", netip.AddrPortFrom(aghnet.IPv4Localhost(), defaultPortDNS)) err := aghnet.CheckPort("tcp", netip.AddrPortFrom(netutil.IPv4Localhost(), defaultPortDNS))
if err != nil { if err != nil {
if errors.Is(err, os.ErrPermission) { if errors.Is(err, os.ErrPermission) {
log.Fatal(`Permission check failed. log.Fatal(`Permission check failed.
@@ -798,7 +817,7 @@ func printWebAddrs(proto, addr string, port, betaPort int) {
// printHTTPAddresses prints the IP addresses which user can use to access the // printHTTPAddresses prints the IP addresses which user can use to access the
// admin interface. proto is either schemeHTTP or schemeHTTPS. // admin interface. proto is either schemeHTTP or schemeHTTPS.
func printHTTPAddresses(proto string) { func printHTTPAddresses(proto string) {
tlsConf := tlsConfigSettings{} tlsConf := tlsConfiguration{}
if Context.tls != nil { if Context.tls != nil {
Context.tls.WriteDiskConfig(&tlsConf) Context.tls.WriteDiskConfig(&tlsConf)
} }
@@ -913,3 +932,37 @@ type jsonError struct {
// Message is the error message, an opaque string. // Message is the error message, an opaque string.
Message string `json:"message"` Message string `json:"message"`
} }
// cmdlineUpdate updates current application and exits.
func cmdlineUpdate(opts options) {
if !opts.performUpdate {
return
}
log.Info("starting update")
if Context.firstRun {
log.Info("update not allowed on first run")
os.Exit(0)
}
_, err := Context.updater.VersionInfo(true)
if err != nil {
vcu := Context.updater.VersionCheckURL()
log.Error("getting version info from %s: %s", vcu, err)
os.Exit(0)
}
if Context.updater.NewVersion() == "" {
log.Info("no updates available")
os.Exit(0)
}
err = Context.updater.Update()
fatalOnError(err)
os.Exit(0)
}

View File

@@ -32,7 +32,11 @@ func setupDNSIPs(t testing.TB) {
}, },
} }
Context.tls = &tlsManager{} var err error
Context.tls, err = newTLSManager(&tlsConfiguration{
Enabled: true,
})
require.NoError(t, err)
} }
func TestHandleMobileConfigDoH(t *testing.T) { func TestHandleMobileConfigDoH(t *testing.T) {
@@ -65,7 +69,11 @@ func TestHandleMobileConfigDoH(t *testing.T) {
oldTLSConf := Context.tls oldTLSConf := Context.tls
t.Cleanup(func() { Context.tls = oldTLSConf }) t.Cleanup(func() { Context.tls = oldTLSConf })
Context.tls = &tlsManager{conf: tlsConfigSettings{}} var err error
Context.tls, err = newTLSManager(&tlsConfiguration{
Enabled: true,
})
require.NoError(t, err)
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil) r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil)
require.NoError(t, err) require.NoError(t, err)
@@ -137,7 +145,11 @@ func TestHandleMobileConfigDoT(t *testing.T) {
oldTLSConf := Context.tls oldTLSConf := Context.tls
t.Cleanup(func() { Context.tls = oldTLSConf }) t.Cleanup(func() { Context.tls = oldTLSConf })
Context.tls = &tlsManager{conf: tlsConfigSettings{}} var err error
Context.tls, err = newTLSManager(&tlsConfiguration{
Enabled: true,
})
require.NoError(t, err)
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil) r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
require.NoError(t, err) require.NoError(t, err)

View File

@@ -47,6 +47,9 @@ type options struct {
// disableUpdate, if set, makes AdGuard Home not check for updates. // disableUpdate, if set, makes AdGuard Home not check for updates.
disableUpdate bool disableUpdate bool
// performUpdate, if set, updates AdGuard Home without GUI and exits.
performUpdate bool
// verbose shows if verbose logging is enabled. // verbose shows if verbose logging is enabled.
verbose bool verbose bool
@@ -221,6 +224,14 @@ var cmdLineOpts = []cmdLineOpt{{
description: "Don't check for updates.", description: "Don't check for updates.",
longName: "no-check-update", longName: "no-check-update",
shortName: "", shortName: "",
}, {
updateWithValue: nil,
updateNoValue: func(o options) (options, error) { o.performUpdate = true; return o, nil },
effect: nil,
serialize: func(o options) (val string, ok bool) { return "", o.performUpdate },
description: "Update application and exit.",
longName: "update",
shortName: "",
}, { }, {
updateWithValue: nil, updateWithValue: nil,
updateNoValue: nil, updateNoValue: nil,

View File

@@ -103,6 +103,11 @@ func TestParseDisableUpdate(t *testing.T) {
assert.True(t, testParseOK(t, "--no-check-update").disableUpdate, "--no-check-update is disable update") assert.True(t, testParseOK(t, "--no-check-update").disableUpdate, "--no-check-update is disable update")
} }
func TestParsePerformUpdate(t *testing.T) {
assert.False(t, testParseOK(t).performUpdate, "empty is not perform update")
assert.True(t, testParseOK(t, "--update").performUpdate, "--update is perform update")
}
// 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 := parseCmdOpts("", []string{"--no-mem-optimization"})
@@ -169,6 +174,10 @@ func TestOptsToArgs(t *testing.T) {
name: "disable_update", name: "disable_update",
args: []string{"--no-check-update"}, args: []string{"--no-check-update"},
opts: options{disableUpdate: true}, opts: options{disableUpdate: true},
}, {
name: "perform_update",
args: []string{"--update"},
opts: options{performUpdate: true},
}, { }, {
name: "control_action", name: "control_action",
args: []string{"-s", "run"}, args: []string{"-s", "run"},

View File

@@ -2,7 +2,7 @@ package home
import ( import (
"encoding/binary" "encoding/binary"
"net" "net/netip"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -21,7 +21,7 @@ type RDNS struct {
usePrivate uint32 usePrivate uint32
// ipCh used to pass client's IP to rDNS workerLoop. // ipCh used to pass client's IP to rDNS workerLoop.
ipCh chan net.IP ipCh chan netip.Addr
// ipCache caches the IP addresses to be resolved by rDNS. The resolved // ipCache caches the IP addresses to be resolved by rDNS. The resolved
// address stays here while it's inside clients. After leaving clients the // address stays here while it's inside clients. After leaving clients the
@@ -50,7 +50,7 @@ func NewRDNS(
EnableLRU: true, EnableLRU: true,
MaxCount: defaultRDNSCacheSize, MaxCount: defaultRDNSCacheSize,
}), }),
ipCh: make(chan net.IP, defaultRDNSIPChSize), ipCh: make(chan netip.Addr, defaultRDNSIPChSize),
} }
if usePrivate { if usePrivate {
rDNS.usePrivate = 1 rDNS.usePrivate = 1
@@ -80,9 +80,10 @@ func (r *RDNS) ensurePrivateCache() {
// isCached returns true if ip is already cached and not expired yet. It also // isCached returns true if ip is already cached and not expired yet. It also
// caches it otherwise. // caches it otherwise.
func (r *RDNS) isCached(ip net.IP) (ok bool) { func (r *RDNS) isCached(ip netip.Addr) (ok bool) {
ipBytes := ip.AsSlice()
now := uint64(time.Now().Unix()) now := uint64(time.Now().Unix())
if expire := r.ipCache.Get(ip); len(expire) != 0 { if expire := r.ipCache.Get(ipBytes); len(expire) != 0 {
if binary.BigEndian.Uint64(expire) > now { if binary.BigEndian.Uint64(expire) > now {
return true return true
} }
@@ -91,14 +92,14 @@ func (r *RDNS) isCached(ip net.IP) (ok bool) {
// The cache entry either expired or doesn't exist. // The cache entry either expired or doesn't exist.
ttl := make([]byte, 8) ttl := make([]byte, 8)
binary.BigEndian.PutUint64(ttl, now+defaultRDNSCacheTTL) binary.BigEndian.PutUint64(ttl, now+defaultRDNSCacheTTL)
r.ipCache.Set(ip, ttl) r.ipCache.Set(ipBytes, ttl)
return false return false
} }
// Begin adds the ip to the resolving queue if it is not cached or already // Begin adds the ip to the resolving queue if it is not cached or already
// resolved. // resolved.
func (r *RDNS) Begin(ip net.IP) { func (r *RDNS) Begin(ip netip.Addr) {
r.ensurePrivateCache() r.ensurePrivateCache()
if r.isCached(ip) || r.clients.exists(ip, ClientSourceRDNS) { if r.isCached(ip) || r.clients.exists(ip, ClientSourceRDNS) {
@@ -107,9 +108,9 @@ func (r *RDNS) Begin(ip net.IP) {
select { select {
case r.ipCh <- ip: case r.ipCh <- ip:
log.Tracef("rdns: %q added to queue", ip) log.Debug("rdns: %q added to queue", ip)
default: default:
log.Tracef("rdns: queue is full") log.Debug("rdns: queue is full")
} }
} }
@@ -119,7 +120,7 @@ func (r *RDNS) workerLoop() {
defer log.OnPanic("rdns") defer log.OnPanic("rdns")
for ip := range r.ipCh { for ip := range r.ipCh {
host, err := r.exchanger.Exchange(ip) host, err := r.exchanger.Exchange(ip.AsSlice())
if err != nil { if err != nil {
log.Debug("rdns: resolving %q: %s", ip, err) log.Debug("rdns: resolving %q: %s", ip, err)
@@ -128,8 +129,6 @@ func (r *RDNS) workerLoop() {
continue continue
} }
// Don't handle any errors since AddHost doesn't return non-nil errors _ = r.clients.AddHost(ip, host, ClientSourceRDNS)
// for now.
_, _ = r.clients.AddHost(ip, host, ClientSourceRDNS)
} }
} }

View File

@@ -27,14 +27,14 @@ func TestRDNS_Begin(t *testing.T) {
w := &bytes.Buffer{} w := &bytes.Buffer{}
aghtest.ReplaceLogWriter(t, w) aghtest.ReplaceLogWriter(t, w)
ip1234, ip1235 := net.IP{1, 2, 3, 4}, net.IP{1, 2, 3, 5} ip1234, ip1235 := netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("1.2.3.5")
testCases := []struct { testCases := []struct {
cliIDIndex map[string]*Client cliIDIndex map[string]*Client
customChan chan net.IP customChan chan netip.Addr
name string name string
wantLog string wantLog string
req net.IP ip netip.Addr
wantCacheHit int wantCacheHit int
wantCacheMiss int wantCacheMiss int
}{{ }{{
@@ -42,7 +42,7 @@ func TestRDNS_Begin(t *testing.T) {
customChan: nil, customChan: nil,
name: "cached", name: "cached",
wantLog: "", wantLog: "",
req: ip1234, ip: ip1234,
wantCacheHit: 1, wantCacheHit: 1,
wantCacheMiss: 0, wantCacheMiss: 0,
}, { }, {
@@ -50,7 +50,7 @@ func TestRDNS_Begin(t *testing.T) {
customChan: nil, customChan: nil,
name: "not_cached", name: "not_cached",
wantLog: "rdns: queue is full", wantLog: "rdns: queue is full",
req: ip1235, ip: ip1235,
wantCacheHit: 0, wantCacheHit: 0,
wantCacheMiss: 1, wantCacheMiss: 1,
}, { }, {
@@ -58,15 +58,15 @@ func TestRDNS_Begin(t *testing.T) {
customChan: nil, customChan: nil,
name: "already_in_clients", name: "already_in_clients",
wantLog: "", wantLog: "",
req: ip1235, ip: ip1235,
wantCacheHit: 0, wantCacheHit: 0,
wantCacheMiss: 1, wantCacheMiss: 1,
}, { }, {
cliIDIndex: map[string]*Client{}, cliIDIndex: map[string]*Client{},
customChan: make(chan net.IP, 1), customChan: make(chan netip.Addr, 1),
name: "add_to_queue", name: "add_to_queue",
wantLog: `rdns: "1.2.3.5" added to queue`, wantLog: `rdns: "1.2.3.5" added to queue`,
req: ip1235, ip: ip1235,
wantCacheHit: 0, wantCacheHit: 0,
wantCacheMiss: 1, wantCacheMiss: 1,
}} }}
@@ -102,7 +102,7 @@ func TestRDNS_Begin(t *testing.T) {
} }
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
rdns.Begin(tc.req) rdns.Begin(tc.ip)
assert.Equal(t, tc.wantCacheHit, ipCache.Stats().Hit) assert.Equal(t, tc.wantCacheHit, ipCache.Stats().Hit)
assert.Equal(t, tc.wantCacheMiss, ipCache.Stats().Miss) assert.Equal(t, tc.wantCacheMiss, ipCache.Stats().Miss)
assert.Contains(t, w.String(), tc.wantLog) assert.Contains(t, w.String(), tc.wantLog)
@@ -179,8 +179,8 @@ func TestRDNS_WorkerLoop(t *testing.T) {
w := &bytes.Buffer{} w := &bytes.Buffer{}
aghtest.ReplaceLogWriter(t, w) aghtest.ReplaceLogWriter(t, w)
localIP := net.IP{192, 168, 1, 1} localIP := netip.MustParseAddr("192.168.1.1")
revIPv4, err := netutil.IPToReversedAddr(localIP) revIPv4, err := netutil.IPToReversedAddr(localIP.AsSlice())
require.NoError(t, err) require.NoError(t, err)
revIPv6, err := netutil.IPToReversedAddr(net.ParseIP("2a00:1450:400c:c06::93")) revIPv6, err := netutil.IPToReversedAddr(net.ParseIP("2a00:1450:400c:c06::93"))
@@ -201,24 +201,24 @@ func TestRDNS_WorkerLoop(t *testing.T) {
testCases := []struct { testCases := []struct {
ups upstream.Upstream ups upstream.Upstream
cliIP netip.Addr
wantLog string wantLog string
name string name string
cliIP net.IP
}{{ }{{
ups: locUpstream, ups: locUpstream,
cliIP: localIP,
wantLog: "", wantLog: "",
name: "all_good", name: "all_good",
cliIP: localIP,
}, { }, {
ups: errUpstream, ups: errUpstream,
cliIP: netip.MustParseAddr("192.168.1.2"),
wantLog: `rdns: resolving "192.168.1.2": test upstream error`, wantLog: `rdns: resolving "192.168.1.2": test upstream error`,
name: "resolve_error", name: "resolve_error",
cliIP: net.IP{192, 168, 1, 2},
}, { }, {
ups: locUpstream, ups: locUpstream,
cliIP: netip.MustParseAddr("2a00:1450:400c:c06::93"),
wantLog: "", wantLog: "",
name: "ipv6_good", name: "ipv6_good",
cliIP: net.ParseIP("2a00:1450:400c:c06::93"),
}} }}
for _, tc := range testCases { for _, tc := range testCases {
@@ -230,7 +230,7 @@ func TestRDNS_WorkerLoop(t *testing.T) {
ipToRC: map[netip.Addr]*RuntimeClient{}, ipToRC: map[netip.Addr]*RuntimeClient{},
allTags: stringutil.NewSet(), allTags: stringutil.NewSet(),
} }
ch := make(chan net.IP) ch := make(chan netip.Addr)
rdns := &RDNS{ rdns := &RDNS{
exchanger: &rDNSExchanger{ exchanger: &rDNSExchanger{
ex: tc.ups, ex: tc.ups,

View File

@@ -8,42 +8,39 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"net/http"
"os" "os"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls" "github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/google/go-cmp/cmp"
) )
// tlsManager contains the current configuration and state of AdGuard Home TLS // tlsManager contains the current configuration and state of AdGuard Home TLS
// encryption. // encryption.
type tlsManager struct { type tlsManager struct {
// status is the current status of the configuration. It is never nil. // mu protects all fields.
status *tlsConfigStatus mu *sync.RWMutex
// certLastMod is the last modification time of the certificate file. // certLastMod is the last modification time of the certificate file.
certLastMod time.Time certLastMod time.Time
confLock sync.Mutex // status is the current status of the configuration. It is never nil.
conf tlsConfigSettings status *tlsConfigStatus
// conf is the current TLS configuration.
conf *tlsConfiguration
} }
// newTLSManager initializes the TLS configuration. // newTLSManager initializes the TLS configuration.
func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) { func newTLSManager(conf *tlsConfiguration) (m *tlsManager, err error) {
m = &tlsManager{ m = &tlsManager{
status: &tlsConfigStatus{}, status: &tlsConfigStatus{},
mu: &sync.RWMutex{},
conf: conf, conf: conf,
} }
@@ -59,9 +56,19 @@ func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) {
return m, nil return m, nil
} }
// confForEncoding returns a partial clone of the current TLS configuration. It
// is safe for concurrent use.
func (m *tlsManager) confForEncoding() (conf *tlsConfiguration) {
m.mu.RLock()
defer m.mu.RUnlock()
return m.conf.cloneForEncoding()
}
// load reloads the TLS configuration from files or data from the config file. // load reloads the TLS configuration from files or data from the config file.
// m.mu is expected to be locked for writing.
func (m *tlsManager) load() (err error) { func (m *tlsManager) load() (err error) {
err = loadTLSConf(&m.conf, m.status) err = loadTLSConf(m.conf, m.status)
if err != nil { if err != nil {
return fmt.Errorf("loading config: %w", err) return fmt.Errorf("loading config: %w", err)
} }
@@ -70,14 +77,12 @@ func (m *tlsManager) load() (err error) {
} }
// WriteDiskConfig - write config // WriteDiskConfig - write config
func (m *tlsManager) WriteDiskConfig(conf *tlsConfigSettings) { func (m *tlsManager) WriteDiskConfig(conf *tlsConfiguration) {
m.confLock.Lock() *conf = *m.confForEncoding()
*conf = m.conf
m.confLock.Unlock()
} }
// setCertFileTime sets t.certLastMod from the certificate. If there are // setCertFileTime sets t.certLastMod from the certificate. If there are
// errors, setCertFileTime logs them. // errors, setCertFileTime logs them. mu is expected to be locked for writing.
func (m *tlsManager) setCertFileTime() { func (m *tlsManager) setCertFileTime() {
if len(m.conf.CertificatePath) == 0 { if len(m.conf.CertificatePath) == 0 {
return return
@@ -97,27 +102,22 @@ func (m *tlsManager) setCertFileTime() {
func (m *tlsManager) start() { func (m *tlsManager) start() {
m.registerWebHandlers() m.registerWebHandlers()
m.confLock.Lock()
tlsConf := m.conf
m.confLock.Unlock()
// The background context is used because the TLSConfigChanged wraps context // The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current // with timeout on its own and shuts down the server, which handles current
// request. // request.
Context.web.TLSConfigChanged(context.Background(), tlsConf) Context.web.TLSConfigChanged(context.Background(), m.confForEncoding())
} }
// reload updates the configuration and restarts t. // reload updates the configuration and restarts m.
func (m *tlsManager) reload() { func (m *tlsManager) reload() {
m.confLock.Lock() m.mu.Lock()
tlsConf := m.conf defer m.mu.Unlock()
m.confLock.Unlock()
if !tlsConf.Enabled || len(tlsConf.CertificatePath) == 0 { if !m.conf.Enabled || len(m.conf.CertificatePath) == 0 {
return return
} }
fi, err := os.Stat(tlsConf.CertificatePath) fi, err := os.Stat(m.conf.CertificatePath)
if err != nil { if err != nil {
log.Error("tls: %s", err) log.Error("tls: %s", err)
@@ -132,9 +132,7 @@ func (m *tlsManager) reload() {
log.Debug("tls: certificate file is modified") log.Debug("tls: certificate file is modified")
m.confLock.Lock()
err = m.load() err = m.load()
m.confLock.Unlock()
if err != nil { if err != nil {
log.Error("tls: reloading: %s", err) log.Error("tls: reloading: %s", err)
@@ -145,19 +143,15 @@ func (m *tlsManager) reload() {
_ = reconfigureDNSServer() _ = reconfigureDNSServer()
m.confLock.Lock()
tlsConf = m.conf
m.confLock.Unlock()
// The background context is used because the TLSConfigChanged wraps context // The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current // with timeout on its own and shuts down the server, which handles current
// request. // request.
Context.web.TLSConfigChanged(context.Background(), tlsConf) Context.web.TLSConfigChanged(context.Background(), m.conf)
} }
// loadTLSConf loads and validates the TLS configuration. The returned error is // loadTLSConf loads and validates the TLS configuration. The returned error is
// also set in status.WarningValidation. // also set in status.WarningValidation.
func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error) { func loadTLSConf(tlsConf *tlsConfiguration, status *tlsConfigStatus) (err error) {
defer func() { defer func() {
if err != nil { if err != nil {
status.WarningValidation = err.Error() status.WarningValidation = err.Error()
@@ -172,13 +166,10 @@ func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error
tlsConf.PrivateKeyData = []byte(tlsConf.PrivateKey) tlsConf.PrivateKeyData = []byte(tlsConf.PrivateKey)
if tlsConf.CertificatePath != "" { if tlsConf.CertificatePath != "" {
if tlsConf.CertificateChain != "" { err = loadCert(tlsConf)
return errors.Error("certificate data and file can't be set together")
}
tlsConf.CertificateChainData, err = os.ReadFile(tlsConf.CertificatePath)
if err != nil { if err != nil {
return fmt.Errorf("reading cert file: %w", err) // Don't wrap the error, since it's informative enough as is.
return err
} }
// Set status.ValidCert to true to signal the frontend that the // Set status.ValidCert to true to signal the frontend that the
@@ -187,13 +178,10 @@ func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error
} }
if tlsConf.PrivateKeyPath != "" { if tlsConf.PrivateKeyPath != "" {
if tlsConf.PrivateKey != "" { err = loadPKey(tlsConf)
return errors.Error("private key data and file can't be set together")
}
tlsConf.PrivateKeyData, err = os.ReadFile(tlsConf.PrivateKeyPath)
if err != nil { if err != nil {
return fmt.Errorf("reading key file: %w", err) // Don't wrap the error, since it's informative enough as is.
return err
} }
status.ValidKey = true status.ValidKey = true
@@ -212,278 +200,29 @@ func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error
return nil return nil
} }
// tlsConfigStatus contains the status of a certificate chain and key pair. // loadCert loads the certificate from file, if necessary.
type tlsConfigStatus struct { func loadCert(tlsConf *tlsConfiguration) (err error) {
// Subject is the subject of the first certificate in the chain. if tlsConf.CertificateChain != "" {
Subject string `json:"subject,omitempty"` return errors.Error("certificate data and file can't be set together")
// Issuer is the issuer of the first certificate in the chain.
Issuer string `json:"issuer,omitempty"`
// KeyType is the type of the private key.
KeyType string `json:"key_type,omitempty"`
// NotBefore is the NotBefore field of the first certificate in the chain.
NotBefore time.Time `json:"not_before,omitempty"`
// NotAfter is the NotAfter field of the first certificate in the chain.
NotAfter time.Time `json:"not_after,omitempty"`
// WarningValidation is a validation warning message with the issue
// description.
WarningValidation string `json:"warning_validation,omitempty"`
// DNSNames is the value of SubjectAltNames field of the first certificate
// in the chain.
DNSNames []string `json:"dns_names"`
// ValidCert is true if the specified certificate chain is a valid chain of
// X509 certificates.
ValidCert bool `json:"valid_cert"`
// ValidChain is true if the specified certificate chain is verified and
// issued by a known CA.
ValidChain bool `json:"valid_chain"`
// ValidKey is true if the key is a valid private key.
ValidKey bool `json:"valid_key"`
// ValidPair is true if both certificate and private key are correct for
// each other.
ValidPair bool `json:"valid_pair"`
}
// tlsConfig is the TLS configuration and status response.
type tlsConfig struct {
*tlsConfigStatus `json:",inline"`
tlsConfigSettingsExt `json:",inline"`
}
// tlsConfigSettingsExt is used to (un)marshal the PrivateKeySaved field to
// ensure that clients don't send and receive previously saved private keys.
type tlsConfigSettingsExt struct {
tlsConfigSettings `json:",inline"`
// PrivateKeySaved is true if the private key is saved as a string and omit
// key from answer.
PrivateKeySaved bool `yaml:"-" json:"private_key_saved,inline"`
}
func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
m.confLock.Lock()
data := tlsConfig{
tlsConfigSettingsExt: tlsConfigSettingsExt{
tlsConfigSettings: m.conf,
},
tlsConfigStatus: m.status,
} }
m.confLock.Unlock()
marshalTLS(w, r, data) tlsConf.CertificateChainData, err = os.ReadFile(tlsConf.CertificatePath)
}
func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts, err := unmarshalTLS(r)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err) return fmt.Errorf("reading cert file: %w", err)
return
} }
if setts.PrivateKeySaved { return nil
setts.PrivateKey = m.conf.PrivateKey
}
if setts.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(setts.PortHTTPS),
tcpPort(setts.PortDNSOverTLS),
tcpPort(setts.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(setts.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
if !webCheckPortAvailable(setts.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable HTTPS on it",
setts.PortHTTPS,
)
return
}
// Skip the error check, since we are only interested in the value of
// status.WarningValidation.
status := &tlsConfigStatus{}
_ = loadTLSConf(&setts.tlsConfigSettings, status)
resp := tlsConfig{
tlsConfigSettingsExt: setts,
tlsConfigStatus: status,
}
marshalTLS(w, r, resp)
} }
func (m *tlsManager) setConfig(newConf tlsConfigSettings, status *tlsConfigStatus) (restartHTTPS bool) { // loadPKey loads the private key from file, if necessary.
m.confLock.Lock() func loadPKey(tlsConf *tlsConfiguration) (err error) {
defer m.confLock.Unlock() if tlsConf.PrivateKey != "" {
return errors.Error("private key data and file cannot be set together")
// Reset the DNSCrypt data before comparing, since we currently do not
// accept these from the frontend.
//
// TODO(a.garipov): Define a custom comparer for dnsforward.TLSConfig.
newConf.DNSCryptConfigFile = m.conf.DNSCryptConfigFile
newConf.PortDNSCrypt = m.conf.PortDNSCrypt
if !cmp.Equal(m.conf, newConf, cmp.AllowUnexported(dnsforward.TLSConfig{})) {
log.Info("tls config has changed, restarting https server")
restartHTTPS = true
} else {
log.Info("tls: config has not changed")
} }
// Note: don't do just `t.conf = data` because we must preserve all other members of t.conf tlsConf.PrivateKeyData, err = os.ReadFile(tlsConf.PrivateKeyPath)
m.conf.Enabled = newConf.Enabled
m.conf.ServerName = newConf.ServerName
m.conf.ForceHTTPS = newConf.ForceHTTPS
m.conf.PortHTTPS = newConf.PortHTTPS
m.conf.PortDNSOverTLS = newConf.PortDNSOverTLS
m.conf.PortDNSOverQUIC = newConf.PortDNSOverQUIC
m.conf.CertificateChain = newConf.CertificateChain
m.conf.CertificatePath = newConf.CertificatePath
m.conf.CertificateChainData = newConf.CertificateChainData
m.conf.PrivateKey = newConf.PrivateKey
m.conf.PrivateKeyPath = newConf.PrivateKeyPath
m.conf.PrivateKeyData = newConf.PrivateKeyData
m.status = status
return restartHTTPS
}
func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err) return fmt.Errorf("reading key file: %w", err)
return
}
if req.PrivateKeySaved {
req.PrivateKey = m.conf.PrivateKey
}
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(req.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
// TODO(e.burkov): Investigate and perhaps check other ports.
if !webCheckPortAvailable(req.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable https on it",
req.PortHTTPS,
)
return
}
status := &tlsConfigStatus{}
err = loadTLSConf(&req.tlsConfigSettings, status)
if err != nil {
resp := tlsConfig{
tlsConfigSettingsExt: req,
tlsConfigStatus: status,
}
marshalTLS(w, r, resp)
return
}
restartHTTPS := m.setConfig(req.tlsConfigSettings, status)
m.setCertFileTime()
onConfigModified()
err = reconfigureDNSServer()
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
resp := tlsConfig{
tlsConfigSettingsExt: req,
tlsConfigStatus: m.status,
}
marshalTLS(w, r, resp)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current
// request. It is also should be done in a separate goroutine due to the
// same reason.
if restartHTTPS {
go func() {
Context.web.TLSConfigChanged(context.Background(), req.tlsConfigSettings)
}()
}
}
// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
// DNS protocols.
func validatePorts(
bindPort, betaBindPort, dohPort, dotPort, dnscryptTCPPort tcpPort,
dnsPort, doqPort udpPort,
) (err error) {
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(
tcpPorts,
tcpPort(bindPort),
tcpPort(betaBindPort),
tcpPort(dohPort),
tcpPort(dotPort),
tcpPort(dnscryptTCPPort),
)
err = tcpPorts.Validate()
if err != nil {
return fmt.Errorf("validating tcp ports: %w", err)
}
udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(dnsPort), udpPort(doqPort))
err = udpPorts.Validate()
if err != nil {
return fmt.Errorf("validating udp ports: %w", err)
} }
return nil return nil
@@ -513,6 +252,11 @@ func validateCertChain(certs []*x509.Certificate, srvName string) (err error) {
return nil return nil
} }
// errNoIPInCert is the error that is returned from [parseCertChain] if the leaf
// certificate doesn't contain IPs.
const errNoIPInCert errors.Error = `certificates has no IP addresses; ` +
`DNS-over-TLS won't be advertised via DDR`
// parseCertChain parses the certificate chain from raw data, and returns it. // parseCertChain parses the certificate chain from raw data, and returns it.
// If ok is true, the returned error, if any, is not critical. // If ok is true, the returned error, if any, is not critical.
func parseCertChain(chain []byte) (parsedCerts []*x509.Certificate, ok bool, err error) { func parseCertChain(chain []byte) (parsedCerts []*x509.Certificate, ok bool, err error) {
@@ -535,8 +279,7 @@ func parseCertChain(chain []byte) (parsedCerts []*x509.Certificate, ok bool, err
log.Info("tls: number of certs: %d", len(parsedCerts)) log.Info("tls: number of certs: %d", len(parsedCerts))
if !aghtls.CertificateHasIP(parsedCerts[0]) { if !aghtls.CertificateHasIP(parsedCerts[0]) {
err = errors.Error(`certificate has no IP addresses` + err = errNoIPInCert
`, this may cause issues with DNS-over-TLS clients`)
} }
return parsedCerts, true, err return parsedCerts, true, err
@@ -696,61 +439,3 @@ func parsePrivateKey(der []byte) (key crypto.PrivateKey, typ string, err error)
return nil, "", errors.Error("tls: failed to parse private key") return nil, "", errors.Error("tls: failed to parse private key")
} }
// unmarshalTLS handles base64-encoded certificates transparently
func unmarshalTLS(r *http.Request) (tlsConfigSettingsExt, error) {
data := tlsConfigSettingsExt{}
err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
return data, fmt.Errorf("failed to parse new TLS config json: %w", err)
}
if data.CertificateChain != "" {
var cert []byte
cert, err = base64.StdEncoding.DecodeString(data.CertificateChain)
if err != nil {
return data, fmt.Errorf("failed to base64-decode certificate chain: %w", err)
}
data.CertificateChain = string(cert)
if data.CertificatePath != "" {
return data, fmt.Errorf("certificate data and file can't be set together")
}
}
if data.PrivateKey != "" {
var key []byte
key, err = base64.StdEncoding.DecodeString(data.PrivateKey)
if err != nil {
return data, fmt.Errorf("failed to base64-decode private key: %w", err)
}
data.PrivateKey = string(key)
if data.PrivateKeyPath != "" {
return data, fmt.Errorf("private key data and file can't be set together")
}
}
return data, nil
}
func marshalTLS(w http.ResponseWriter, r *http.Request, data tlsConfig) {
if data.CertificateChain != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(data.CertificateChain))
data.CertificateChain = encoded
}
if data.PrivateKey != "" {
data.PrivateKeySaved = true
data.PrivateKey = ""
}
_ = aghhttp.WriteJSONResponse(w, r, data)
}
// registerWebHandlers registers HTTP handlers for TLS configuration.
func (m *tlsManager) registerWebHandlers() {
httpRegister(http.MethodGet, "/control/tls/status", m.handleTLSStatus)
httpRegister(http.MethodPost, "/control/tls/configure", m.handleTLSConfigure)
httpRegister(http.MethodPost, "/control/tls/validate", m.handleTLSValidate)
}

362
internal/home/tlshttp.go Normal file
View File

@@ -0,0 +1,362 @@
package home
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/golibs/log"
"github.com/google/go-cmp/cmp"
)
// Encryption Settings HTTP API
// tlsConfigStatus contains the status of a certificate chain and key pair.
type tlsConfigStatus struct {
// Subject is the subject of the first certificate in the chain.
Subject string `json:"subject,omitempty"`
// Issuer is the issuer of the first certificate in the chain.
Issuer string `json:"issuer,omitempty"`
// KeyType is the type of the private key.
KeyType string `json:"key_type,omitempty"`
// NotBefore is the NotBefore field of the first certificate in the chain.
NotBefore time.Time `json:"not_before,omitempty"`
// NotAfter is the NotAfter field of the first certificate in the chain.
NotAfter time.Time `json:"not_after,omitempty"`
// WarningValidation is a validation warning message with the issue
// description.
WarningValidation string `json:"warning_validation,omitempty"`
// DNSNames is the value of SubjectAltNames field of the first certificate
// in the chain.
DNSNames []string `json:"dns_names"`
// ValidCert is true if the specified certificate chain is a valid chain of
// X509 certificates.
ValidCert bool `json:"valid_cert"`
// ValidChain is true if the specified certificate chain is verified and
// issued by a known CA.
ValidChain bool `json:"valid_chain"`
// ValidKey is true if the key is a valid private key.
ValidKey bool `json:"valid_key"`
// ValidPair is true if both certificate and private key are correct for
// each other.
ValidPair bool `json:"valid_pair"`
}
// tlsConfigResp is the TLS configuration and status response.
type tlsConfigResp struct {
*tlsConfigStatus
*tlsConfiguration
// PrivateKeySaved is true if the private key is saved as a string and omit
// key from answer.
PrivateKeySaved bool `yaml:"-" json:"private_key_saved"`
}
// tlsConfigReq is the TLS configuration request.
type tlsConfigReq struct {
tlsConfiguration
// PrivateKeySaved is true if the private key is saved as a string and omit
// key from answer.
PrivateKeySaved bool `yaml:"-" json:"private_key_saved"`
}
// handleTLSStatus is the handler for the GET /control/tls/status HTTP API.
func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
var resp *tlsConfigResp
func() {
m.mu.RLock()
defer m.mu.RUnlock()
resp = &tlsConfigResp{
tlsConfigStatus: m.status,
tlsConfiguration: m.conf.cloneForEncoding(),
}
}()
marshalTLS(w, r, resp)
}
// handleTLSValidate is the handler for the POST /control/tls/validate HTTP API.
func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
if req.PrivateKeySaved {
req.PrivateKey = m.confForEncoding().PrivateKey
}
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(req.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
if !webCheckPortAvailable(req.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable HTTPS on it",
req.PortHTTPS,
)
return
}
resp := &tlsConfigResp{
tlsConfigStatus: &tlsConfigStatus{},
tlsConfiguration: &req.tlsConfiguration,
}
// Skip the error check, since we are only interested in the value of
// resl.tlsConfigStatus.WarningValidation.
_ = loadTLSConf(resp.tlsConfiguration, resp.tlsConfigStatus)
marshalTLS(w, r, resp)
}
// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
// DNS protocols.
func validatePorts(
bindPort, betaBindPort, dohPort, dotPort, dnscryptTCPPort tcpPort,
dnsPort, doqPort udpPort,
) (err error) {
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(
tcpPorts,
tcpPort(bindPort),
tcpPort(betaBindPort),
tcpPort(dohPort),
tcpPort(dotPort),
tcpPort(dnscryptTCPPort),
)
err = tcpPorts.Validate()
if err != nil {
return fmt.Errorf("validating tcp ports: %w", err)
}
udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(dnsPort), udpPort(doqPort))
err = udpPorts.Validate()
if err != nil {
return fmt.Errorf("validating udp ports: %w", err)
}
return nil
}
// handleTLSConfigure is the handler for the POST /control/tls/configure HTTP
// API.
func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
if req.PrivateKeySaved {
req.PrivateKey = m.confForEncoding().PrivateKey
}
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(req.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
// TODO(e.burkov): Investigate and perhaps check other ports.
if !webCheckPortAvailable(req.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable https on it",
req.PortHTTPS,
)
return
}
resp := &tlsConfigResp{
tlsConfigStatus: &tlsConfigStatus{},
tlsConfiguration: &req.tlsConfiguration,
}
err = loadTLSConf(resp.tlsConfiguration, resp.tlsConfigStatus)
if err != nil {
marshalTLS(w, r, resp)
return
}
restartRequired := m.setConf(resp)
onConfigModified()
err = reconfigureDNSServer()
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
resp.tlsConfiguration = m.confForEncoding()
marshalTLS(w, r, resp)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current
// request. It is also should be done in a separate goroutine due to the
// same reason.
if restartRequired {
go func() {
Context.web.TLSConfigChanged(context.Background(), resp.tlsConfiguration)
}()
}
}
// setConf sets the necessary values from the new configuration.
func (m *tlsManager) setConf(newConf *tlsConfigResp) (restartRequired bool) {
m.mu.Lock()
defer m.mu.Unlock()
// Reset the DNSCrypt data before comparing, since we currently do not
// accept these from the frontend.
//
// TODO(a.garipov): Define a custom comparer for dnsforward.TLSConfig.
newConf.DNSCryptConfigFile = m.conf.DNSCryptConfigFile
newConf.PortDNSCrypt = m.conf.PortDNSCrypt
if !cmp.Equal(m.conf, newConf, cmp.AllowUnexported(dnsforward.TLSConfig{})) {
log.Info("tls: config has changed, restarting https server")
restartRequired = true
} else {
log.Info("tls: config has not changed")
}
// Do not just write "m.conf = *newConf.tlsConfiguration", because all other
// members of m.conf must be preserved.
m.conf.Enabled = newConf.Enabled
m.conf.ServerName = newConf.ServerName
m.conf.ForceHTTPS = newConf.ForceHTTPS
m.conf.PortHTTPS = newConf.PortHTTPS
m.conf.PortDNSOverTLS = newConf.PortDNSOverTLS
m.conf.PortDNSOverQUIC = newConf.PortDNSOverQUIC
m.conf.CertificateChain = newConf.CertificateChain
m.conf.CertificatePath = newConf.CertificatePath
m.conf.CertificateChainData = newConf.CertificateChainData
m.conf.PrivateKey = newConf.PrivateKey
m.conf.PrivateKeyPath = newConf.PrivateKeyPath
m.conf.PrivateKeyData = newConf.PrivateKeyData
m.setCertFileTime()
m.status = newConf.tlsConfigStatus
return restartRequired
}
// marshalTLS handles Base64-encoded certificates transparently.
func marshalTLS(w http.ResponseWriter, r *http.Request, conf *tlsConfigResp) {
if conf.CertificateChain != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(conf.CertificateChain))
conf.CertificateChain = encoded
}
if conf.PrivateKey != "" {
conf.PrivateKeySaved = true
conf.PrivateKey = ""
}
_ = aghhttp.WriteJSONResponse(w, r, conf)
}
// unmarshalTLS handles Base64-encoded certificates transparently.
func unmarshalTLS(r *http.Request) (req *tlsConfigReq, err error) {
req = &tlsConfigReq{}
err = json.NewDecoder(r.Body).Decode(req)
if err != nil {
return nil, fmt.Errorf("parsing tls config: %w", err)
}
if req.CertificateChain != "" {
var cert []byte
cert, err = base64.StdEncoding.DecodeString(req.CertificateChain)
if err != nil {
return nil, fmt.Errorf("failed to base64-decode certificate chain: %w", err)
}
req.CertificateChain = string(cert)
if req.CertificatePath != "" {
return nil, fmt.Errorf("certificate data and file can't be set together")
}
}
if req.PrivateKey != "" {
var key []byte
key, err = base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
return nil, fmt.Errorf("failed to base64-decode private key: %w", err)
}
req.PrivateKey = string(key)
if req.PrivateKeyPath != "" {
return nil, fmt.Errorf("private key data and file can't be set together")
}
}
return req, nil
}
// registerWebHandlers registers HTTP handlers for TLS configuration.
func (m *tlsManager) registerWebHandlers() {
httpRegister(http.MethodGet, "/control/tls/status", m.handleTLSStatus)
httpRegister(http.MethodPost, "/control/tls/configure", m.handleTLSConfigure)
httpRegister(http.MethodPost, "/control/tls/validate", m.handleTLSValidate)
}

View File

@@ -143,7 +143,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 *tlsConfiguration) {
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)

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/netip"
"strings" "strings"
"time" "time"
@@ -26,7 +27,7 @@ const (
// WHOIS - module context // WHOIS - module context
type WHOIS struct { type WHOIS struct {
clients *clientsContainer clients *clientsContainer
ipChan chan net.IP ipChan chan netip.Addr
// dialContext specifies the dial function for creating unencrypted TCP // dialContext specifies the dial function for creating unencrypted TCP
// connections. // connections.
@@ -51,7 +52,7 @@ func initWHOIS(clients *clientsContainer) *WHOIS {
MaxCount: 10000, MaxCount: 10000,
}), }),
dialContext: customDialContext, dialContext: customDialContext,
ipChan: make(chan net.IP, 255), ipChan: make(chan netip.Addr, 255),
} }
go w.workerLoop() go w.workerLoop()
@@ -192,7 +193,7 @@ func (w *WHOIS) queryAll(ctx context.Context, target string) (string, error) {
} }
// Request WHOIS information // Request WHOIS information
func (w *WHOIS) process(ctx context.Context, ip net.IP) (wi *RuntimeClientWHOISInfo) { func (w *WHOIS) process(ctx context.Context, ip netip.Addr) (wi *RuntimeClientWHOISInfo) {
resp, err := w.queryAll(ctx, ip.String()) resp, err := w.queryAll(ctx, ip.String())
if err != nil { if err != nil {
log.Debug("whois: error: %s IP:%s", err, ip) log.Debug("whois: error: %s IP:%s", err, ip)
@@ -220,24 +221,25 @@ func (w *WHOIS) process(ctx context.Context, ip net.IP) (wi *RuntimeClientWHOISI
} }
// Begin - begin requesting WHOIS info // Begin - begin requesting WHOIS info
func (w *WHOIS) Begin(ip net.IP) { func (w *WHOIS) Begin(ip netip.Addr) {
ipBytes := ip.AsSlice()
now := uint64(time.Now().Unix()) now := uint64(time.Now().Unix())
expire := w.ipAddrs.Get([]byte(ip)) expire := w.ipAddrs.Get(ipBytes)
if len(expire) != 0 { if len(expire) != 0 {
exp := binary.BigEndian.Uint64(expire) exp := binary.BigEndian.Uint64(expire)
if exp > now { if exp > now {
return return
} }
// TTL expired
} }
expire = make([]byte, 8) expire = make([]byte, 8)
binary.BigEndian.PutUint64(expire, now+whoisTTL) binary.BigEndian.PutUint64(expire, now+whoisTTL)
_ = w.ipAddrs.Set([]byte(ip), expire) _ = w.ipAddrs.Set(ipBytes, expire)
log.Debug("whois: adding %s", ip) log.Debug("whois: adding %s", ip)
select { select {
case w.ipChan <- ip: case w.ipChan <- ip:
//
default: default:
log.Debug("whois: queue is full") log.Debug("whois: queue is full")
} }

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/netip"
"os" "os"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -64,7 +65,7 @@ type Interface interface {
// GetTopClientIP returns at most limit IP addresses corresponding to the // GetTopClientIP returns at most limit IP addresses corresponding to the
// clients with the most number of requests. // clients with the most number of requests.
TopClientsIP(limit uint) []net.IP TopClientsIP(limit uint) []netip.Addr
// WriteDiskConfig puts the Interface's configuration to the dc. // WriteDiskConfig puts the Interface's configuration to the dc.
WriteDiskConfig(dc *DiskConfig) WriteDiskConfig(dc *DiskConfig)
@@ -107,8 +108,6 @@ type StatsCtx struct {
filename string filename string
} }
var _ Interface = &StatsCtx{}
// New creates s from conf and properly initializes it. Don't use s before // New creates s from conf and properly initializes it. Don't use s before
// calling it's Start method. // calling it's Start method.
func New(conf Config) (s *StatsCtx, err error) { func New(conf Config) (s *StatsCtx, err error) {
@@ -178,6 +177,9 @@ func withRecovered(orig *error) {
*orig = errors.WithDeferred(*orig, err) *orig = errors.WithDeferred(*orig, err)
} }
// type check
var _ Interface = (*StatsCtx)(nil)
// Start implements the Interface interface for *StatsCtx. // Start implements the Interface interface for *StatsCtx.
func (s *StatsCtx) Start() { func (s *StatsCtx) Start() {
s.initWeb() s.initWeb()
@@ -250,8 +252,8 @@ func (s *StatsCtx) WriteDiskConfig(dc *DiskConfig) {
dc.Interval = atomic.LoadUint32(&s.limitHours) / 24 dc.Interval = atomic.LoadUint32(&s.limitHours) / 24
} }
// TopClientsIP implements the Interface interface for *StatsCtx. // TopClientsIP implements the [Interface] interface for *StatsCtx.
func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []net.IP) { func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []netip.Addr) {
limit := atomic.LoadUint32(&s.limitHours) limit := atomic.LoadUint32(&s.limitHours)
if limit == 0 { if limit == 0 {
return nil return nil
@@ -271,10 +273,10 @@ func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []net.IP) {
} }
a := convertMapToSlice(m, int(maxCount)) a := convertMapToSlice(m, int(maxCount))
ips = []net.IP{} ips = []netip.Addr{}
for _, it := range a { for _, it := range a {
ip := net.ParseIP(it.Name) ip, err := netip.ParseAddr(it.Name)
if ip != nil { if err == nil {
ips = append(ips, ip) ips = append(ips, ip)
} }
} }

View File

@@ -11,6 +11,7 @@ import (
"testing" "testing"
"github.com/AdguardTeam/AdGuardHome/internal/stats" "github.com/AdguardTeam/AdGuardHome/internal/stats"
"github.com/AdguardTeam/golibs/netutil"
"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"
@@ -45,7 +46,7 @@ func assertSuccessAndUnmarshal(t *testing.T, to any, handler http.Handler, req *
} }
func TestStats(t *testing.T) { func TestStats(t *testing.T) {
cliIP := net.IP{127, 0, 0, 1} cliIP := netutil.IPv4Localhost()
cliIPStr := cliIP.String() cliIPStr := cliIP.String()
handlers := map[string]http.Handler{} handlers := map[string]http.Handler{}
@@ -123,7 +124,7 @@ func TestStats(t *testing.T) {
topClients := s.TopClientsIP(2) topClients := s.TopClientsIP(2)
require.NotEmpty(t, topClients) require.NotEmpty(t, topClients)
assert.True(t, cliIP.Equal(topClients[0])) assert.Equal(t, cliIP, topClients[0])
}) })
t.Run("reset", func(t *testing.T) { t.Run("reset", func(t *testing.T) {

View File

@@ -10,7 +10,7 @@ require (
github.com/kyoh86/looppointer v0.1.9 github.com/kyoh86/looppointer v0.1.9
github.com/securego/gosec/v2 v2.14.0 github.com/securego/gosec/v2 v2.14.0
golang.org/x/tools v0.2.0 golang.org/x/tools v0.2.0
golang.org/x/vuln v0.0.0-20221025230227-995372c58a16 golang.org/x/vuln v0.0.0-20221103225512-4f561ca73b59
honnef.co/go/tools v0.3.3 honnef.co/go/tools v0.3.3
mvdan.cc/gofumpt v0.4.0 mvdan.cc/gofumpt v0.4.0
mvdan.cc/unparam v0.0.0-20220926085101-66de63301820 mvdan.cc/unparam v0.0.0-20220926085101-66de63301820
@@ -24,10 +24,10 @@ require (
github.com/kyoh86/nolint v0.0.1 // indirect github.com/kyoh86/nolint v0.0.1 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect
golang.org/x/exp/typeparams v0.0.0-20221031165847-c99f073a8326 // indirect golang.org/x/exp/typeparams v0.0.0-20221106115401-f9659909a136 // indirect
golang.org/x/mod v0.6.0 // indirect golang.org/x/mod v0.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect golang.org/x/sys v0.2.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )

View File

@@ -53,10 +53,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE= golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/typeparams v0.0.0-20221031165847-c99f073a8326 h1:fl8k2zg28yA23264d82M4dp+YlJ3ngDcpuB1bewkQi4= golang.org/x/exp/typeparams v0.0.0-20221106115401-f9659909a136 h1:962j4VxUJV3GKI6NxKDI9NjATh+tAixlH+9k9MvHSlU=
golang.org/x/exp/typeparams v0.0.0-20221031165847-c99f073a8326/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20221106115401-f9659909a136/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
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.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
@@ -83,8 +83,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -98,8 +98,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/vuln v0.0.0-20221025230227-995372c58a16 h1:/H6ddBUaKrFDOBFz0Y3l1/Ppbx19f/rK11jABxiqKFw= golang.org/x/vuln v0.0.0-20221103225512-4f561ca73b59 h1:eOOJSuIRc2QwKAgX5qOIhUZJAd2LLKSBfk839dv+Clo=
golang.org/x/vuln v0.0.0-20221025230227-995372c58a16/go.mod h1:F12iebNzxRMpJsm4W7ape+r/KdnXiSy3VC94WsyCG68= golang.org/x/vuln v0.0.0-20221103225512-4f561ca73b59/go.mod h1:F12iebNzxRMpJsm4W7ape+r/KdnXiSy3VC94WsyCG68=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -188,6 +188,10 @@ After the download you'll find the output locales in the `client/src/__locales/`
directory. directory.
Optional environment: Optional environment:
* `SLEEP_TIME`: set the sleep time between downloads for `locales:download`,
in milliseconds. The default is 250 ms.
* `UPLOAD_LANGUAGE`: set an alternative language for `locales:upload` to * `UPLOAD_LANGUAGE`: set an alternative language for `locales:upload` to
upload. upload.

View File

@@ -110,7 +110,8 @@ const download = async () => {
// Don't request the Crowdin API too aggressively to prevent spurious // Don't request the Crowdin API too aggressively to prevent spurious
// 400 errors. // 400 errors.
await sleep(400); const sleepTime = process.env.SLEEP_TIME || 250;
await sleep(sleepTime);
} }
Promise Promise