Compare commits
10 Commits
v0.108.0-b
...
4927-refac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c7d56dca3 | ||
|
|
08282dc4d9 | ||
|
|
f36efa26a4 | ||
|
|
a8850059db | ||
|
|
93882d6860 | ||
|
|
167b112511 | ||
|
|
98af0e000e | ||
|
|
2bfdcbbc10 | ||
|
|
8fdbcc005c | ||
|
|
464fbf0b54 |
39
CHANGELOG.md
39
CHANGELOG.md
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
10
go.mod
@@ -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
20
go.sum
@@ -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=
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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^",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"},
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
362
internal/home/tlshttp.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user