Compare commits
44 Commits
v0.107.0-b
...
fix-stats-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02834e6b7b | ||
|
|
b45162a0f2 | ||
|
|
ce0df255be | ||
|
|
b735c8167c | ||
|
|
2ac2ad0609 | ||
|
|
37a3666a2a | ||
|
|
5aed8db5cf | ||
|
|
e1e064db59 | ||
|
|
b1242ee93f | ||
|
|
5f3131c799 | ||
|
|
1714a986e3 | ||
|
|
6ac28ee8ee | ||
|
|
bf1263628a | ||
|
|
176a344aee | ||
|
|
48b0cefb29 | ||
|
|
857f876486 | ||
|
|
eeb99db973 | ||
|
|
9a1d3ec694 | ||
|
|
fac574d324 | ||
|
|
53f7c0b2df | ||
|
|
424f20da98 | ||
|
|
8fdd789474 | ||
|
|
80548233ba | ||
|
|
eeda06f9b0 | ||
|
|
b77ceaaf55 | ||
|
|
bdd0ca5423 | ||
|
|
138718e6ec | ||
|
|
2365ddd2f2 | ||
|
|
bb4cd312c2 | ||
|
|
1265be507a | ||
|
|
e064e0f277 | ||
|
|
595441603f | ||
|
|
d1de47b636 | ||
|
|
e5c73877c8 | ||
|
|
77821ec816 | ||
|
|
9f52adf33d | ||
|
|
8454e65cd9 | ||
|
|
756c70644d | ||
|
|
16092e8ba9 | ||
|
|
b92db25e6a | ||
|
|
b81030ba70 | ||
|
|
9809ed8019 | ||
|
|
9246c1340e | ||
|
|
550b1798a1 |
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
@@ -5,6 +5,7 @@
|
|||||||
# Issues with these labels will never be considered stale.
|
# Issues with these labels will never be considered stale.
|
||||||
'exemptLabels':
|
'exemptLabels':
|
||||||
- 'bug'
|
- 'bug'
|
||||||
|
- 'documentation'
|
||||||
- 'enhancement'
|
- 'enhancement'
|
||||||
- 'feature request'
|
- 'feature request'
|
||||||
- 'localization'
|
- 'localization'
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nl": "Dutch",
|
"nl": "Nederlands",
|
||||||
"no": "Norsk",
|
"no": "Norsk",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt-br": "Português (BR)",
|
"pt-br": "Português (BR)",
|
||||||
|
|||||||
81
CHANGELOG.md
81
CHANGELOG.md
@@ -10,11 +10,15 @@ and this project adheres to
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
## [v0.107.0] - 2021-08-03 (APPROX.)
|
## [v0.107.0] - 2021-09-28 (APPROX.)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- DNS server IP addresses to the `mobileconfig` API responses ([#3568],
|
||||||
|
[#3607]).
|
||||||
|
- Setting the timeout for IP address pinging in the "Fastest IP address" mode
|
||||||
|
through the new `fastest_timeout` field in the configuration file ([#1992]).
|
||||||
- Static IP address detection on FreeBSD ([#3289]).
|
- Static IP address detection on FreeBSD ([#3289]).
|
||||||
- Optimistic cache ([#2145]).
|
- Optimistic cache ([#2145]).
|
||||||
- New possible value of `6h` for `querylog_interval` setting ([#2504]).
|
- New possible value of `6h` for `querylog_interval` setting ([#2504]).
|
||||||
@@ -27,7 +31,8 @@ and this project adheres to
|
|||||||
- Settable timeouts for querying the upstream servers ([#2280]).
|
- Settable timeouts for querying the upstream servers ([#2280]).
|
||||||
- Configuration file parameters to change group and user ID on startup on Unix
|
- Configuration file parameters to change group and user ID on startup on Unix
|
||||||
([#2763]).
|
([#2763]).
|
||||||
- Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439], [#3225]).
|
- Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439], [#3225],
|
||||||
|
[#3226]).
|
||||||
- Support for custom port in DNS-over-HTTPS profiles for Apple's devices
|
- Support for custom port in DNS-over-HTTPS profiles for Apple's devices
|
||||||
([#3172]).
|
([#3172]).
|
||||||
- `darwin/arm64` support ([#2443]).
|
- `darwin/arm64` support ([#2443]).
|
||||||
@@ -43,6 +48,12 @@ and this project adheres to
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- The `systemd` service script will now create the `/var/log` directory when it
|
||||||
|
doesn't exist ([#3579]).
|
||||||
|
- Items in allowed clients, disallowed clients, and blocked hosts lists are now
|
||||||
|
required to be unique ([#3419]).
|
||||||
|
- The TLS private key previously saved as a string isn't shown in API responses
|
||||||
|
anymore ([#1898]).
|
||||||
- Better OpenWrt detection ([#3435]).
|
- Better OpenWrt detection ([#3435]).
|
||||||
- DNS-over-HTTPS queries that come from HTTP proxies in the `trusted_proxies`
|
- DNS-over-HTTPS queries that come from HTTP proxies in the `trusted_proxies`
|
||||||
list now use the real IP address of the client instead of the address of the
|
list now use the real IP address of the client instead of the address of the
|
||||||
@@ -59,12 +70,61 @@ and this project adheres to
|
|||||||
file, together with the new `group` and `user` settings ([#2763]).
|
file, together with the new `group` and `user` settings ([#2763]).
|
||||||
- Permissions on filter files are now `0o644` instead of `0o600` ([#3198]).
|
- Permissions on filter files are now `0o644` instead of `0o600` ([#3198]).
|
||||||
|
|
||||||
|
#### Configuration Changes
|
||||||
|
|
||||||
|
In this release, the schema version has changed from 10 to 12.
|
||||||
|
|
||||||
|
- Parameter `dns.querylog_interval`, which in schema versions 11 and earlier
|
||||||
|
used to be an integer number of days, is now a string with a human-readable
|
||||||
|
duration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# BEFORE:
|
||||||
|
'dns':
|
||||||
|
# …
|
||||||
|
'querylog_interval': 90
|
||||||
|
|
||||||
|
# AFTER:
|
||||||
|
'dns':
|
||||||
|
# …
|
||||||
|
'querylog_interval': '2160h'
|
||||||
|
```
|
||||||
|
|
||||||
|
To rollback this change, convert the parameter back into days and change the
|
||||||
|
`schema_version` back to `11`.
|
||||||
|
|
||||||
|
- Parameter `rlimit_nofile`, which in schema versions 10 and earlier used to be
|
||||||
|
on the top level, is now moved to the new `os` object:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# BEFORE:
|
||||||
|
'rlimit_nofile': 42
|
||||||
|
|
||||||
|
# AFTER:
|
||||||
|
'os':
|
||||||
|
'group': ''
|
||||||
|
'rlimit_nofile': 42
|
||||||
|
'user': ''
|
||||||
|
```
|
||||||
|
|
||||||
|
To rollback this change, move the parameter on the top level and change the
|
||||||
|
`schema_version` back to `10`.
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
- Go 1.16 support. v0.108.0 will require at least Go 1.17 to build.
|
- Go 1.16 support. v0.108.0 will require at least Go 1.17 to build.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Adding an IP into only one of the matching ipsets on Linux ([#3638]).
|
||||||
|
- Removal of temporary filter files ([#3567]).
|
||||||
|
- Panic when an upstream server responds with an empty question section
|
||||||
|
([#3551]).
|
||||||
|
- 9GAG blocking ([#3564]).
|
||||||
|
- DHCP now follows RFCs more closely when it comes to response sending and
|
||||||
|
option selection ([#3443], [#3538]).
|
||||||
|
- Occasional panics when reading old statistics databases ([#3506]).
|
||||||
|
- `reload` service action on macOS and FreeBSD ([#3457]).
|
||||||
- Inaccurate using of service actions in the installation script ([#3450]).
|
- Inaccurate using of service actions in the installation script ([#3450]).
|
||||||
- Client ID checking ([#3437]).
|
- Client ID checking ([#3437]).
|
||||||
- Discovering other DHCP servers on `darwin` and `freebsd` ([#3417]).
|
- Discovering other DHCP servers on `darwin` and `freebsd` ([#3417]).
|
||||||
@@ -93,6 +153,8 @@ and this project adheres to
|
|||||||
|
|
||||||
[#1381]: https://github.com/AdguardTeam/AdGuardHome/issues/1381
|
[#1381]: https://github.com/AdguardTeam/AdGuardHome/issues/1381
|
||||||
[#1691]: https://github.com/AdguardTeam/AdGuardHome/issues/1691
|
[#1691]: https://github.com/AdguardTeam/AdGuardHome/issues/1691
|
||||||
|
[#1898]: https://github.com/AdguardTeam/AdGuardHome/issues/1898
|
||||||
|
[#1992]: https://github.com/AdguardTeam/AdGuardHome/issues/1992
|
||||||
[#2141]: https://github.com/AdguardTeam/AdGuardHome/issues/2141
|
[#2141]: https://github.com/AdguardTeam/AdGuardHome/issues/2141
|
||||||
[#2145]: https://github.com/AdguardTeam/AdGuardHome/issues/2145
|
[#2145]: https://github.com/AdguardTeam/AdGuardHome/issues/2145
|
||||||
[#2280]: https://github.com/AdguardTeam/AdGuardHome/issues/2280
|
[#2280]: https://github.com/AdguardTeam/AdGuardHome/issues/2280
|
||||||
@@ -117,6 +179,7 @@ and this project adheres to
|
|||||||
[#3198]: https://github.com/AdguardTeam/AdGuardHome/issues/3198
|
[#3198]: https://github.com/AdguardTeam/AdGuardHome/issues/3198
|
||||||
[#3217]: https://github.com/AdguardTeam/AdGuardHome/issues/3217
|
[#3217]: https://github.com/AdguardTeam/AdGuardHome/issues/3217
|
||||||
[#3225]: https://github.com/AdguardTeam/AdGuardHome/issues/3225
|
[#3225]: https://github.com/AdguardTeam/AdGuardHome/issues/3225
|
||||||
|
[#3226]: https://github.com/AdguardTeam/AdGuardHome/issues/3226
|
||||||
[#3256]: https://github.com/AdguardTeam/AdGuardHome/issues/3256
|
[#3256]: https://github.com/AdguardTeam/AdGuardHome/issues/3256
|
||||||
[#3257]: https://github.com/AdguardTeam/AdGuardHome/issues/3257
|
[#3257]: https://github.com/AdguardTeam/AdGuardHome/issues/3257
|
||||||
[#3289]: https://github.com/AdguardTeam/AdGuardHome/issues/3289
|
[#3289]: https://github.com/AdguardTeam/AdGuardHome/issues/3289
|
||||||
@@ -125,9 +188,21 @@ and this project adheres to
|
|||||||
[#3351]: https://github.com/AdguardTeam/AdGuardHome/issues/3351
|
[#3351]: https://github.com/AdguardTeam/AdGuardHome/issues/3351
|
||||||
[#3372]: https://github.com/AdguardTeam/AdGuardHome/issues/3372
|
[#3372]: https://github.com/AdguardTeam/AdGuardHome/issues/3372
|
||||||
[#3417]: https://github.com/AdguardTeam/AdGuardHome/issues/3417
|
[#3417]: https://github.com/AdguardTeam/AdGuardHome/issues/3417
|
||||||
|
[#3419]: https://github.com/AdguardTeam/AdGuardHome/issues/3419
|
||||||
[#3435]: https://github.com/AdguardTeam/AdGuardHome/issues/3435
|
[#3435]: https://github.com/AdguardTeam/AdGuardHome/issues/3435
|
||||||
[#3437]: https://github.com/AdguardTeam/AdGuardHome/issues/3437
|
[#3437]: https://github.com/AdguardTeam/AdGuardHome/issues/3437
|
||||||
|
[#3443]: https://github.com/AdguardTeam/AdGuardHome/issues/3443
|
||||||
[#3450]: https://github.com/AdguardTeam/AdGuardHome/issues/3450
|
[#3450]: https://github.com/AdguardTeam/AdGuardHome/issues/3450
|
||||||
|
[#3457]: https://github.com/AdguardTeam/AdGuardHome/issues/3457
|
||||||
|
[#3506]: https://github.com/AdguardTeam/AdGuardHome/issues/3506
|
||||||
|
[#3538]: https://github.com/AdguardTeam/AdGuardHome/issues/3538
|
||||||
|
[#3551]: https://github.com/AdguardTeam/AdGuardHome/issues/3551
|
||||||
|
[#3564]: https://github.com/AdguardTeam/AdGuardHome/issues/3564
|
||||||
|
[#3567]: https://github.com/AdguardTeam/AdGuardHome/issues/3567
|
||||||
|
[#3568]: https://github.com/AdguardTeam/AdGuardHome/issues/3568
|
||||||
|
[#3579]: https://github.com/AdguardTeam/AdGuardHome/issues/3579
|
||||||
|
[#3607]: https://github.com/AdguardTeam/AdGuardHome/issues/3607
|
||||||
|
[#3638]: https://github.com/AdguardTeam/AdGuardHome/issues/3638
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -331,7 +406,7 @@ and this project adheres to
|
|||||||
- The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response
|
- The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response
|
||||||
is now correctly named again ([#2678]).
|
is now correctly named again ([#2678]).
|
||||||
- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` settings aren't reset to
|
- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` settings aren't reset to
|
||||||
`false` on update any more ([#2653]).
|
`false` on update anymore ([#2653]).
|
||||||
- The `Vary` header is now added along with `Access-Control-Allow-Origin` to
|
- The `Vary` header is now added along with `Access-Control-Allow-Origin` to
|
||||||
prevent cache-related and other issues in browsers ([#2658]).
|
prevent cache-related and other issues in browsers ([#2658]).
|
||||||
- The request body size limit is now set for HTTPS requests as well.
|
- The request body size limit is now set for HTTPS requests as well.
|
||||||
|
|||||||
619
client/package-lock.json
generated
vendored
619
client/package-lock.json
generated
vendored
@@ -70,15 +70,6 @@
|
|||||||
"ms": "^2.1.1"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"json5": {
|
|
||||||
"version": "2.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
|
||||||
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"minimist": "^1.2.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
@@ -1188,16 +1179,6 @@
|
|||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/runtime-corejs3": {
|
|
||||||
"version": "7.9.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.6.tgz",
|
|
||||||
"integrity": "sha512-6toWAfaALQjt3KMZQc6fABqZwUDDuWzz+cAfPhqyEnzxvdWOAkjwPNxgF8xlmo7OWLsSjaKjsskpKHRLaMArOA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"core-js-pure": "^3.0.0",
|
|
||||||
"regenerator-runtime": "^0.13.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
"version": "7.8.6",
|
"version": "7.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz",
|
||||||
@@ -1909,11 +1890,6 @@
|
|||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"kind-of": {
|
|
||||||
"version": "6.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
|
|
||||||
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
|
|
||||||
},
|
|
||||||
"micromatch": {
|
"micromatch": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
|
||||||
@@ -2875,13 +2851,15 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"array-includes": {
|
"array-includes": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz",
|
||||||
"integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==",
|
"integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"es-abstract": "^1.17.0",
|
"es-abstract": "^1.18.0-next.2",
|
||||||
|
"get-intrinsic": "^1.1.1",
|
||||||
"is-string": "^1.0.5"
|
"is-string": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2907,13 +2885,26 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"array.prototype.flat": {
|
"array.prototype.flat": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz",
|
||||||
"integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==",
|
"integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"call-bind": "^1.0.0",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"es-abstract": "^1.17.0-next.1"
|
"es-abstract": "^1.18.0-next.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"array.prototype.flatmap": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"call-bind": "^1.0.0",
|
||||||
|
"define-properties": "^1.1.3",
|
||||||
|
"es-abstract": "^1.18.0-next.1",
|
||||||
|
"function-bind": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"arrify": {
|
"arrify": {
|
||||||
@@ -3197,11 +3188,6 @@
|
|||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"minimist": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
|
||||||
},
|
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
|
||||||
@@ -3863,6 +3849,15 @@
|
|||||||
"unset-value": "^1.0.0"
|
"unset-value": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"call-bind": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||||
|
"requires": {
|
||||||
|
"function-bind": "^1.1.1",
|
||||||
|
"get-intrinsic": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"caller-callsite": {
|
"caller-callsite": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
|
||||||
@@ -4325,12 +4320,6 @@
|
|||||||
"integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
|
"integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"contains-path": {
|
|
||||||
"version": "0.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
|
|
||||||
"integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"content-disposition": {
|
"content-disposition": {
|
||||||
"version": "0.5.3",
|
"version": "0.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||||
@@ -4550,12 +4539,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"core-js-pure": {
|
|
||||||
"version": "3.6.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz",
|
|
||||||
"integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
@@ -5421,21 +5404,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"es-abstract": {
|
"es-abstract": {
|
||||||
"version": "1.17.5",
|
"version": "1.18.5",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz",
|
||||||
"integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
|
"integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
"es-to-primitive": "^1.2.1",
|
"es-to-primitive": "^1.2.1",
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
|
"get-intrinsic": "^1.1.1",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
"has-symbols": "^1.0.1",
|
"has-symbols": "^1.0.2",
|
||||||
"is-callable": "^1.1.5",
|
"internal-slot": "^1.0.3",
|
||||||
"is-regex": "^1.0.5",
|
"is-callable": "^1.2.3",
|
||||||
"object-inspect": "^1.7.0",
|
"is-negative-zero": "^2.0.1",
|
||||||
|
"is-regex": "^1.1.3",
|
||||||
|
"is-string": "^1.0.6",
|
||||||
|
"object-inspect": "^1.11.0",
|
||||||
"object-keys": "^1.1.1",
|
"object-keys": "^1.1.1",
|
||||||
"object.assign": "^4.1.0",
|
"object.assign": "^4.1.2",
|
||||||
"string.prototype.trimleft": "^2.1.1",
|
"string.prototype.trimend": "^1.0.4",
|
||||||
"string.prototype.trimright": "^2.1.1"
|
"string.prototype.trimstart": "^1.0.4",
|
||||||
|
"unbox-primitive": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"es-to-primitive": {
|
"es-to-primitive": {
|
||||||
@@ -5658,22 +5647,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-import-resolver-node": {
|
"eslint-import-resolver-node": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz",
|
||||||
"integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==",
|
"integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "^2.6.9",
|
"debug": "^3.2.7",
|
||||||
"resolve": "^1.13.1"
|
"resolve": "^1.20.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "2.6.9",
|
"version": "3.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "2.0.0"
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"version": "1.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
|
||||||
|
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-core-module": "^2.2.0",
|
||||||
|
"path-parse": "^1.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5741,15 +5746,6 @@
|
|||||||
"path-exists": "^4.0.0"
|
"path-exists": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"json5": {
|
|
||||||
"version": "2.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
|
||||||
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"minimist": "^1.2.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"loader-utils": {
|
"loader-utils": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||||
@@ -5837,44 +5833,53 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-module-utils": {
|
"eslint-module-utils": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz",
|
||||||
"integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==",
|
"integrity": "sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "^2.6.9",
|
"debug": "^3.2.7",
|
||||||
"pkg-dir": "^2.0.0"
|
"pkg-dir": "^2.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "2.6.9",
|
"version": "3.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "2.0.0"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-plugin-import": {
|
"eslint-plugin-import": {
|
||||||
"version": "2.20.2",
|
"version": "2.24.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.1.tgz",
|
||||||
"integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==",
|
"integrity": "sha512-KSFWhNxPH8OGJwpRJJs+Z7I0a13E2iFQZJIvSnCu6KUs4qmgAm3xN9GYBCSoiGWmwA7gERZPXqYQjcoCROnYhQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"array-includes": "^3.0.3",
|
"array-includes": "^3.1.3",
|
||||||
"array.prototype.flat": "^1.2.1",
|
"array.prototype.flat": "^1.2.4",
|
||||||
"contains-path": "^0.1.0",
|
|
||||||
"debug": "^2.6.9",
|
"debug": "^2.6.9",
|
||||||
"doctrine": "1.5.0",
|
"doctrine": "^2.1.0",
|
||||||
"eslint-import-resolver-node": "^0.3.2",
|
"eslint-import-resolver-node": "^0.3.6",
|
||||||
"eslint-module-utils": "^2.4.1",
|
"eslint-module-utils": "^2.6.2",
|
||||||
|
"find-up": "^2.0.0",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
|
"is-core-module": "^2.6.0",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"object.values": "^1.1.0",
|
"object.values": "^1.1.4",
|
||||||
"read-pkg-up": "^2.0.0",
|
"pkg-up": "^2.0.0",
|
||||||
"resolve": "^1.12.0"
|
"read-pkg-up": "^3.0.0",
|
||||||
|
"resolve": "^1.20.0",
|
||||||
|
"tsconfig-paths": "^3.10.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": {
|
"debug": {
|
||||||
@@ -5887,20 +5892,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"doctrine": {
|
"doctrine": {
|
||||||
"version": "1.5.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
||||||
"integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
|
"integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"esutils": "^2.0.2",
|
"esutils": "^2.0.2"
|
||||||
"isarray": "^1.0.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"isarray": {
|
"resolve": {
|
||||||
"version": "1.0.0",
|
"version": "1.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
|
||||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-core-module": "^2.2.0",
|
||||||
|
"path-parse": "^1.0.6"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -5930,22 +5938,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-plugin-react": {
|
"eslint-plugin-react": {
|
||||||
"version": "7.20.0",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz",
|
||||||
"integrity": "sha512-rqe1abd0vxMjmbPngo4NaYxTcR3Y4Hrmc/jg4T+sYz63yqlmJRknpEQfmWY+eDWPuMmix6iUIK+mv0zExjeLgA==",
|
"integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"array-includes": "^3.1.1",
|
"array-includes": "^3.1.3",
|
||||||
|
"array.prototype.flatmap": "^1.2.4",
|
||||||
"doctrine": "^2.1.0",
|
"doctrine": "^2.1.0",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
"jsx-ast-utils": "^2.2.3",
|
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
|
||||||
"object.entries": "^1.1.1",
|
"minimatch": "^3.0.4",
|
||||||
"object.fromentries": "^2.0.2",
|
"object.entries": "^1.1.4",
|
||||||
"object.values": "^1.1.1",
|
"object.fromentries": "^2.0.4",
|
||||||
|
"object.values": "^1.1.4",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"resolve": "^1.15.1",
|
"resolve": "^2.0.0-next.3",
|
||||||
"string.prototype.matchall": "^4.0.2",
|
"string.prototype.matchall": "^4.0.5"
|
||||||
"xregexp": "^4.3.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"doctrine": {
|
"doctrine": {
|
||||||
@@ -5957,6 +5966,16 @@
|
|||||||
"esutils": "^2.0.2"
|
"esutils": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jsx-ast-utils": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"array-includes": "^3.1.2",
|
||||||
|
"object.assign": "^4.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.7.2",
|
"version": "15.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
@@ -5967,6 +5986,16 @@
|
|||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
"react-is": "^16.8.1"
|
"react-is": "^16.8.1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"version": "2.0.0-next.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz",
|
||||||
|
"integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-core-module": "^2.2.0",
|
||||||
|
"path-parse": "^1.0.6"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6883,6 +6912,16 @@
|
|||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"get-intrinsic": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
|
||||||
|
"requires": {
|
||||||
|
"function-bind": "^1.1.1",
|
||||||
|
"has": "^1.0.3",
|
||||||
|
"has-symbols": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"get-package-type": {
|
"get-package-type": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||||
@@ -7191,15 +7230,28 @@
|
|||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"has-bigints": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA=="
|
||||||
|
},
|
||||||
"has-flag": {
|
"has-flag": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||||
},
|
},
|
||||||
"has-symbols": {
|
"has-symbols": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||||
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
|
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
||||||
|
},
|
||||||
|
"has-tostringtag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
||||||
|
"requires": {
|
||||||
|
"has-symbols": "^1.0.2"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"has-value": {
|
"has-value": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -7824,14 +7876,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal-slot": {
|
"internal-slot": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
|
||||||
"integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==",
|
"integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"es-abstract": "^1.17.0-next.1",
|
"get-intrinsic": "^1.1.0",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
"side-channel": "^1.0.2"
|
"side-channel": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"interpret": {
|
"interpret": {
|
||||||
@@ -7929,6 +7980,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
|
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
|
||||||
},
|
},
|
||||||
|
"is-bigint": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
|
||||||
|
"requires": {
|
||||||
|
"has-bigints": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"is-binary-path": {
|
"is-binary-path": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
|
||||||
@@ -7938,6 +7997,15 @@
|
|||||||
"binary-extensions": "^1.0.0"
|
"binary-extensions": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"is-boolean-object": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
|
||||||
|
"requires": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
|
"has-tostringtag": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"is-buffer": {
|
"is-buffer": {
|
||||||
"version": "1.1.6",
|
"version": "1.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||||
@@ -7945,9 +8013,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-callable": {
|
"is-callable": {
|
||||||
"version": "1.1.5",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
|
||||||
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
|
"integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
|
||||||
},
|
},
|
||||||
"is-ci": {
|
"is-ci": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -7958,6 +8026,15 @@
|
|||||||
"ci-info": "^2.0.0"
|
"ci-info": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"is-core-module": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"has": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"is-data-descriptor": {
|
"is-data-descriptor": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
|
||||||
@@ -8060,6 +8137,11 @@
|
|||||||
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
|
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"is-negative-zero": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w=="
|
||||||
|
},
|
||||||
"is-number": {
|
"is-number": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||||
@@ -8080,6 +8162,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"is-number-object": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==",
|
||||||
|
"requires": {
|
||||||
|
"has-tostringtag": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"is-path-cwd": {
|
"is-path-cwd": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
|
||||||
@@ -8131,11 +8221,12 @@
|
|||||||
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
|
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
|
||||||
},
|
},
|
||||||
"is-regex": {
|
"is-regex": {
|
||||||
"version": "1.0.5",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||||
"integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
|
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"has": "^1.0.3"
|
"call-bind": "^1.0.2",
|
||||||
|
"has-tostringtag": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"is-regexp": {
|
"is-regexp": {
|
||||||
@@ -8150,10 +8241,12 @@
|
|||||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||||
},
|
},
|
||||||
"is-string": {
|
"is-string": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
|
||||||
"integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
|
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
|
||||||
"dev": true
|
"requires": {
|
||||||
|
"has-tostringtag": "^1.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"is-symbol": {
|
"is-symbol": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
@@ -10121,9 +10214,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "2.1.3",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||||
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
|
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "^1.2.5"
|
"minimist": "^1.2.5"
|
||||||
@@ -10237,24 +10330,25 @@
|
|||||||
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA="
|
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA="
|
||||||
},
|
},
|
||||||
"load-json-file": {
|
"load-json-file": {
|
||||||
"version": "2.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
|
||||||
"integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
|
"integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"parse-json": "^2.2.0",
|
"parse-json": "^4.0.0",
|
||||||
"pify": "^2.0.0",
|
"pify": "^3.0.0",
|
||||||
"strip-bom": "^3.0.0"
|
"strip-bom": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parse-json": {
|
"parse-json": {
|
||||||
"version": "2.2.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
|
||||||
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
|
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"error-ex": "^1.2.0"
|
"error-ex": "^1.3.1",
|
||||||
|
"json-parse-better-errors": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11327,9 +11421,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"object-inspect": {
|
"object-inspect": {
|
||||||
"version": "1.7.0",
|
"version": "1.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz",
|
||||||
"integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw=="
|
"integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg=="
|
||||||
},
|
},
|
||||||
"object-is": {
|
"object-is": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
@@ -11355,37 +11449,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"object.assign": {
|
"object.assign": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
|
||||||
"integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
|
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.2",
|
"call-bind": "^1.0.0",
|
||||||
"function-bind": "^1.1.1",
|
"define-properties": "^1.1.3",
|
||||||
"has-symbols": "^1.0.0",
|
"has-symbols": "^1.0.1",
|
||||||
"object-keys": "^1.0.11"
|
"object-keys": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"object.entries": {
|
"object.entries": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz",
|
||||||
"integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==",
|
"integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"es-abstract": "^1.17.0-next.1",
|
"es-abstract": "^1.18.2"
|
||||||
"function-bind": "^1.1.1",
|
|
||||||
"has": "^1.0.3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"object.fromentries": {
|
"object.fromentries": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz",
|
||||||
"integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==",
|
"integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"es-abstract": "^1.17.0-next.1",
|
"es-abstract": "^1.18.0-next.2",
|
||||||
"function-bind": "^1.1.1",
|
|
||||||
"has": "^1.0.3"
|
"has": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -11409,15 +11502,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"object.values": {
|
"object.values": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz",
|
||||||
"integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==",
|
"integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"es-abstract": "^1.17.0-next.1",
|
"es-abstract": "^1.18.2"
|
||||||
"function-bind": "^1.1.1",
|
|
||||||
"has": "^1.0.3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"obuf": {
|
"obuf": {
|
||||||
@@ -11791,9 +11883,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "2.3.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
||||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"pinkie": {
|
"pinkie": {
|
||||||
@@ -12718,35 +12810,35 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"read-pkg": {
|
"read-pkg": {
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
|
||||||
"integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
|
"integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"load-json-file": "^2.0.0",
|
"load-json-file": "^4.0.0",
|
||||||
"normalize-package-data": "^2.3.2",
|
"normalize-package-data": "^2.3.2",
|
||||||
"path-type": "^2.0.0"
|
"path-type": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-type": {
|
"path-type": {
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
|
||||||
"integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
|
"integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"pify": "^2.0.0"
|
"pify": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"read-pkg-up": {
|
"read-pkg-up": {
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
|
||||||
"integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
|
"integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"find-up": "^2.0.0",
|
"find-up": "^2.0.0",
|
||||||
"read-pkg": "^2.0.0"
|
"read-pkg": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
@@ -12894,12 +12986,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"regexp.prototype.flags": {
|
"regexp.prototype.flags": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz",
|
||||||
"integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
|
"integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.3",
|
"call-bind": "^1.0.2",
|
||||||
"es-abstract": "^1.17.0-next.1"
|
"define-properties": "^1.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"regexpp": {
|
"regexpp": {
|
||||||
@@ -13572,13 +13664,13 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"side-channel": {
|
"side-channel": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||||
"integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==",
|
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"es-abstract": "^1.17.0-next.1",
|
"call-bind": "^1.0.0",
|
||||||
"object-inspect": "^1.7.0"
|
"get-intrinsic": "^1.0.2",
|
||||||
|
"object-inspect": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
@@ -14101,6 +14193,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||||
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
|
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
|
||||||
},
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "~5.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"string-length": {
|
"string-length": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz",
|
||||||
@@ -14163,64 +14264,37 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string.prototype.matchall": {
|
"string.prototype.matchall": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz",
|
||||||
"integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==",
|
"integrity": "sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
"es-abstract": "^1.17.0",
|
"es-abstract": "^1.18.2",
|
||||||
"has-symbols": "^1.0.1",
|
"get-intrinsic": "^1.1.1",
|
||||||
"internal-slot": "^1.0.2",
|
"has-symbols": "^1.0.2",
|
||||||
"regexp.prototype.flags": "^1.3.0",
|
"internal-slot": "^1.0.3",
|
||||||
"side-channel": "^1.0.2"
|
"regexp.prototype.flags": "^1.3.1",
|
||||||
|
"side-channel": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string.prototype.trimend": {
|
"string.prototype.trimend": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
|
||||||
"integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
|
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.3",
|
"call-bind": "^1.0.2",
|
||||||
"es-abstract": "^1.17.5"
|
"define-properties": "^1.1.3"
|
||||||
}
|
|
||||||
},
|
|
||||||
"string.prototype.trimleft": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
|
|
||||||
"requires": {
|
|
||||||
"define-properties": "^1.1.3",
|
|
||||||
"es-abstract": "^1.17.5",
|
|
||||||
"string.prototype.trimstart": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string.prototype.trimright": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
|
|
||||||
"requires": {
|
|
||||||
"define-properties": "^1.1.3",
|
|
||||||
"es-abstract": "^1.17.5",
|
|
||||||
"string.prototype.trimend": "^1.0.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string.prototype.trimstart": {
|
"string.prototype.trimstart": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
|
||||||
"integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
|
"integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"define-properties": "^1.1.3",
|
"call-bind": "^1.0.2",
|
||||||
"es-abstract": "^1.17.5"
|
"define-properties": "^1.1.3"
|
||||||
}
|
|
||||||
},
|
|
||||||
"string_decoder": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "~5.1.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stringify-entities": {
|
"stringify-entities": {
|
||||||
@@ -15111,6 +15185,17 @@
|
|||||||
"integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==",
|
"integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"tsconfig-paths": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"json5": "^2.2.0",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"strip-bom": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tslib": {
|
"tslib": {
|
||||||
"version": "1.13.0",
|
"version": "1.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||||
@@ -15194,6 +15279,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
||||||
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
||||||
},
|
},
|
||||||
|
"unbox-primitive": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==",
|
||||||
|
"requires": {
|
||||||
|
"function-bind": "^1.1.1",
|
||||||
|
"has-bigints": "^1.0.1",
|
||||||
|
"has-symbols": "^1.0.2",
|
||||||
|
"which-boxed-primitive": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"unherit": {
|
"unherit": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz",
|
||||||
@@ -16247,6 +16343,18 @@
|
|||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"which-boxed-primitive": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
|
||||||
|
"requires": {
|
||||||
|
"is-bigint": "^1.0.1",
|
||||||
|
"is-boolean-object": "^1.1.0",
|
||||||
|
"is-number-object": "^1.0.4",
|
||||||
|
"is-string": "^1.0.5",
|
||||||
|
"is-symbol": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"which-module": {
|
"which-module": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||||
@@ -16361,15 +16469,6 @@
|
|||||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"xregexp": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime-corejs3": "^7.8.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|||||||
4
client/package.json
vendored
4
client/package.json
vendored
@@ -64,9 +64,9 @@
|
|||||||
"eslint-config-airbnb": "^18.1.0",
|
"eslint-config-airbnb": "^18.1.0",
|
||||||
"eslint-import-resolver-webpack": "^0.12.1",
|
"eslint-import-resolver-webpack": "^0.12.1",
|
||||||
"eslint-loader": "^4.0.2",
|
"eslint-loader": "^4.0.2",
|
||||||
"eslint-plugin-import": "^2.20.2",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||||
"eslint-plugin-react": "^7.20.0",
|
"eslint-plugin-react": "^7.24.0",
|
||||||
"eslint-plugin-react-hooks": "^2.5.0",
|
"eslint-plugin-react-hooks": "^2.5.0",
|
||||||
"file-loader": "6.0.0",
|
"file-loader": "6.0.0",
|
||||||
"html-webpack-plugin": "^4.3.0",
|
"html-webpack-plugin": "^4.3.0",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"reset_settings": "Изтрий всички настройки",
|
"reset_settings": "Изтрий всички настройки",
|
||||||
"update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация.",
|
"update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация.",
|
||||||
"disable_ipv6": "Изключете IPv6 протокола",
|
"disable_ipv6": "Изключете IPv6 протокола",
|
||||||
|
"check_updates_now": "Провери за актуализации",
|
||||||
"show_blocked_responses": "Блокирано",
|
"show_blocked_responses": "Блокирано",
|
||||||
"port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това."
|
"port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "můžete použít <0>DNS razítka</0> pro <1>DNSCrypt</1> nebo <2>DNS skrze HTTPS</2> řešitele",
|
"example_upstream_sdns": "můžete použít <0>DNS razítka</0> pro <1>DNSCrypt</1> nebo <2>DNS skrze HTTPS</2> řešitele",
|
||||||
"example_upstream_tcp": "obyčejný DNS (přes TCP)",
|
"example_upstream_tcp": "obyčejný DNS (přes TCP)",
|
||||||
"all_lists_up_to_date_toast": "Všechny seznamy jsou již aktuální",
|
"all_lists_up_to_date_toast": "Všechny seznamy jsou již aktuální",
|
||||||
"updated_upstream_dns_toast": "Aktualizované upstream DNS servery",
|
"updated_upstream_dns_toast": "Odchozí servery byly úspěšně uloženy",
|
||||||
"dns_test_ok_toast": "Specifikované DNS servery pracují správně",
|
"dns_test_ok_toast": "Specifikované DNS servery pracují správně",
|
||||||
"dns_test_not_ok_toast": "Server \"{{key}}\": nemohl být použit, zkontrolujte, zda jste ho správně napsali",
|
"dns_test_not_ok_toast": "Server \"{{key}}\": nemohl být použit, zkontrolujte, zda jste ho správně napsali",
|
||||||
"unblock": "Odblokovat",
|
"unblock": "Odblokovat",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Načítání...",
|
"loading_table_status": "Načítání...",
|
||||||
"page_table_footer_text": "Stránka",
|
"page_table_footer_text": "Stránka",
|
||||||
"rows_table_footer_text": "řádky",
|
"rows_table_footer_text": "řádky",
|
||||||
"updated_custom_filtering_toast": "Aktualizovaná vlastní pravidla filtrování",
|
"updated_custom_filtering_toast": "Vlastní pravidla byla úspěšně uložena",
|
||||||
"rule_removed_from_custom_filtering_toast": "Pravidlo odstraněno z vlastních pravidel filtrování: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Pravidlo odstraněno z vlastních pravidel filtrování: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Pravidlo přidáno do vlastních pravidel filtrování: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Pravidlo přidáno do vlastních pravidel filtrování: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Budete muset nakonfigurovat Vaše zařízení nebo router, aby používali DNS server na následujících adresách:",
|
"install_settings_dns_desc": "Budete muset nakonfigurovat Vaše zařízení nebo router, aby používali DNS server na následujících adresách:",
|
||||||
"install_settings_all_interfaces": "Všechna rozhraní",
|
"install_settings_all_interfaces": "Všechna rozhraní",
|
||||||
"install_auth_title": "Ověřování",
|
"install_auth_title": "Ověřování",
|
||||||
"install_auth_desc": "Doporučujeme Vám nakonfigurovat v administrátorském webovém rozhraní AdGuard Home ověření Vaší identity heslem. I když je přístupné pouze ve Vaší lokální síti, je stále důležité chránit jej před neomezeným přístupem.",
|
"install_auth_desc": "V administrátorském webovém rozhraní AdGuard Home musí být nastaveno ověřovací heslo. I když je AdGuard Home přístupný pouze v místní síti, je důležité jej chránit před neomezeným přístupem.",
|
||||||
"install_auth_username": "Uživatelské jméno",
|
"install_auth_username": "Uživatelské jméno",
|
||||||
"install_auth_password": "Heslo",
|
"install_auth_password": "Heslo",
|
||||||
"install_auth_confirm": "Potvrďte heslo",
|
"install_auth_confirm": "Potvrďte heslo",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Opravdu chcete vyčistit statistiky?",
|
"statistics_clear_confirm": "Opravdu chcete vyčistit statistiky?",
|
||||||
"statistics_retention_confirm": "Opravdu chcete změnit uchovávání statistik? Pokud snížíte hodnotu intervalu, některá data budou ztracena",
|
"statistics_retention_confirm": "Opravdu chcete změnit uchovávání statistik? Pokud snížíte hodnotu intervalu, některá data budou ztracena",
|
||||||
"statistics_cleared": "Statistiky úspěšně vyčištěny",
|
"statistics_cleared": "Statistiky úspěšně vyčištěny",
|
||||||
|
"statistics_enable": "Povolit statistiky",
|
||||||
"interval_hours": "Hodiny: {{count}}",
|
"interval_hours": "Hodiny: {{count}}",
|
||||||
"interval_hours_plural": "Hodiny: {{count}}",
|
"interval_hours_plural": "Hodiny: {{count}}",
|
||||||
"filters_configuration": "Konfigurace filtrů",
|
"filters_configuration": "Konfigurace filtrů",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Klikněte pro zobrazení dotazů",
|
"click_to_view_queries": "Klikněte pro zobrazení dotazů",
|
||||||
"port_53_faq_link": "Port 53 je často obsazen službami \"DNSStubListener\" nebo \"systemd-resolved\". Přečtěte si <0>tento návod</0> o tom, jak to vyřešit.",
|
"port_53_faq_link": "Port 53 je často obsazen službami \"DNSStubListener\" nebo \"systemd-resolved\". Přečtěte si <0>tento návod</0> o tom, jak to vyřešit.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home zruší všechny DNS dotazy tohoto klienta.",
|
"adg_will_drop_dns_queries": "AdGuard Home zruší všechny DNS dotazy tohoto klienta.",
|
||||||
"client_not_in_allowed_clients": "Tento klient není povolen, protože není na seznamu \"Povolení klienti\".",
|
"filter_allowlist": "VAROVÁNÍ: Tato akce také vyloučí pravidlo \"{{disallowed_rule}}\" ze seznamu povolených klientů.",
|
||||||
"experimental": "Experimentální"
|
"last_rule_in_allowlist": "Nelze zakázat tohoto klienta, protože vyloučení pravidla \"{{disallowed_rule}}\" ZRUŠÍ seznam \"Povolených klientů\".",
|
||||||
|
"experimental": "Experimentální",
|
||||||
|
"use_saved_key": "Použít dříve uložený klíče"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "du kan bruge <0>DNS Stamps</0> til <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-resolvers",
|
"example_upstream_sdns": "du kan bruge <0>DNS Stamps</0> til <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-resolvers",
|
||||||
"example_upstream_tcp": "almindelig DNS (over TCP)",
|
"example_upstream_tcp": "almindelig DNS (over TCP)",
|
||||||
"all_lists_up_to_date_toast": "Alle lister er allerede opdaterede",
|
"all_lists_up_to_date_toast": "Alle lister er allerede opdaterede",
|
||||||
"updated_upstream_dns_toast": "Opdaterede upstream DNS-serverene",
|
"updated_upstream_dns_toast": "Upstream-servere er gemt",
|
||||||
"dns_test_ok_toast": "Angivne DNS-servere fungerer korrekt",
|
"dns_test_ok_toast": "Angivne DNS-servere fungerer korrekt",
|
||||||
"dns_test_not_ok_toast": "Server \"{{key}}\": Kunne ikke bruges. Tjek, at du har angivet den korrekt",
|
"dns_test_not_ok_toast": "Server \"{{key}}\": Kunne ikke bruges. Tjek, at du har angivet den korrekt",
|
||||||
"unblock": "Afblokering",
|
"unblock": "Afblokering",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Indlæser...",
|
"loading_table_status": "Indlæser...",
|
||||||
"page_table_footer_text": "Side",
|
"page_table_footer_text": "Side",
|
||||||
"rows_table_footer_text": "rækker",
|
"rows_table_footer_text": "rækker",
|
||||||
"updated_custom_filtering_toast": "Tilpassede filtreringsregler er nu opdateret",
|
"updated_custom_filtering_toast": "Tilpassede regler er gemt",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regel fjernet fra de tilpassede filtreringsregler: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Regel fjernet fra de tilpassede filtreringsregler: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regel føjet til de tilpassede filtreringsregler: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Regel føjet til de tilpassede filtreringsregler: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Du skal opsætte dine enheder eller router til at bruge DNS-serveren på flg. adresser:",
|
"install_settings_dns_desc": "Du skal opsætte dine enheder eller router til at bruge DNS-serveren på flg. adresser:",
|
||||||
"install_settings_all_interfaces": "Alle grænseflader",
|
"install_settings_all_interfaces": "Alle grænseflader",
|
||||||
"install_auth_title": "Godkendelse",
|
"install_auth_title": "Godkendelse",
|
||||||
"install_auth_desc": "Det anbefales stærkt at opsætte adgangskodegodkendelse på din AdGuard Home admin webgrænseflade. Selvom den kun er tilgængelig på dit lokalnetværk, er det stadig vigtigt at få den beskyttet mod ubegrænset adgang.",
|
"install_auth_desc": "Adgangskodegodkendelse på din AdGuard Home admin-webflade skal opsættes. Selv hvis AdGuard Home kun er tilgængelig på lokalnetværket, er beskyttelse mod uautoriseret og ubegrænset adgang stadig vigtig.",
|
||||||
"install_auth_username": "Brugernavn",
|
"install_auth_username": "Brugernavn",
|
||||||
"install_auth_password": "Adgangskode",
|
"install_auth_password": "Adgangskode",
|
||||||
"install_auth_confirm": "Bekræft adgangskode",
|
"install_auth_confirm": "Bekræft adgangskode",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Sikker på, at du vil slette statistikkerne?",
|
"statistics_clear_confirm": "Sikker på, at du vil slette statistikkerne?",
|
||||||
"statistics_retention_confirm": "Sikker på, at du vil ændre på statistikbevaring? Mindskes intervalværdien, vil nogle data gå tabt",
|
"statistics_retention_confirm": "Sikker på, at du vil ændre på statistikbevaring? Mindskes intervalværdien, vil nogle data gå tabt",
|
||||||
"statistics_cleared": "Statistikkerne er ryddet",
|
"statistics_cleared": "Statistikkerne er ryddet",
|
||||||
|
"statistics_enable": "Aktivér statistikker",
|
||||||
"interval_hours": "{{count}} time",
|
"interval_hours": "{{count}} time",
|
||||||
"interval_hours_plural": "{{count}} timer",
|
"interval_hours_plural": "{{count}} timer",
|
||||||
"filters_configuration": "Filteropsætninger",
|
"filters_configuration": "Filteropsætninger",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Klik for at se forespørgsler",
|
"click_to_view_queries": "Klik for at se forespørgsler",
|
||||||
"port_53_faq_link": "Port 53 optages ofte af \"DNSStubListener\" eller \"systemd-resolved\" tjenester. Læs <0>denne instruktion</0> om, hvordan du løser dette.",
|
"port_53_faq_link": "Port 53 optages ofte af \"DNSStubListener\" eller \"systemd-resolved\" tjenester. Læs <0>denne instruktion</0> om, hvordan du løser dette.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home vil afbryde alle DNS-forespørgsler fra denne klient.",
|
"adg_will_drop_dns_queries": "AdGuard Home vil afbryde alle DNS-forespørgsler fra denne klient.",
|
||||||
"client_not_in_allowed_clients": "Klienten er ikke tilladt, fordi den ikke er på listen \"Tilladte klienter\".",
|
"filter_allowlist": "ADVARSEL: Denne handling udelukker også reglen \"{{disallowed_rule}}\" fra listen over tilladte klienter.",
|
||||||
"experimental": "Eksperimentel"
|
"last_rule_in_allowlist": "Kan ikke afvise denne klient, da udelukkelse af reglen \"{{disallowed_rule}}\" DEAKTIVERER listen \"Tilladte klienter\".",
|
||||||
|
"experimental": "Eksperimentel",
|
||||||
|
"use_saved_key": "Brug den tidligere gemte nøgle"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"form_error_mac_format": "Ungültiges MAC-Format",
|
"form_error_mac_format": "Ungültiges MAC-Format",
|
||||||
"form_error_client_id_format": "Ungültiges Client-ID-Format",
|
"form_error_client_id_format": "Ungültiges Client-ID-Format",
|
||||||
"form_error_server_name": "Ungültiger Servername",
|
"form_error_server_name": "Ungültiger Servername",
|
||||||
"form_error_subnet": "Subnetz „{{cidr}}” enthält nicht die IP-Adresse „{{ip}}”",
|
"form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“",
|
||||||
"form_error_positive": "Muss größer als 0 sein.",
|
"form_error_positive": "Muss größer als 0 sein.",
|
||||||
"form_error_negative": "Muss gleich oder größer als 0 (Null) sein",
|
"form_error_negative": "Muss gleich oder größer als 0 (Null) sein",
|
||||||
"range_end_error": "Muss größer als der Bereichsbeginn sein",
|
"range_end_error": "Muss größer als der Bereichsbeginn sein",
|
||||||
@@ -62,9 +62,9 @@
|
|||||||
"dhcp_warning": "Wenn Sie den DHCP-Server trotzdem aktivieren möchten, stellen Sie sicher, dass sich in Ihrem Netzwerk kein anderer aktiver DHCP-Server befindet. Andernfalls kann es bei angeschlossenen Geräten zu einem Ausfall des Internets kommen!",
|
"dhcp_warning": "Wenn Sie den DHCP-Server trotzdem aktivieren möchten, stellen Sie sicher, dass sich in Ihrem Netzwerk kein anderer aktiver DHCP-Server befindet. Andernfalls kann es bei angeschlossenen Geräten zu einem Ausfall des Internets kommen!",
|
||||||
"dhcp_error": "Es konnte nicht ermittelt werden, ob es einen anderen DHCP-Server im Netzwerk gibt.",
|
"dhcp_error": "Es konnte nicht ermittelt werden, ob es einen anderen DHCP-Server im Netzwerk gibt.",
|
||||||
"dhcp_static_ip_error": "Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Es konnte nicht ermittelt werden, ob diese Netzwerkschnittstelle mit statischer IP-Adresse konfiguriert ist. Bitte legen Sie eine statische IP-Adresse manuell fest.",
|
"dhcp_static_ip_error": "Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Es konnte nicht ermittelt werden, ob diese Netzwerkschnittstelle mit statischer IP-Adresse konfiguriert ist. Bitte legen Sie eine statische IP-Adresse manuell fest.",
|
||||||
"dhcp_dynamic_ip_found": "Ihr System verwendet die dynamische Konfiguration der IP-Adresse für die Schnittstelle <0>{{interfaceName}}</0>. Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Ihre aktuelle IP-Adresse ist <0>{{ipAddress}}</0>. Diese IP-Adresse wird automatisch als statisch festgelegt, sobald Sie auf die Schaltfläche „DHCP-Server aktivieren” klicken.",
|
"dhcp_dynamic_ip_found": "Ihr System verwendet die dynamische Konfiguration der IP-Adresse für die Schnittstelle <0>{{interfaceName}}</0>. Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Ihre aktuelle IP-Adresse ist <0>{{ipAddress}}</0>. Diese IP-Adresse wird automatisch als statisch festgelegt, sobald Sie auf die Schaltfläche „DHCP-Server aktivieren“ klicken.",
|
||||||
"dhcp_lease_added": "Statischer Lease „{{key}}” erfolgreich hinzugefügt",
|
"dhcp_lease_added": "Statischer Lease „{{key}}“ erfolgreich hinzugefügt",
|
||||||
"dhcp_lease_deleted": "Statischer Lease „{{key}}” erfolgreich entfernt",
|
"dhcp_lease_deleted": "Statischer Lease „{{key}}“ erfolgreich entfernt",
|
||||||
"dhcp_new_static_lease": "Neuer statischer Lease",
|
"dhcp_new_static_lease": "Neuer statischer Lease",
|
||||||
"dhcp_static_leases_not_found": "Keine statischen DHCP-Leases gefunden",
|
"dhcp_static_leases_not_found": "Keine statischen DHCP-Leases gefunden",
|
||||||
"dhcp_add_static_lease": "Statischen Lease hinzufügen",
|
"dhcp_add_static_lease": "Statischen Lease hinzufügen",
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
"dhcp_reset": "Möchten Sie die DHCP-Konfiguration wirklich zurücksetzen?",
|
"dhcp_reset": "Möchten Sie die DHCP-Konfiguration wirklich zurücksetzen?",
|
||||||
"country": "Land",
|
"country": "Land",
|
||||||
"city": "Stadt",
|
"city": "Stadt",
|
||||||
"delete_confirm": "Möchten Sie „{{key}}” wirklich löschen?",
|
"delete_confirm": "Möchten Sie „{{key}}“ wirklich löschen?",
|
||||||
"form_enter_hostname": "Gerätenamen eingeben",
|
"form_enter_hostname": "Gerätenamen eingeben",
|
||||||
"error_details": "Fehlerdetails",
|
"error_details": "Fehlerdetails",
|
||||||
"response_details": "Einzelheiten der Antwort",
|
"response_details": "Einzelheiten der Antwort",
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
"number_of_dns_query_days_plural": "Anzahl der DNS-Abfragen, die in den letzten {{count}} Tagen verarbeitet wurden",
|
"number_of_dns_query_days_plural": "Anzahl der DNS-Abfragen, die in den letzten {{count}} Tagen verarbeitet wurden",
|
||||||
"number_of_dns_query_24_hours": "Anzahl der in den letzten 24 Stunden durchgeführten DNS-Anfragen",
|
"number_of_dns_query_24_hours": "Anzahl der in den letzten 24 Stunden durchgeführten DNS-Anfragen",
|
||||||
"number_of_dns_query_blocked_24_hours": "Anzahl der durch Werbefilter und Host-Sperrlisten abgelehnte DNS-Anfragen",
|
"number_of_dns_query_blocked_24_hours": "Anzahl der durch Werbefilter und Host-Sperrlisten abgelehnte DNS-Anfragen",
|
||||||
"number_of_dns_query_blocked_24_hours_by_sec": "Anzahl der durch das AdGuard-Modul „Internetsicherheit” gesperrten DNS-Anfragen",
|
"number_of_dns_query_blocked_24_hours_by_sec": "Anzahl der durch das AdGuard-Modul „Internetsicherheit“ gesperrten DNS-Anfragen",
|
||||||
"number_of_dns_query_blocked_24_hours_adult": "Anzahl der gesperrten Webseiten mit jugendgefährdenden Inhalten",
|
"number_of_dns_query_blocked_24_hours_adult": "Anzahl der gesperrten Webseiten mit jugendgefährdenden Inhalten",
|
||||||
"enforced_save_search": "SafeSearch erzwungen",
|
"enforced_save_search": "SafeSearch erzwungen",
|
||||||
"number_of_dns_query_to_safe_search": "Anzahl der DNS-Anfragen bei denen SafeSearch für Suchanfragen erzwungen wurde",
|
"number_of_dns_query_to_safe_search": "Anzahl der DNS-Anfragen bei denen SafeSearch für Suchanfragen erzwungen wurde",
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "Sie können <0>DNS-Stempel</0> für <1>DNSCrypt</1> oder <2>DNS-over-HTTPS</2> Resolver benutzen",
|
"example_upstream_sdns": "Sie können <0>DNS-Stempel</0> für <1>DNSCrypt</1> oder <2>DNS-over-HTTPS</2> Resolver benutzen",
|
||||||
"example_upstream_tcp": "regulärer DNS (über TCP)",
|
"example_upstream_tcp": "regulärer DNS (über TCP)",
|
||||||
"all_lists_up_to_date_toast": "Alle Listen sind bereits auf dem neuesten Stand",
|
"all_lists_up_to_date_toast": "Alle Listen sind bereits auf dem neuesten Stand",
|
||||||
"updated_upstream_dns_toast": "Upstream-DNS-Server wurden aktualisiert",
|
"updated_upstream_dns_toast": "Upstream-Server erfolgreich gespeichert",
|
||||||
"dns_test_ok_toast": "Angegebene DNS-Server arbeiten ordnungsgemäß",
|
"dns_test_ok_toast": "Angegebene DNS-Server arbeiten ordnungsgemäß",
|
||||||
"dns_test_not_ok_toast": "Server „{{key}}“: konnte nicht verwendet werden, bitte überprüfen Sie die korrekte Schreibweise",
|
"dns_test_not_ok_toast": "Server „{{key}}“: konnte nicht verwendet werden, bitte überprüfen Sie die korrekte Schreibweise",
|
||||||
"unblock": "Entsperren",
|
"unblock": "Entsperren",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Wird geladen …",
|
"loading_table_status": "Wird geladen …",
|
||||||
"page_table_footer_text": "Seite",
|
"page_table_footer_text": "Seite",
|
||||||
"rows_table_footer_text": "Reihen",
|
"rows_table_footer_text": "Reihen",
|
||||||
"updated_custom_filtering_toast": "Die benutzerdefinierten Filterregeln wurden aktualisiert",
|
"updated_custom_filtering_toast": "Benutzerdefinierten Filterregeln erfolgreich gespeichert",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regel wurde aus den benutzerdefinierten Filterregeln entfernt: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Regel wurde aus den benutzerdefinierten Filterregeln entfernt: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regel wurde zu den benutzerdefinierten Filterregeln hinzugefügt: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Regel wurde zu den benutzerdefinierten Filterregeln hinzugefügt: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Sie müssen Ihre Geräte oder Ihren Router so konfigurieren, dass er den DNS-Server unter den folgenden Adressen verwendet:",
|
"install_settings_dns_desc": "Sie müssen Ihre Geräte oder Ihren Router so konfigurieren, dass er den DNS-Server unter den folgenden Adressen verwendet:",
|
||||||
"install_settings_all_interfaces": "Alle Schnittstellen",
|
"install_settings_all_interfaces": "Alle Schnittstellen",
|
||||||
"install_auth_title": "Authentifizierung",
|
"install_auth_title": "Authentifizierung",
|
||||||
"install_auth_desc": "Es wird dringend empfohlen, die Passwortauthentifizierung für Ihre AdGuard Home Administrator-Weboberfläche zu konfigurieren. Auch wenn es nur in Ihrem lokalen Netzwerk zugänglich ist, ist es dennoch wichtig, es vor unbefugtem Zugriff zu schützen.",
|
"install_auth_desc": "Die Passwortauthentifizierung für Ihre AdGuard Home Administrator-Weboberfläche muss konfiguriert sein. Auch wenn AdGuard Home nur in Ihrem lokalen Netzwerk zugänglich ist, ist es dennoch wichtig, es vor unbefugtem Zugriff zu schützen.",
|
||||||
"install_auth_username": "Benutzername",
|
"install_auth_username": "Benutzername",
|
||||||
"install_auth_password": "Passwort",
|
"install_auth_password": "Passwort",
|
||||||
"install_auth_confirm": "Passwort bestätigen",
|
"install_auth_confirm": "Passwort bestätigen",
|
||||||
@@ -325,22 +325,22 @@
|
|||||||
"install_devices_router_list_3": "Geben Sie dort Ihre AdGuard Home Server-Adressen ein.",
|
"install_devices_router_list_3": "Geben Sie dort Ihre AdGuard Home Server-Adressen ein.",
|
||||||
"install_devices_router_list_4": "Bei einigen Routertypen kann kein eigener DNS-Server eingerichtet werden. In diesem Fall kann es helfen, AdGuard Home als <0>DHCP-Server</0> einzurichten. Andernfalls sollten Sie im Handbuch des Routers nachsehen, wie Sie DNS-Server auf Ihrem konkreten Router-Modell anpassen können.",
|
"install_devices_router_list_4": "Bei einigen Routertypen kann kein eigener DNS-Server eingerichtet werden. In diesem Fall kann es helfen, AdGuard Home als <0>DHCP-Server</0> einzurichten. Andernfalls sollten Sie im Handbuch des Routers nachsehen, wie Sie DNS-Server auf Ihrem konkreten Router-Modell anpassen können.",
|
||||||
"install_devices_windows_list_1": "Öffnen Sie die Systemsteuerung über das Startmenü oder die Windows-Suche.",
|
"install_devices_windows_list_1": "Öffnen Sie die Systemsteuerung über das Startmenü oder die Windows-Suche.",
|
||||||
"install_devices_windows_list_2": "Öffnen Sie die Kategorie „Netzwerk und Internet” und dann „Netzwerk- und Freigabecenter”.",
|
"install_devices_windows_list_2": "Öffnen Sie die Kategorie „Netzwerk und Internet“ und dann „Netzwerk- und Freigabecenter“.",
|
||||||
"install_devices_windows_list_3": "Suchen Sie auf der linken Seite des Bildschirms nach „Adaptereinstellungen ändern” und klicken Sie darauf.",
|
"install_devices_windows_list_3": "Suchen Sie auf der linken Seite des Bildschirms nach „Adaptereinstellungen ändern“ und klicken Sie darauf.",
|
||||||
"install_devices_windows_list_4": "Wählen Sie Ihre aktive Verbindung aus, klicken Sie mit der rechten Maustaste darauf und wählen Sie „Eigenschaften”.",
|
"install_devices_windows_list_4": "Wählen Sie Ihre aktive Verbindung aus, klicken Sie mit der rechten Maustaste darauf und wählen Sie „Eigenschaften“.",
|
||||||
"install_devices_windows_list_5": "Suchen Sie in der Liste nach „Internet Protokoll Version 4 (TCP/IP)” (oder, für IPv6, „Internet Protocol Version 6 (TCP/IPv6)“), markieren Sie diese und klicken Sie dann erneut auf „Eigenschaften”.",
|
"install_devices_windows_list_5": "Suchen Sie in der Liste nach „Internet Protokoll Version 4 (TCP/IP)“ (oder, für IPv6, „Internet Protocol Version 6 (TCP/IPv6)“), markieren Sie diese und klicken Sie dann erneut auf „Eigenschaften“.",
|
||||||
"install_devices_windows_list_6": "Wählen Sie „Folgende DNS-Serveradressen verwenden” und geben Sie Ihre AdGuard Home-Serveradressen ein.",
|
"install_devices_windows_list_6": "Wählen Sie „Folgende DNS-Serveradressen verwenden“ und geben Sie Ihre AdGuard Home-Serveradressen ein.",
|
||||||
"install_devices_macos_list_1": "Klicken Sie auf das Apple-Symbol (oben links in der Menüzeile) und wählen den Eintrag „Systemeinstellungen”.",
|
"install_devices_macos_list_1": "Klicken Sie auf das Apple-Symbol (oben links in der Menüzeile) und wählen den Eintrag „Systemeinstellungen“.",
|
||||||
"install_devices_macos_list_2": "Klicken Sie dort auf „Netzwerk”",
|
"install_devices_macos_list_2": "Klicken Sie dort auf „Netzwerk“",
|
||||||
"install_devices_macos_list_3": "Wählen Sie die erste Verbindung in Ihrer Liste aus und klicken Sie auf „Weitere Optionen”.",
|
"install_devices_macos_list_3": "Wählen Sie die erste Verbindung in Ihrer Liste aus und klicken Sie auf „Weitere Optionen“.",
|
||||||
"install_devices_macos_list_4": "Wählen Sie den Tab „DNS” und geben Sie dort Ihre AdGuard Home-Serveradressen ein.",
|
"install_devices_macos_list_4": "Wählen Sie den Tab „DNS“ und geben Sie dort Ihre AdGuard Home-Serveradressen ein.",
|
||||||
"install_devices_android_list_1": "Tippen Sie auf dem Startbildschirm des Android-Menüs auf „Einstellungen”.",
|
"install_devices_android_list_1": "Tippen Sie auf dem Startbildschirm des Android-Menüs auf „Einstellungen“.",
|
||||||
"install_devices_android_list_2": "Tippen Sie im Menü auf „WLAN”. Der Bildschirm mit allen verfügbaren Netzwerken wird angezeigt (es ist nicht möglich, einen benutzerdefinierten DNS für die mobile Verbindung einzustellen).",
|
"install_devices_android_list_2": "Tippen Sie im Menü auf „WLAN“. Der Bildschirm mit allen verfügbaren Netzwerken wird angezeigt (es ist nicht möglich, einen benutzerdefinierten DNS für die mobile Verbindung einzustellen).",
|
||||||
"install_devices_android_list_3": "Drücken Sie lange auf das Netzwerk, mit dem Sie verbunden sind, und tippen Sie auf „Netzwerk ändern”.",
|
"install_devices_android_list_3": "Drücken Sie lange auf das Netzwerk, mit dem Sie verbunden sind, und tippen Sie auf „Netzwerk ändern“.",
|
||||||
"install_devices_android_list_4": "Bei einigen Geräten müssen Sie möglicherweise das Kontrollkästchen für „Erweitert” aktivieren, um weitere Einstellungen anzuzeigen. Um Ihre Android-DNS-Einstellungen anzupassen, müssen Sie die IP-Einstellungen von „DHCP” auf „Statisch” umstellen.",
|
"install_devices_android_list_4": "Bei einigen Geräten müssen Sie möglicherweise das Kontrollkästchen für „Erweitert“ aktivieren, um weitere Einstellungen anzuzeigen. Um Ihre Android-DNS-Einstellungen anzupassen, müssen Sie die IP-Einstellungen von „DHCP“ auf „Statisch“ umstellen.",
|
||||||
"install_devices_android_list_5": "Ändern Sie die Werte für „DNS 1” und „DNS 2” auf Ihre AdGuard Home-Serveradressen.",
|
"install_devices_android_list_5": "Ändern Sie die Werte für „DNS 1“ und „DNS 2“ auf Ihre AdGuard Home-Serveradressen.",
|
||||||
"install_devices_ios_list_1": "Tippen Sie auf dem Startbildschirm auf „Einstellungen”.",
|
"install_devices_ios_list_1": "Tippen Sie auf dem Startbildschirm auf „Einstellungen“.",
|
||||||
"install_devices_ios_list_2": "Wählen Sie „WLAN” im linken Menü (es ist nicht möglich, DNS für mobile Netzwerke zu konfigurieren).",
|
"install_devices_ios_list_2": "Wählen Sie „WLAN“ im linken Menü (es ist nicht möglich, DNS für mobile Netzwerke zu konfigurieren).",
|
||||||
"install_devices_ios_list_3": "Tippen Sie auf den Namen des aktuell aktiven Netzwerks.",
|
"install_devices_ios_list_3": "Tippen Sie auf den Namen des aktuell aktiven Netzwerks.",
|
||||||
"install_devices_ios_list_4": "Geben Sie im DNS-Feld Ihre AdGuard Home-Serveradressen ein.",
|
"install_devices_ios_list_4": "Geben Sie im DNS-Feld Ihre AdGuard Home-Serveradressen ein.",
|
||||||
"get_started": "Anfangen",
|
"get_started": "Anfangen",
|
||||||
@@ -356,7 +356,7 @@
|
|||||||
"encryption_redirect": "Automatisch auf HTTPS umleiten",
|
"encryption_redirect": "Automatisch auf HTTPS umleiten",
|
||||||
"encryption_redirect_desc": "Wenn aktiviert, leitet AdGuard Home Sie automatisch von HTTP- auf HTTPS-Adressen um.",
|
"encryption_redirect_desc": "Wenn aktiviert, leitet AdGuard Home Sie automatisch von HTTP- auf HTTPS-Adressen um.",
|
||||||
"encryption_https": "HTTPS-Port",
|
"encryption_https": "HTTPS-Port",
|
||||||
"encryption_https_desc": "Wenn der HTTPS-Port konfiguriert ist, ist die AdGuard Home-Administrationsschnittstelle über HTTPS zugänglich und bietet auch DNS-over-HTTPS am Server „/dns-query”.",
|
"encryption_https_desc": "Wenn der HTTPS-Port konfiguriert ist, ist die AdGuard Home-Administrationsschnittstelle über HTTPS zugänglich und bietet auch DNS-over-HTTPS am Server „/dns-query“.",
|
||||||
"encryption_dot": "DNS-over-TLS",
|
"encryption_dot": "DNS-over-TLS",
|
||||||
"encryption_dot_desc": "Wenn dieser Port konfiguriert ist, führt AdGuard Home auf diesem Port einen DNS-over-TLS-Server aus.",
|
"encryption_dot_desc": "Wenn dieser Port konfiguriert ist, führt AdGuard Home auf diesem Port einen DNS-over-TLS-Server aus.",
|
||||||
"encryption_doq": "Port für DNS-over-QUIC",
|
"encryption_doq": "Port für DNS-over-QUIC",
|
||||||
@@ -410,18 +410,18 @@
|
|||||||
"ip_address": "IP-Adresse",
|
"ip_address": "IP-Adresse",
|
||||||
"client_identifier_desc": "Clients können durch die IP-Adresse, CIDR, MAC-Adresse oder eine spezielle Client-ID (können für DoT/DoH/DoQ verwendet werden) identifiziert werden. <0>Hier</0> erfahren Sie mehr darüber, wie Sie Kunden identifizieren.",
|
"client_identifier_desc": "Clients können durch die IP-Adresse, CIDR, MAC-Adresse oder eine spezielle Client-ID (können für DoT/DoH/DoQ verwendet werden) identifiziert werden. <0>Hier</0> erfahren Sie mehr darüber, wie Sie Kunden identifizieren.",
|
||||||
"form_enter_ip": "IP-Adresse eingeben",
|
"form_enter_ip": "IP-Adresse eingeben",
|
||||||
"form_enter_subnet_ip": "IP-Adresse zum Subnetz „{{cidr}}” hinzufügen",
|
"form_enter_subnet_ip": "IP-Adresse zum Subnetz „{{cidr}}“ hinzufügen",
|
||||||
"form_enter_mac": "MAC-Adresse eingeben",
|
"form_enter_mac": "MAC-Adresse eingeben",
|
||||||
"form_enter_id": "Kennung eingeben",
|
"form_enter_id": "Kennung eingeben",
|
||||||
"form_add_id": "Kennung hinzufügen",
|
"form_add_id": "Kennung hinzufügen",
|
||||||
"form_client_name": "Clientnamen eingeben",
|
"form_client_name": "Clientnamen eingeben",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"client_global_settings": "Allgemeine Einstellungen nutzen",
|
"client_global_settings": "Allgemeine Einstellungen nutzen",
|
||||||
"client_deleted": "Client „{{key}}” erfolgreich entfernt",
|
"client_deleted": "Client „{{key}}“ erfolgreich entfernt",
|
||||||
"client_added": "Client „{{key}}” erfolgreich hinzugefügt",
|
"client_added": "Client „{{key}}“ erfolgreich hinzugefügt",
|
||||||
"client_updated": "Client „{{key}}” erfolgreich aktualisiert",
|
"client_updated": "Client „{{key}}“ erfolgreich aktualisiert",
|
||||||
"clients_not_found": "Keine Clients gefunden",
|
"clients_not_found": "Keine Clients gefunden",
|
||||||
"client_confirm_delete": "Möchten Sie den Client „{{key}}” wirklich löschen?",
|
"client_confirm_delete": "Möchten Sie den Client „{{key}}“ wirklich löschen?",
|
||||||
"list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?",
|
"list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?",
|
||||||
"auto_clients_title": "Clients (Laufzeit)",
|
"auto_clients_title": "Clients (Laufzeit)",
|
||||||
"auto_clients_desc": "Daten zu den Clients, die AdGuard Home verwenden, aber nicht in der Konfiguration gespeichert sind",
|
"auto_clients_desc": "Daten zu den Clients, die AdGuard Home verwenden, aber nicht in der Konfiguration gespeichert sind",
|
||||||
@@ -441,11 +441,11 @@
|
|||||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Zeichenkette <1>{{address}}</1> verwenden.",
|
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Zeichenkette <1>{{address}}</1> verwenden.",
|
||||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Zeichenkette <1>{{address}}</1> verwenden.",
|
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Zeichenkette <1>{{address}}</1> verwenden.",
|
||||||
"setup_dns_privacy_3": "<0>Hier ist eine Liste von Software, die Sie verwenden können.</0>",
|
"setup_dns_privacy_3": "<0>Hier ist eine Liste von Software, die Sie verwenden können.</0>",
|
||||||
"setup_dns_privacy_4": "Auf einem iOS 14 oder macOS Big Sur-Gerät können Sie eine spezielle Datei „.mobileconfig” herunterladen, die Server für <highlight>DNS-über-HTTPS</highlight> oder <highlight>DNS-über-TLS</highlight> zu den DNS-Einstellungen hinzufügt.",
|
"setup_dns_privacy_4": "Auf einem iOS 14 oder macOS Big Sur-Gerät können Sie eine spezielle Datei „.mobileconfig“ herunterladen, die Server für <highlight>DNS-über-HTTPS</highlight> oder <highlight>DNS-über-TLS</highlight> zu den DNS-Einstellungen hinzufügt.",
|
||||||
"setup_dns_privacy_android_1": "Android 9 unterstützt DNS-over-TLS nativ. Um es zu konfigurieren, gehen Sie zu „Einstellungen” → „Netzwerk & Internet” → „Erweitert” → „Privater DNS” und geben Sie dort Ihren Domainnamen ein.",
|
"setup_dns_privacy_android_1": "Android 9 unterstützt DNS-over-TLS nativ. Um es zu konfigurieren, gehen Sie zu „Einstellungen“ → „Netzwerk & Internet“ → „Erweitert“ → „Privater DNS“ und geben Sie dort Ihren Domainnamen ein.",
|
||||||
"setup_dns_privacy_android_2": "<0>AdGuard für Android</0> unterstützt <1>DNS-over-HTTTPS</1> und <1>DNS-over-TLS</1>.",
|
"setup_dns_privacy_android_2": "<0>AdGuard für Android</0> unterstützt <1>DNS-over-HTTTPS</1> und <1>DNS-over-TLS</1>.",
|
||||||
"setup_dns_privacy_android_3": "„<0>Intra</0>” fügt <1>DNS-over-HTTPS</1>-Unterstützung zu Android hinzu.",
|
"setup_dns_privacy_android_3": "„<0>Intra</0>“ fügt <1>DNS-over-HTTPS</1>-Unterstützung zu Android hinzu.",
|
||||||
"setup_dns_privacy_ios_1": "„<0>DNSCloak</0>” unterstützt <1>DNS-over-HTTPS</1>, aber um es so zu konfigurieren, dass es Ihren eigenen Server verwendet, müssen Sie einen <2>DNS-Stempel</2> dafür generieren.",
|
"setup_dns_privacy_ios_1": "„<0>DNSCloak</0>“ unterstützt <1>DNS-over-HTTPS</1>, aber um es so zu konfigurieren, dass es Ihren eigenen Server verwendet, müssen Sie einen <2>DNS-Stempel</2> dafür generieren.",
|
||||||
"setup_dns_privacy_ios_2": "<0>AdGuard für iOS</0> unterstützt die Einrichtung von <1>DNS-over-HTTTPS</1> und <1>DNS-over-TLS</1>.",
|
"setup_dns_privacy_ios_2": "<0>AdGuard für iOS</0> unterstützt die Einrichtung von <1>DNS-over-HTTTPS</1> und <1>DNS-over-TLS</1>.",
|
||||||
"setup_dns_privacy_other_title": "Weitere Umsetzungen",
|
"setup_dns_privacy_other_title": "Weitere Umsetzungen",
|
||||||
"setup_dns_privacy_other_1": "AdGuard Home selbst kann ein sicherer DNS-Client auf jeder Plattform sein.",
|
"setup_dns_privacy_other_1": "AdGuard Home selbst kann ein sicherer DNS-Client auf jeder Plattform sein.",
|
||||||
@@ -455,11 +455,11 @@
|
|||||||
"setup_dns_privacy_other_5": "Weitere Umsetzungen finden Sie <0>hier</0> und <1>hier</1>.",
|
"setup_dns_privacy_other_5": "Weitere Umsetzungen finden Sie <0>hier</0> und <1>hier</1>.",
|
||||||
"setup_dns_privacy_ioc_mac": "Konfiguration für iOS und macOS",
|
"setup_dns_privacy_ioc_mac": "Konfiguration für iOS und macOS",
|
||||||
"setup_dns_notice": "Um <1>DNS-over-HTTTPS</1> oder <1>DNS-over-TLS</1> verwenden zu können, müssen Sie in den AdGuard Home Einstellungen die <0>Verschlüsselung konfigurieren</0>.",
|
"setup_dns_notice": "Um <1>DNS-over-HTTTPS</1> oder <1>DNS-over-TLS</1> verwenden zu können, müssen Sie in den AdGuard Home Einstellungen die <0>Verschlüsselung konfigurieren</0>.",
|
||||||
"rewrite_added": "DNS-Umschreibung für „{{key}}” erfolgreich hinzugefügt",
|
"rewrite_added": "DNS-Umschreibung für „{{key}}“ erfolgreich hinzugefügt",
|
||||||
"rewrite_deleted": "DNS-Umschreibung für „{{key}}” erfolgreich entfernt",
|
"rewrite_deleted": "DNS-Umschreibung für „{{key}}“ erfolgreich entfernt",
|
||||||
"rewrite_add": "DNS-Umschreibung hinzufügen",
|
"rewrite_add": "DNS-Umschreibung hinzufügen",
|
||||||
"rewrite_not_found": "Keine DNS-Umschreibungen gefunden",
|
"rewrite_not_found": "Keine DNS-Umschreibungen gefunden",
|
||||||
"rewrite_confirm_delete": "Möchten Sie die DNS-Umschreibung für „{{key}}” wirklich entfernen?",
|
"rewrite_confirm_delete": "Möchten Sie die DNS-Umschreibung für „{{key}}“ wirklich entfernen?",
|
||||||
"rewrite_desc": "Ermöglicht die einfache Konfiguration der benutzerdefinierten DNS-Antwort für einen bestimmten Domainnamen.",
|
"rewrite_desc": "Ermöglicht die einfache Konfiguration der benutzerdefinierten DNS-Antwort für einen bestimmten Domainnamen.",
|
||||||
"rewrite_applied": "Umschreibungsregel ist angewendet",
|
"rewrite_applied": "Umschreibungsregel ist angewendet",
|
||||||
"rewrite_hosts_applied": "Von Hostdatei-Regel umgeschrieben",
|
"rewrite_hosts_applied": "Von Hostdatei-Regel umgeschrieben",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Möchten Sie die Statistiken wirklich löschen?",
|
"statistics_clear_confirm": "Möchten Sie die Statistiken wirklich löschen?",
|
||||||
"statistics_retention_confirm": "Möchten Sie wirklich die Aufbewahrung der Statistiken ändern? Wenn Sie den Zeitabstand verringern, gehen einige Daten verloren.",
|
"statistics_retention_confirm": "Möchten Sie wirklich die Aufbewahrung der Statistiken ändern? Wenn Sie den Zeitabstand verringern, gehen einige Daten verloren.",
|
||||||
"statistics_cleared": "Statistiken wurden erfolgreich gelöscht",
|
"statistics_cleared": "Statistiken wurden erfolgreich gelöscht",
|
||||||
|
"statistics_enable": "Statistiken aktivieren",
|
||||||
"interval_hours": "{{count}} Stunde",
|
"interval_hours": "{{count}} Stunde",
|
||||||
"interval_hours_plural": "{{count}} Stunden",
|
"interval_hours_plural": "{{count}} Stunden",
|
||||||
"filters_configuration": "Filterkonfiguration",
|
"filters_configuration": "Filterkonfiguration",
|
||||||
@@ -538,7 +539,7 @@
|
|||||||
"disable_ipv6_desc": "Löschen Sie alle DNS-Abfragen für IPv6-Adressen (Typ AAAA).",
|
"disable_ipv6_desc": "Löschen Sie alle DNS-Abfragen für IPv6-Adressen (Typ AAAA).",
|
||||||
"fastest_addr": "Schnellste IP-Adresse",
|
"fastest_addr": "Schnellste IP-Adresse",
|
||||||
"fastest_addr_desc": "Fragen Sie alle DNS-Server ab und geben Sie die schnellste IP-Adresse unter allen Antworten zurück. Dies verlangsamt DNS-Abfragen, da AdGuard Home auf Antworten von allen DNS-Servern warten muss, verbessert jedoch die Gesamtkonnektivität.",
|
"fastest_addr_desc": "Fragen Sie alle DNS-Server ab und geben Sie die schnellste IP-Adresse unter allen Antworten zurück. Dies verlangsamt DNS-Abfragen, da AdGuard Home auf Antworten von allen DNS-Servern warten muss, verbessert jedoch die Gesamtkonnektivität.",
|
||||||
"autofix_warning_text": "Wenn Sie auf „Beheben” klicken, konfiguriert AdGuardHome Ihr System für die Verwendung des AdGuardHome-DNS-Servers.",
|
"autofix_warning_text": "Wenn Sie auf „Beheben“ klicken, konfiguriert AdGuardHome Ihr System für die Verwendung des AdGuardHome-DNS-Servers.",
|
||||||
"autofix_warning_list": "Es werden folgende Aufgaben ausgeführt: <0>Deaktivieren des DNSStubListener-Systems</0> <0>Festlegen der DNS-Server-Adresse auf 127.0.0.1</0> <0>Ersetzen des symbolischen Linkziels von /etc/resolv.conf auf /run/systemd/resolve/resolv.conf</0> <0>Anhalten des DNSStubListener (systemseitig aufgelöster Dienst wird nachladen)</0>",
|
"autofix_warning_list": "Es werden folgende Aufgaben ausgeführt: <0>Deaktivieren des DNSStubListener-Systems</0> <0>Festlegen der DNS-Server-Adresse auf 127.0.0.1</0> <0>Ersetzen des symbolischen Linkziels von /etc/resolv.conf auf /run/systemd/resolve/resolv.conf</0> <0>Anhalten des DNSStubListener (systemseitig aufgelöster Dienst wird nachladen)</0>",
|
||||||
"autofix_warning_result": "Als Folge daraus werden alle DNS-Anforderungen von Ihrem System standardmäßig von AdGuardHome verarbeitet.",
|
"autofix_warning_result": "Als Folge daraus werden alle DNS-Anforderungen von Ihrem System standardmäßig von AdGuardHome verarbeitet.",
|
||||||
"tags_title": "Schlagwörter",
|
"tags_title": "Schlagwörter",
|
||||||
@@ -558,10 +559,10 @@
|
|||||||
"check_service": "Dienstname: {{service}}",
|
"check_service": "Dienstname: {{service}}",
|
||||||
"service_name": "Name des Dienstes",
|
"service_name": "Name des Dienstes",
|
||||||
"check_not_found": "Nicht in Ihren Filterlisten enthalten",
|
"check_not_found": "Nicht in Ihren Filterlisten enthalten",
|
||||||
"client_confirm_block": "Möchten Sie den Client „{{ip}}” wirklich sperren?",
|
"client_confirm_block": "Möchten Sie den Client „{{ip}}“ wirklich sperren?",
|
||||||
"client_confirm_unblock": "Möchten Sie den Client „{{ip}}” wirklich entsperren?",
|
"client_confirm_unblock": "Möchten Sie den Client „{{ip}}“ wirklich entsperren?",
|
||||||
"client_blocked": "Client „{{ip}}” erfolgreich gesperrt",
|
"client_blocked": "Client „{{ip}}“ erfolgreich gesperrt",
|
||||||
"client_unblocked": "Client „{{ip}}” erfolgreich entsperrt",
|
"client_unblocked": "Client „{{ip}}“ erfolgreich entsperrt",
|
||||||
"static_ip": "Feste IP-Adresse",
|
"static_ip": "Feste IP-Adresse",
|
||||||
"static_ip_desc": "AdGuard Home ist ein Server und benötigt daher eine feste IP-Adresse, um ordnungsgemäß zu funktionieren. Andernfalls weist Ihr Router diesem Gerät möglicherweise irgendwann eine andere IP-Adresse zu.",
|
"static_ip_desc": "AdGuard Home ist ein Server und benötigt daher eine feste IP-Adresse, um ordnungsgemäß zu funktionieren. Andernfalls weist Ihr Router diesem Gerät möglicherweise irgendwann eine andere IP-Adresse zu.",
|
||||||
"set_static_ip": "Feste IP-Adresse festlegen",
|
"set_static_ip": "Feste IP-Adresse festlegen",
|
||||||
@@ -610,8 +611,10 @@
|
|||||||
"setup_config_to_enable_dhcp_server": "Einrichten der Konfiguration zur Aktivierung des DHCP-Servers",
|
"setup_config_to_enable_dhcp_server": "Einrichten der Konfiguration zur Aktivierung des DHCP-Servers",
|
||||||
"original_response": "Ursprüngliche Antwort",
|
"original_response": "Ursprüngliche Antwort",
|
||||||
"click_to_view_queries": "Anklicken, um Abfragen anzuzeigen",
|
"click_to_view_queries": "Anklicken, um Abfragen anzuzeigen",
|
||||||
"port_53_faq_link": "Port 53 wird oft von Diensten wie „DNSStubListener” oder „systemresolved” belegt. Bitte lesen Sie <0>diese Anweisung</0>, wie dies behoben werden kann.",
|
"port_53_faq_link": "Port 53 wird oft von Diensten wie „DNSStubListener“ oder „systemresolved“ belegt. Bitte lesen Sie <0>diese Anweisung</0>, wie dies behoben werden kann.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home wird alle DNS-Abfragen von diesem Client verwerfen.",
|
"adg_will_drop_dns_queries": "AdGuard Home wird alle DNS-Abfragen von diesem Client verwerfen.",
|
||||||
"client_not_in_allowed_clients": "Dieser Client ist nicht zugelassen, da dieser nicht in der Liste „Erlaubte Clients” aufgeführt ist.",
|
"filter_allowlist": "Warnhinweis: Durch diese Aktion wird außerdem die Regel „{{disallowed_rule}}“ aus der Liste der zugelassenen Clients ausgeschlossen.",
|
||||||
"experimental": "Experimentell"
|
"last_rule_in_allowlist": "Dieser Client kann nicht gesperrt werden, da das Ausschließen der Regel „{{disallowed_rule}}“ die Liste „Zugelassene Clients“ deaktivieren würde.",
|
||||||
|
"experimental": "Experimentell",
|
||||||
|
"use_saved_key": "Zuvor gespeicherten Schlüssel verwenden"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "you can use <0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers",
|
"example_upstream_sdns": "you can use <0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers",
|
||||||
"example_upstream_tcp": "regular DNS (over TCP)",
|
"example_upstream_tcp": "regular DNS (over TCP)",
|
||||||
"all_lists_up_to_date_toast": "All lists are already up-to-date",
|
"all_lists_up_to_date_toast": "All lists are already up-to-date",
|
||||||
"updated_upstream_dns_toast": "Updated the upstream DNS servers",
|
"updated_upstream_dns_toast": "Upstream servers successfully saved",
|
||||||
"dns_test_ok_toast": "Specified DNS servers are working correctly",
|
"dns_test_ok_toast": "Specified DNS servers are working correctly",
|
||||||
"dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly",
|
"dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly",
|
||||||
"unblock": "Unblock",
|
"unblock": "Unblock",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Loading...",
|
"loading_table_status": "Loading...",
|
||||||
"page_table_footer_text": "Page",
|
"page_table_footer_text": "Page",
|
||||||
"rows_table_footer_text": "rows",
|
"rows_table_footer_text": "rows",
|
||||||
"updated_custom_filtering_toast": "Updated the custom filtering rules",
|
"updated_custom_filtering_toast": "Custom rules successfully saved",
|
||||||
"rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server on the following addresses:",
|
"install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server on the following addresses:",
|
||||||
"install_settings_all_interfaces": "All interfaces",
|
"install_settings_all_interfaces": "All interfaces",
|
||||||
"install_auth_title": "Authentication",
|
"install_auth_title": "Authentication",
|
||||||
"install_auth_desc": "It is highly recommended to configure password authentication to your AdGuard Home admin web interface. Even if it is accessible only in your local network, it is still important to protect it from unrestricted access.",
|
"install_auth_desc": "Password authentication to your AdGuard Home admin web interface must be configured. Even if AdGuard Home is accessible only in your local network, it is still important to protect it from unrestricted access.",
|
||||||
"install_auth_username": "Username",
|
"install_auth_username": "Username",
|
||||||
"install_auth_password": "Password",
|
"install_auth_password": "Password",
|
||||||
"install_auth_confirm": "Confirm password",
|
"install_auth_confirm": "Confirm password",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Are you sure you want to clear statistics?",
|
"statistics_clear_confirm": "Are you sure you want to clear statistics?",
|
||||||
"statistics_retention_confirm": "Are you sure you want to change statistics retention? If you decrease the interval value, some data will be lost",
|
"statistics_retention_confirm": "Are you sure you want to change statistics retention? If you decrease the interval value, some data will be lost",
|
||||||
"statistics_cleared": "Statistics successfully cleared",
|
"statistics_cleared": "Statistics successfully cleared",
|
||||||
|
"statistics_enable": "Enable statistics",
|
||||||
"interval_hours": "{{count}} hour",
|
"interval_hours": "{{count}} hour",
|
||||||
"interval_hours_plural": "{{count}} hours",
|
"interval_hours_plural": "{{count}} hours",
|
||||||
"filters_configuration": "Filters configuration",
|
"filters_configuration": "Filters configuration",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Click to view queries",
|
"click_to_view_queries": "Click to view queries",
|
||||||
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this.",
|
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.",
|
"adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.",
|
||||||
"client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.",
|
"filter_allowlist": "WARNING: This action also will exclude the rule \"{{disallowed_rule}}\" from the list of allowed clients.",
|
||||||
"experimental": "Experimental"
|
"last_rule_in_allowlist": "Cannot disallow this client because excluding the rule \"{{disallowed_rule}}\" will DISABLE \"Allowed clients\" list.",
|
||||||
|
"experimental": "Experimental",
|
||||||
|
"use_saved_key": "Use the previously saved key"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "puedes usar <0>DNS Stamps</0> para <1>DNSCrypt</1> o resolutores <2>DNS mediante HTTPS</2>",
|
"example_upstream_sdns": "puedes usar <0>DNS Stamps</0> para <1>DNSCrypt</1> o resolutores <2>DNS mediante HTTPS</2>",
|
||||||
"example_upstream_tcp": "DNS regular (mediante TCP)",
|
"example_upstream_tcp": "DNS regular (mediante TCP)",
|
||||||
"all_lists_up_to_date_toast": "Todas las listas ya están actualizadas",
|
"all_lists_up_to_date_toast": "Todas las listas ya están actualizadas",
|
||||||
"updated_upstream_dns_toast": "Servidores DNS de subida actualizados",
|
"updated_upstream_dns_toast": "Servidores DNS de subida guardados correctamente",
|
||||||
"dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
|
"dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
|
||||||
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revisa si lo has escrito correctamente",
|
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revisa si lo has escrito correctamente",
|
||||||
"unblock": "Desbloquear",
|
"unblock": "Desbloquear",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Cargando...",
|
"loading_table_status": "Cargando...",
|
||||||
"page_table_footer_text": "Página",
|
"page_table_footer_text": "Página",
|
||||||
"rows_table_footer_text": "filas",
|
"rows_table_footer_text": "filas",
|
||||||
"updated_custom_filtering_toast": "Reglas de filtrado personalizado actualizadas",
|
"updated_custom_filtering_toast": "Reglas personalizadas guardadas correctamente",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizado: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizado: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regla añadida a las reglas de filtrado personalizado: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Regla añadida a las reglas de filtrado personalizado: {{rule}}",
|
||||||
"query_log_response_status": "Estado: {{value}}",
|
"query_log_response_status": "Estado: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Deberás configurar tus dispositivos o router para usar el servidor DNS en las siguientes direcciones:",
|
"install_settings_dns_desc": "Deberás configurar tus dispositivos o router para usar el servidor DNS en las siguientes direcciones:",
|
||||||
"install_settings_all_interfaces": "Todas las interfaces",
|
"install_settings_all_interfaces": "Todas las interfaces",
|
||||||
"install_auth_title": "Autenticación",
|
"install_auth_title": "Autenticación",
|
||||||
"install_auth_desc": "Se recomienda encarecidamente configurar la autenticación por contraseña para la interfaz web de administración de AdGuard Home. Incluso si solo es accesible en tu red local, es importante que estés protegido contra el acceso no autorizado.",
|
"install_auth_desc": "Debe configurarse la autenticación por contraseña para la interfaz web de administración de AdGuard Home. Incluso si AdGuard Home es accesible solo en tu red local, es importante protegerlo del acceso no autorizado.",
|
||||||
"install_auth_username": "Usuario",
|
"install_auth_username": "Usuario",
|
||||||
"install_auth_password": "Contraseña",
|
"install_auth_password": "Contraseña",
|
||||||
"install_auth_confirm": "Confirmar contraseña",
|
"install_auth_confirm": "Confirmar contraseña",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "¿Estás seguro de que deseas borrar las estadísticas?",
|
"statistics_clear_confirm": "¿Estás seguro de que deseas borrar las estadísticas?",
|
||||||
"statistics_retention_confirm": "¿Estás seguro de que deseas cambiar la retención de estadísticas? Si disminuye el valor del intervalo, se perderán algunos datos",
|
"statistics_retention_confirm": "¿Estás seguro de que deseas cambiar la retención de estadísticas? Si disminuye el valor del intervalo, se perderán algunos datos",
|
||||||
"statistics_cleared": "Estadísticas borradas correctamente",
|
"statistics_cleared": "Estadísticas borradas correctamente",
|
||||||
|
"statistics_enable": "Habilitar estadísticas",
|
||||||
"interval_hours": "{{count}} hora",
|
"interval_hours": "{{count}} hora",
|
||||||
"interval_hours_plural": "{{count}} horas",
|
"interval_hours_plural": "{{count}} horas",
|
||||||
"filters_configuration": "Configuración de filtros",
|
"filters_configuration": "Configuración de filtros",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Clic para ver las consultas",
|
"click_to_view_queries": "Clic para ver las consultas",
|
||||||
"port_53_faq_link": "El puerto 53 suele estar ocupado por los servicios \"DNSStubListener\" o \"systemd-resolved\". Por favor lee <0>esta instrucción</0> sobre cómo resolver esto.",
|
"port_53_faq_link": "El puerto 53 suele estar ocupado por los servicios \"DNSStubListener\" o \"systemd-resolved\". Por favor lee <0>esta instrucción</0> sobre cómo resolver esto.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home descartará todas las consultas DNS de este cliente.",
|
"adg_will_drop_dns_queries": "AdGuard Home descartará todas las consultas DNS de este cliente.",
|
||||||
"client_not_in_allowed_clients": "El cliente no está permitido porque no está en la lista de \"Clientes permitidos\".",
|
"filter_allowlist": "ADVERTENCIA: Esta acción también excluirá la regla \"{{disallowed_rule}}\" de la lista de clientes permitidos.",
|
||||||
"experimental": "experimental"
|
"last_rule_in_allowlist": "No se puede desautorizar a este cliente porque al excluir la regla \"{{disallowed_rule}}\" DESHABILITARÁ la lista de \"Clientes permitidos\".",
|
||||||
|
"experimental": "experimental",
|
||||||
|
"use_saved_key": "Usar la clave guardada previamente"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps</0> pour <1>DNSCrypt</1> ou les resolveurs <2>DNS_over_HTTPS</2>",
|
"example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps</0> pour <1>DNSCrypt</1> ou les resolveurs <2>DNS_over_HTTPS</2>",
|
||||||
"example_upstream_tcp": "DNS classique (au-dessus de TCP)",
|
"example_upstream_tcp": "DNS classique (au-dessus de TCP)",
|
||||||
"all_lists_up_to_date_toast": "Toutes les listes sont déjà à jour",
|
"all_lists_up_to_date_toast": "Toutes les listes sont déjà à jour",
|
||||||
"updated_upstream_dns_toast": "Les serveurs DNS upstream sont mis à jour",
|
"updated_upstream_dns_toast": "Serveurs en amont enregistrés",
|
||||||
"dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement",
|
"dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement",
|
||||||
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur « {{key}} »: veuillez vérifier si le nom saisi est bien correct",
|
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur « {{key}} »: veuillez vérifier si le nom saisi est bien correct",
|
||||||
"unblock": "Débloquer",
|
"unblock": "Débloquer",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Chargement en cours ...",
|
"loading_table_status": "Chargement en cours ...",
|
||||||
"page_table_footer_text": "Page",
|
"page_table_footer_text": "Page",
|
||||||
"rows_table_footer_text": "lignes",
|
"rows_table_footer_text": "lignes",
|
||||||
"updated_custom_filtering_toast": "Règles de filtrage d'utilisateur mises à jour",
|
"updated_custom_filtering_toast": "Règles d'utilisateur enregistrées",
|
||||||
"rule_removed_from_custom_filtering_toast": "Règle retirée des règles d'utilisateur : {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Règle retirée des règles d'utilisateur : {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Règle ajoutée aux règles d'utilisateur : {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Règle ajoutée aux règles d'utilisateur : {{rule}}",
|
||||||
"query_log_response_status": "Statut : {{value}}",
|
"query_log_response_status": "Statut : {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Vous devrez configurer vos appareils et votre routeur pour utiliser le serveur DNS sur les adresses suivantes :",
|
"install_settings_dns_desc": "Vous devrez configurer vos appareils et votre routeur pour utiliser le serveur DNS sur les adresses suivantes :",
|
||||||
"install_settings_all_interfaces": "Toutes les interfaces",
|
"install_settings_all_interfaces": "Toutes les interfaces",
|
||||||
"install_auth_title": "Authentification",
|
"install_auth_title": "Authentification",
|
||||||
"install_auth_desc": "Il est fortement recommandé de configurer un mot de passe pour accéder à l'interface web administrateur AdGuard Home. Même si elle est disponible que dans votre réseau local, cela reste important de se protéger contre des accès non désirés.",
|
"install_auth_desc": "C'est nécessaire de configurer l'authentification par aide de mot de passe pour accéder à l'interface web administrateur de votre AdGuard Home. Même si AdGuard Home n'est accessible que dans votre réseau local, c'est important d'y restreindre accès aux tiers.",
|
||||||
"install_auth_username": "Nom d'utilisateur",
|
"install_auth_username": "Nom d'utilisateur",
|
||||||
"install_auth_password": "Mot de passe",
|
"install_auth_password": "Mot de passe",
|
||||||
"install_auth_confirm": "Confirmer le mot de passe",
|
"install_auth_confirm": "Confirmer le mot de passe",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Voulez-vous vraiment effacer les statistiques ?",
|
"statistics_clear_confirm": "Voulez-vous vraiment effacer les statistiques ?",
|
||||||
"statistics_retention_confirm": "Êtes-vous sûr de vouloir modifier le maintien des statistiques ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues",
|
"statistics_retention_confirm": "Êtes-vous sûr de vouloir modifier le maintien des statistiques ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues",
|
||||||
"statistics_cleared": "Statistiques effacées",
|
"statistics_cleared": "Statistiques effacées",
|
||||||
|
"statistics_enable": "Activer les statistiques",
|
||||||
"interval_hours": "{{count}} heure",
|
"interval_hours": "{{count}} heure",
|
||||||
"interval_hours_plural": "{{count}} heures",
|
"interval_hours_plural": "{{count}} heures",
|
||||||
"filters_configuration": "Configuration des filtres",
|
"filters_configuration": "Configuration des filtres",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Cliquez pour voir les requêtes",
|
"click_to_view_queries": "Cliquez pour voir les requêtes",
|
||||||
"port_53_faq_link": "Le port 53 est souvent occupé par les services « DNSStubListener » ou « systemd-resolved ». Veuillez lire <0>cette instruction</0> pour savoir comment résoudre ce problème.",
|
"port_53_faq_link": "Le port 53 est souvent occupé par les services « DNSStubListener » ou « systemd-resolved ». Veuillez lire <0>cette instruction</0> pour savoir comment résoudre ce problème.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home ignorera toutes les requêtes DNS de ce client.",
|
"adg_will_drop_dns_queries": "AdGuard Home ignorera toutes les requêtes DNS de ce client.",
|
||||||
"client_not_in_allowed_clients": "Le client n’est pas autorisé car il ne figure pas dans la liste « Clients autorisés ».",
|
"filter_allowlist": "ATTENTION : Cette action exclura également la règle « {{disallowed_rule}} » de la liste des clients autorisés.",
|
||||||
"experimental": "Expérimental"
|
"last_rule_in_allowlist": "Impossible d’interdire ce client, car l’exclusion de la règle « {{disallowed_rule}} » DÉSACTIVERA la liste des « clients autorisés ».",
|
||||||
|
"experimental": "Expérimental",
|
||||||
|
"use_saved_key": "Utiliser la clef précédemment enregistrée"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "használhatja a <0> DNS Stamps</0>-ot a <1>DNSCrypt</1> vagy a <2>DNS-over-HTTPS</2> feloldások érdekében",
|
"example_upstream_sdns": "használhatja a <0> DNS Stamps</0>-ot a <1>DNSCrypt</1> vagy a <2>DNS-over-HTTPS</2> feloldások érdekében",
|
||||||
"example_upstream_tcp": "hagyományos DNS (TCP felett)",
|
"example_upstream_tcp": "hagyományos DNS (TCP felett)",
|
||||||
"all_lists_up_to_date_toast": "Már minden lista naprakész",
|
"all_lists_up_to_date_toast": "Már minden lista naprakész",
|
||||||
"updated_upstream_dns_toast": "Frissítette az upstream DNS-kiszolgálókat",
|
"updated_upstream_dns_toast": "Upstream szerverek sikeresen mentve",
|
||||||
"dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek",
|
"dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek",
|
||||||
"dns_test_not_ok_toast": "Szerver \"{{key}}\": nem használható, ellenőrizze, hogy helyesen írta-e be",
|
"dns_test_not_ok_toast": "Szerver \"{{key}}\": nem használható, ellenőrizze, hogy helyesen írta-e be",
|
||||||
"unblock": "Feloldás",
|
"unblock": "Feloldás",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Betöltés...",
|
"loading_table_status": "Betöltés...",
|
||||||
"page_table_footer_text": "Oldal",
|
"page_table_footer_text": "Oldal",
|
||||||
"rows_table_footer_text": "sor",
|
"rows_table_footer_text": "sor",
|
||||||
"updated_custom_filtering_toast": "Egyéni szűrőszabályok frissítve",
|
"updated_custom_filtering_toast": "Egyéni szűrőszabályok sikeresen mentve",
|
||||||
"rule_removed_from_custom_filtering_toast": "Szabály eltávolítva az egyéni szűrőszabályok közül: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Szabály eltávolítva az egyéni szűrőszabályok közül: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Szabály hozzáadva az egyéni szűrőszabályokhoz: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Szabály hozzáadva az egyéni szűrőszabályokhoz: {{rule}}",
|
||||||
"query_log_response_status": "Állapot: {{value}}",
|
"query_log_response_status": "Állapot: {{value}}",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Biztosan vissza akarja állítani a statisztikákat?",
|
"statistics_clear_confirm": "Biztosan vissza akarja állítani a statisztikákat?",
|
||||||
"statistics_retention_confirm": "Biztos benne, hogy megváltoztatja a statisztika megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
|
"statistics_retention_confirm": "Biztos benne, hogy megváltoztatja a statisztika megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
|
||||||
"statistics_cleared": "A statisztikák sikeresen vissza lettek állítva",
|
"statistics_cleared": "A statisztikák sikeresen vissza lettek állítva",
|
||||||
|
"statistics_enable": "Statisztikák engedélyezése",
|
||||||
"interval_hours": "{{count}} óra",
|
"interval_hours": "{{count}} óra",
|
||||||
"interval_hours_plural": "{{count}} óra",
|
"interval_hours_plural": "{{count}} óra",
|
||||||
"filters_configuration": "Szűrők beállításai",
|
"filters_configuration": "Szűrők beállításai",
|
||||||
@@ -613,5 +614,6 @@
|
|||||||
"port_53_faq_link": "Az 53-as portot gyakran a \"DNSStubListener\" vagy a \"systemd-resolved\" (rendszer által feloldott) szolgáltatások használják. Kérjük, olvassa el <0>ezt az útmutatót</0> a probléma megoldásához.",
|
"port_53_faq_link": "Az 53-as portot gyakran a \"DNSStubListener\" vagy a \"systemd-resolved\" (rendszer által feloldott) szolgáltatások használják. Kérjük, olvassa el <0>ezt az útmutatót</0> a probléma megoldásához.",
|
||||||
"adg_will_drop_dns_queries": "Az AdGuard Home eldobja az összes DNS kérést erről a kliensről.",
|
"adg_will_drop_dns_queries": "Az AdGuard Home eldobja az összes DNS kérést erről a kliensről.",
|
||||||
"client_not_in_allowed_clients": "Ez a kliens nincs engedélyezve, mivel nincs rajta az \"Engedélyezett kliensek\" listáján.",
|
"client_not_in_allowed_clients": "Ez a kliens nincs engedélyezve, mivel nincs rajta az \"Engedélyezett kliensek\" listáján.",
|
||||||
"experimental": "Kísérleti"
|
"experimental": "Kísérleti",
|
||||||
|
"use_saved_key": "Előzőleg mentett kulcs használata"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Memuat...",
|
"loading_table_status": "Memuat...",
|
||||||
"page_table_footer_text": "Halaman",
|
"page_table_footer_text": "Halaman",
|
||||||
"rows_table_footer_text": "baris",
|
"rows_table_footer_text": "baris",
|
||||||
"updated_custom_filtering_toast": "Perbarui aturan penyaringan khusus",
|
"updated_custom_filtering_toast": "Aturan kustom berhasil disimpan",
|
||||||
"rule_removed_from_custom_filtering_toast": "Aturan dihapus dari aturan penyaringan khusus: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Aturan dihapus dari aturan penyaringan khusus: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Aturan ditambah ke aturan penyaringan khusus: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Aturan ditambah ke aturan penyaringan khusus: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -613,5 +613,6 @@
|
|||||||
"port_53_faq_link": "Port 53 sering ditempati oleh layanan \"DNSStubListener\" atau \"systemd-resolved\". Silakan baca <0>instruksi ini</0> tentang cara menyelesaikan ini.",
|
"port_53_faq_link": "Port 53 sering ditempati oleh layanan \"DNSStubListener\" atau \"systemd-resolved\". Silakan baca <0>instruksi ini</0> tentang cara menyelesaikan ini.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home akan menghapus semua permintaan DNS dari klien ini.",
|
"adg_will_drop_dns_queries": "AdGuard Home akan menghapus semua permintaan DNS dari klien ini.",
|
||||||
"client_not_in_allowed_clients": "Klien tidak diizinkan karena tidak ada dalam daftar \"Klien yang diizinkan\".",
|
"client_not_in_allowed_clients": "Klien tidak diizinkan karena tidak ada dalam daftar \"Klien yang diizinkan\".",
|
||||||
"experimental": "Eksperimental"
|
"experimental": "Eksperimental",
|
||||||
|
"use_saved_key": "Gunakan kunci yang disimpan sebelumnya"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,9 +48,9 @@
|
|||||||
"range_end_error": "Deve essere maggiore dell'intervallo di inizio",
|
"range_end_error": "Deve essere maggiore dell'intervallo di inizio",
|
||||||
"dhcp_form_gateway_input": "IP Gateway",
|
"dhcp_form_gateway_input": "IP Gateway",
|
||||||
"dhcp_form_subnet_input": "Maschera di sottorete",
|
"dhcp_form_subnet_input": "Maschera di sottorete",
|
||||||
"dhcp_form_range_title": "Range indirizzi IP",
|
"dhcp_form_range_title": "Intervallo di indirizzi IP",
|
||||||
"dhcp_form_range_start": "Inizio range",
|
"dhcp_form_range_start": "Intervallo iniziale",
|
||||||
"dhcp_form_range_end": "Fine range",
|
"dhcp_form_range_end": "Intervallo finale",
|
||||||
"dhcp_form_lease_title": "Tempo di lease DHCP (in secondi)",
|
"dhcp_form_lease_title": "Tempo di lease DHCP (in secondi)",
|
||||||
"dhcp_form_lease_input": "Durata lease",
|
"dhcp_form_lease_input": "Durata lease",
|
||||||
"dhcp_interface_select": "Seleziona l'interfaccia DHCP",
|
"dhcp_interface_select": "Seleziona l'interfaccia DHCP",
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "puoi utilizzare <0>DNS Stamps</0> per <1>DNSCrypt</1> oppure dei risolutori <2>DNS-over-HTTPS</2>",
|
"example_upstream_sdns": "puoi utilizzare <0>DNS Stamps</0> per <1>DNSCrypt</1> oppure dei risolutori <2>DNS-over-HTTPS</2>",
|
||||||
"example_upstream_tcp": "DNS regolari (via TCP)",
|
"example_upstream_tcp": "DNS regolari (via TCP)",
|
||||||
"all_lists_up_to_date_toast": "Tutte le liste sono aggiornate",
|
"all_lists_up_to_date_toast": "Tutte le liste sono aggiornate",
|
||||||
"updated_upstream_dns_toast": "Server DNS upstream aggiornati",
|
"updated_upstream_dns_toast": "I server upstream sono stati salvati correttamente",
|
||||||
"dns_test_ok_toast": "I server DNS specificati funzionano correttamente",
|
"dns_test_ok_toast": "I server DNS specificati funzionano correttamente",
|
||||||
"dns_test_not_ok_toast": "Server \"{{key}}\": non può essere utilizzato, assicurati di averlo digitato correttamente",
|
"dns_test_not_ok_toast": "Server \"{{key}}\": non può essere utilizzato, assicurati di averlo digitato correttamente",
|
||||||
"unblock": "Sblocca",
|
"unblock": "Sblocca",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Caricamento...",
|
"loading_table_status": "Caricamento...",
|
||||||
"page_table_footer_text": "Pagina",
|
"page_table_footer_text": "Pagina",
|
||||||
"rows_table_footer_text": "righe",
|
"rows_table_footer_text": "righe",
|
||||||
"updated_custom_filtering_toast": "Le regole dei filtri personalizzate sono state aggiornate",
|
"updated_custom_filtering_toast": "Le regole personalizzate sono state correttamente salvate",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regola rimossa dalle regole dei filtri personalizzate: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Regola rimossa dalle regole dei filtri personalizzate: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regola aggiunta alle regole dei filtri personalizzate: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Regola aggiunta alle regole dei filtri personalizzate: {{rule}}",
|
||||||
"query_log_response_status": "Stato: {{value}}",
|
"query_log_response_status": "Stato: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Sarà necessario configurare i dispositivi o il router per utilizzare il server DNS nei seguenti indirizzi:",
|
"install_settings_dns_desc": "Sarà necessario configurare i dispositivi o il router per utilizzare il server DNS nei seguenti indirizzi:",
|
||||||
"install_settings_all_interfaces": "Tutte le interfacce",
|
"install_settings_all_interfaces": "Tutte le interfacce",
|
||||||
"install_auth_title": "Autenticazione",
|
"install_auth_title": "Autenticazione",
|
||||||
"install_auth_desc": "Si consiglia vivamente di configurare l'autenticazione della password per l'interfaccia web di amministrazione di AdGuard Home. Anche se è accessibile solo nella rete locale, è comunque importante proteggerlo da accessi illimitati.",
|
"install_auth_desc": "L\\'autenticazione con password sulla tua interfaccia web da amministratore di AdGuard Home dev\\'esser configurata. Anche se AdGuard Home è accessibile solo dalla tua rete locale, è comunque importante proteggerlo da accessi non limitati.",
|
||||||
"install_auth_username": "Nome utente",
|
"install_auth_username": "Nome utente",
|
||||||
"install_auth_password": "Password",
|
"install_auth_password": "Password",
|
||||||
"install_auth_confirm": "Conferma password",
|
"install_auth_confirm": "Conferma password",
|
||||||
@@ -394,7 +394,7 @@
|
|||||||
"fix": "Risolvi",
|
"fix": "Risolvi",
|
||||||
"dns_providers": "Qui c\\'è una <0>lista di provider DNS</0> da cui scegliere.",
|
"dns_providers": "Qui c\\'è una <0>lista di provider DNS</0> da cui scegliere.",
|
||||||
"update_now": "Aggiorna ora",
|
"update_now": "Aggiorna ora",
|
||||||
"update_failed": "Aggiornamento automatico non riuscito. Si prega di <a>seguire questi passaggi</a>per aggiornare manualmente.",
|
"update_failed": "Aggiornamento automatico non riuscito. Ti suggeriamo di <a>seguire questi passaggi</a> per aggiornare manualmente.",
|
||||||
"processing_update": "Perfavore aspetta, AdGuard Home si sta aggiornando",
|
"processing_update": "Perfavore aspetta, AdGuard Home si sta aggiornando",
|
||||||
"clients_title": "Client",
|
"clients_title": "Client",
|
||||||
"clients_desc": "Configura i dispositivi connessi ad AdGuard Home",
|
"clients_desc": "Configura i dispositivi connessi ad AdGuard Home",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Sei sicuro di voler azzerare le statistiche?",
|
"statistics_clear_confirm": "Sei sicuro di voler azzerare le statistiche?",
|
||||||
"statistics_retention_confirm": "Sei sicuro di voler modificare la conservazione delle statistiche? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi",
|
"statistics_retention_confirm": "Sei sicuro di voler modificare la conservazione delle statistiche? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi",
|
||||||
"statistics_cleared": "Statistiche azzerate correttamente",
|
"statistics_cleared": "Statistiche azzerate correttamente",
|
||||||
|
"statistics_enable": "Attiva statistiche",
|
||||||
"interval_hours": "{{count}} ora",
|
"interval_hours": "{{count}} ora",
|
||||||
"interval_hours_plural": "{{count}} ore",
|
"interval_hours_plural": "{{count}} ore",
|
||||||
"filters_configuration": "Configurazione filtri",
|
"filters_configuration": "Configurazione filtri",
|
||||||
@@ -566,7 +567,7 @@
|
|||||||
"static_ip_desc": "AdGuard Home è un server quindi ha bisogno di un indirizzo IP statico per funzionare correttamente. In caso contrario, ad un certo punto, il router potrebbe assegnare un indirizzo IP differente a questo dispositivo.",
|
"static_ip_desc": "AdGuard Home è un server quindi ha bisogno di un indirizzo IP statico per funzionare correttamente. In caso contrario, ad un certo punto, il router potrebbe assegnare un indirizzo IP differente a questo dispositivo.",
|
||||||
"set_static_ip": "Imposta un indirizzo IP statico",
|
"set_static_ip": "Imposta un indirizzo IP statico",
|
||||||
"install_static_ok": "Buone notizie! L'indirizzo IP statico è già configurato",
|
"install_static_ok": "Buone notizie! L'indirizzo IP statico è già configurato",
|
||||||
"install_static_error": "AdGuard Home non può configurarlo automaticamente per questa interfaccia di rete. Si prega di cercare un'istruzione su come farlo manualmente.",
|
"install_static_error": "AdGuard Home non può configurarlo automaticamente per questa interfaccia di rete. Ti suggeriamo di cercare un metodo alternativo per effettuare tale operazione manualmente.",
|
||||||
"install_static_configure": "AdGuard Home ha rilevato l\\'utilizzo dell\\'indirizzo IP dinamico <0> {{ip}} </0>. Desideri impostarlo come indirizzo statico?",
|
"install_static_configure": "AdGuard Home ha rilevato l\\'utilizzo dell\\'indirizzo IP dinamico <0> {{ip}} </0>. Desideri impostarlo come indirizzo statico?",
|
||||||
"confirm_static_ip": "AdGuard Home configurerà {{ip}} come indirizzo IP statico. Desideri procedere?",
|
"confirm_static_ip": "AdGuard Home configurerà {{ip}} come indirizzo IP statico. Desideri procedere?",
|
||||||
"list_updated": "{{count}} lista aggiornata",
|
"list_updated": "{{count}} lista aggiornata",
|
||||||
@@ -610,8 +611,10 @@
|
|||||||
"setup_config_to_enable_dhcp_server": "Configurazione dell\\'installazione per l\\'attivazione del server DHCP",
|
"setup_config_to_enable_dhcp_server": "Configurazione dell\\'installazione per l\\'attivazione del server DHCP",
|
||||||
"original_response": "Responso originale",
|
"original_response": "Responso originale",
|
||||||
"click_to_view_queries": "Clicca per visualizzare le richieste",
|
"click_to_view_queries": "Clicca per visualizzare le richieste",
|
||||||
"port_53_faq_link": "La Porta 53 è spesso occupata dai servizi \"DNSStubListener\" o \"systemd-resolved\". Si prega di leggere <0>queste istruzioni</0> per risolvere il problema.",
|
"port_53_faq_link": "La Porta 53 è spesso occupata dai servizi \"DNSStubListener\" o \"systemd-resolved\". Ti suggeriamo di leggere <0>queste istruzioni</0> per risolvere il problema.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home eliminerà tutte le richieste DNS da questo client.",
|
"adg_will_drop_dns_queries": "AdGuard Home eliminerà tutte le richieste DNS da questo client.",
|
||||||
"client_not_in_allowed_clients": "Il client non è consentito perché non è nell'elenco \"Client consentiti\".",
|
"filter_allowlist": "ATTENZIONE: Quest\\'azione escluderà anche la regola \"{{disallowed_rule}}\" dall\\'elenco di clienti consentiti.",
|
||||||
"experimental": "Sperimentale"
|
"last_rule_in_allowlist": "Impossibile bloccare questo client perché escludere la regola \"{{disallowed_rule}}\" DISATIVERÁ l\\'elenco \"Clienti consentiti\".",
|
||||||
|
"experimental": "Sperimentale",
|
||||||
|
"use_saved_key": "Utilizza la chiave salvata in precedenza"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "<1>DNSCrypt</1> または <2>DNS-over-HTTPS</2> リゾルバのために <0>DNS Stamps</0> を使えます",
|
"example_upstream_sdns": "<1>DNSCrypt</1> または <2>DNS-over-HTTPS</2> リゾルバのために <0>DNS Stamps</0> を使えます",
|
||||||
"example_upstream_tcp": "通常のDNS(TCPでの問い合わせ)",
|
"example_upstream_tcp": "通常のDNS(TCPでの問い合わせ)",
|
||||||
"all_lists_up_to_date_toast": "すべてのリストは既に最新です",
|
"all_lists_up_to_date_toast": "すべてのリストは既に最新です",
|
||||||
"updated_upstream_dns_toast": "上流DNSサーバを更新しました",
|
"updated_upstream_dns_toast": "上流DNSサーバを保存しました。",
|
||||||
"dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています",
|
"dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています",
|
||||||
"dns_test_not_ok_toast": "サーバ \"{{key}}\": 使用できませんでした。正しく入力されているかどうかを確認してください",
|
"dns_test_not_ok_toast": "サーバ \"{{key}}\": 使用できませんでした。正しく入力されているかどうかを確認してください",
|
||||||
"unblock": "ブロック解除",
|
"unblock": "ブロック解除",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "読み込み中…",
|
"loading_table_status": "読み込み中…",
|
||||||
"page_table_footer_text": "ページ",
|
"page_table_footer_text": "ページ",
|
||||||
"rows_table_footer_text": "行",
|
"rows_table_footer_text": "行",
|
||||||
"updated_custom_filtering_toast": "カスタム・フィルタリングルールを更新しました",
|
"updated_custom_filtering_toast": "カスタムルールを保存しました。",
|
||||||
"rule_removed_from_custom_filtering_toast": "ルールをカスタム・フィルタリングルールから除去しました {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "ルールをカスタム・フィルタリングルールから除去しました {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "ルールをカスタム・フィルタリングルールに追加しました {{rule}}",
|
"rule_added_to_custom_filtering_toast": "ルールをカスタム・フィルタリングルールに追加しました {{rule}}",
|
||||||
"query_log_response_status": "ステータス: {{value}}",
|
"query_log_response_status": "ステータス: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "次のアドレスでDNSサーバを使用するようにあなたのデバイスまたはルータを設定する必要があります:",
|
"install_settings_dns_desc": "次のアドレスでDNSサーバを使用するようにあなたのデバイスまたはルータを設定する必要があります:",
|
||||||
"install_settings_all_interfaces": "すべてのインターフェイス",
|
"install_settings_all_interfaces": "すべてのインターフェイス",
|
||||||
"install_auth_title": "認証",
|
"install_auth_title": "認証",
|
||||||
"install_auth_desc": "AdGuard Homeの管理ウェブインターフェースにパスワード認証を設定することを強くお勧めします。ローカルネットワークでのみアクセス可能であっても、制限のないアクセスから保護することは重要です。",
|
"install_auth_desc": "AdGuard Homeの管理ウェブインターフェースにパスワード認証を設定する必要があります。AdGuard Homeがローカルネットワークでのみアクセス可能であっても、制限のないアクセスから保護することは重要です。",
|
||||||
"install_auth_username": "ユーザ名",
|
"install_auth_username": "ユーザ名",
|
||||||
"install_auth_password": "パスワード",
|
"install_auth_password": "パスワード",
|
||||||
"install_auth_confirm": "パスワード(確認用)",
|
"install_auth_confirm": "パスワード(確認用)",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "統計を消去してもよろしいですか?",
|
"statistics_clear_confirm": "統計を消去してもよろしいですか?",
|
||||||
"statistics_retention_confirm": "統計の保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます",
|
"statistics_retention_confirm": "統計の保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます",
|
||||||
"statistics_cleared": "統計の消去に成功しました",
|
"statistics_cleared": "統計の消去に成功しました",
|
||||||
|
"statistics_enable": "統計を有効にする",
|
||||||
"interval_hours": "{{count}}時間",
|
"interval_hours": "{{count}}時間",
|
||||||
"interval_hours_plural": "{{count}}時間",
|
"interval_hours_plural": "{{count}}時間",
|
||||||
"filters_configuration": "フィルタ設定",
|
"filters_configuration": "フィルタ設定",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "クエリを表示するにはクリックしてください",
|
"click_to_view_queries": "クエリを表示するにはクリックしてください",
|
||||||
"port_53_faq_link": "多くの場合、ポート53は \"DNSStubListener\" または \"systemd-resolved\" サービスによって利用されています。これを解決する方法については、<0>この手順</0>をお読みください。",
|
"port_53_faq_link": "多くの場合、ポート53は \"DNSStubListener\" または \"systemd-resolved\" サービスによって利用されています。これを解決する方法については、<0>この手順</0>をお読みください。",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Homeは、このクライアントからすべてのDNSクエリを落とします。",
|
"adg_will_drop_dns_queries": "AdGuard Homeは、このクライアントからすべてのDNSクエリを落とします。",
|
||||||
"client_not_in_allowed_clients": "「許可されたクライアント」リストにないため、このクライアントは許可されていません。",
|
"filter_allowlist": "【注意】このアクションは、許可されたクライアントのリストから「{{disallowed_rule}}」というルールも除外します。",
|
||||||
"experimental": "実験用"
|
"last_rule_in_allowlist": "ルール「{{disallowed_rule}}」を除外すると「許可されたクライアント」リストが無効になるため、このクライアントを拒否することはできません。",
|
||||||
|
"experimental": "実験用",
|
||||||
|
"use_saved_key": "以前に保存したキーを使用する"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "<1>DNSCrypt</1>나 <2>DNS-over-HTTPS</2> 리졸버를 위해 <0>DNS 스탬프</0>를 사용할 수 있습니다",
|
"example_upstream_sdns": "<1>DNSCrypt</1>나 <2>DNS-over-HTTPS</2> 리졸버를 위해 <0>DNS 스탬프</0>를 사용할 수 있습니다",
|
||||||
"example_upstream_tcp": "사용자 지정 DNS (TCP를 통한 접속)",
|
"example_upstream_tcp": "사용자 지정 DNS (TCP를 통한 접속)",
|
||||||
"all_lists_up_to_date_toast": "모든 리스트가 이미 최신입니다",
|
"all_lists_up_to_date_toast": "모든 리스트가 이미 최신입니다",
|
||||||
"updated_upstream_dns_toast": "업스트림 DNS 서버를 업데이트하였습니다",
|
"updated_upstream_dns_toast": "업스트림 서버가 성공적으로 저장되었습니다",
|
||||||
"dns_test_ok_toast": "특정 DNS 서버들은 정상적으로 동작 중입니다",
|
"dns_test_ok_toast": "특정 DNS 서버들은 정상적으로 동작 중입니다",
|
||||||
"dns_test_not_ok_toast": "서버 \"{{key}}\": 사용할 수 없습니다, 제대로 작성했는지 확인하세요.",
|
"dns_test_not_ok_toast": "서버 \"{{key}}\": 사용할 수 없습니다, 제대로 작성했는지 확인하세요.",
|
||||||
"unblock": "차단 해제",
|
"unblock": "차단 해제",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "로딩중...",
|
"loading_table_status": "로딩중...",
|
||||||
"page_table_footer_text": "페이지",
|
"page_table_footer_text": "페이지",
|
||||||
"rows_table_footer_text": "행",
|
"rows_table_footer_text": "행",
|
||||||
"updated_custom_filtering_toast": "사용자 정의 필터링 규칙 업데이트",
|
"updated_custom_filtering_toast": "사용자 정의 규칙이 성공적으로 저장되었습니다",
|
||||||
"rule_removed_from_custom_filtering_toast": "사용자 정의 필터링 규칙에서 규칙 제거 {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "사용자 정의 필터링 규칙에서 규칙 제거 {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "사용자 정의 필터링 규칙에 추가된 규칙 {{rule}}",
|
"rule_added_to_custom_filtering_toast": "사용자 정의 필터링 규칙에 추가된 규칙 {{rule}}",
|
||||||
"query_log_response_status": "상태: {{value}}",
|
"query_log_response_status": "상태: {{value}}",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "통계를 정말로 초기화하시겠습니까?",
|
"statistics_clear_confirm": "통계를 정말로 초기화하시겠습니까?",
|
||||||
"statistics_retention_confirm": "정말로 통계 저장 기간을 변경하시겠습니까? 저장 주기를 낮출 경우, 일부 데이터가 손실됩니다",
|
"statistics_retention_confirm": "정말로 통계 저장 기간을 변경하시겠습니까? 저장 주기를 낮출 경우, 일부 데이터가 손실됩니다",
|
||||||
"statistics_cleared": "통계를 성공적으로 초기화했습니다.",
|
"statistics_cleared": "통계를 성공적으로 초기화했습니다.",
|
||||||
|
"statistics_enable": "통계 활성화",
|
||||||
"interval_hours": "{{count}} 시간",
|
"interval_hours": "{{count}} 시간",
|
||||||
"interval_hours_plural": "{{count}} 시간",
|
"interval_hours_plural": "{{count}} 시간",
|
||||||
"filters_configuration": "필터 구성",
|
"filters_configuration": "필터 구성",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "쿼리를 보려면 클릭합니다",
|
"click_to_view_queries": "쿼리를 보려면 클릭합니다",
|
||||||
"port_53_faq_link": "53번 포트는 보통 \"DNSStubListener\"나 \"systemd-resolved\" 서비스가 이미 사용하고 있습니다. 이 문제에 대한 해결 방법을 <0>설명</0>에서 찾아보세요.",
|
"port_53_faq_link": "53번 포트는 보통 \"DNSStubListener\"나 \"systemd-resolved\" 서비스가 이미 사용하고 있습니다. 이 문제에 대한 해결 방법을 <0>설명</0>에서 찾아보세요.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home은 이 클라이언트에서 모든 DNS 쿼리를 삭제합니다.",
|
"adg_will_drop_dns_queries": "AdGuard Home은 이 클라이언트에서 모든 DNS 쿼리를 삭제합니다.",
|
||||||
"client_not_in_allowed_clients": "이 클라이언트는 허용된 클라이언트 목록에 없으므로 허용되지 않습니다.",
|
"filter_allowlist": "경고: 이 경우 허용된 클라이언트 목록에서 '{{disallowed_rule}}' 규칙 또한 제외됩니다.",
|
||||||
"experimental": "실험"
|
"last_rule_in_allowlist": "'{{disallowed_rule}}' 규칙을 제외하면 '허용된 클라이언트' 목록이 꺼지므로 해당 클라이언트를 제외할 수 없습니다.",
|
||||||
|
"experimental": "실험",
|
||||||
|
"use_saved_key": "이전에 저장했던 키 사용하기"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "je kunt <0>DNS Stamps</0> voor <1>DNSCrypt</1> of <2>DNS-via-HTTPS</2> oplossingen gebruiken",
|
"example_upstream_sdns": "je kunt <0>DNS Stamps</0> voor <1>DNSCrypt</1> of <2>DNS-via-HTTPS</2> oplossingen gebruiken",
|
||||||
"example_upstream_tcp": "standaard DNS (over TCP)",
|
"example_upstream_tcp": "standaard DNS (over TCP)",
|
||||||
"all_lists_up_to_date_toast": "Alle lijsten zijn reeds up-to-date",
|
"all_lists_up_to_date_toast": "Alle lijsten zijn reeds up-to-date",
|
||||||
"updated_upstream_dns_toast": "De upstream DNS-servers zijn bijgewerkt",
|
"updated_upstream_dns_toast": "Upstream-servers succesvol opgeslagen",
|
||||||
"dns_test_ok_toast": "Opgegeven DNS-servers werken correct",
|
"dns_test_ok_toast": "Opgegeven DNS-servers werken correct",
|
||||||
"dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of je het correct hebt geschreven",
|
"dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of je het correct hebt geschreven",
|
||||||
"unblock": "Deblokkeren",
|
"unblock": "Deblokkeren",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Laden...",
|
"loading_table_status": "Laden...",
|
||||||
"page_table_footer_text": "Pagina",
|
"page_table_footer_text": "Pagina",
|
||||||
"rows_table_footer_text": "rijen",
|
"rows_table_footer_text": "rijen",
|
||||||
"updated_custom_filtering_toast": "Aangepaste filter regels zijn bijgewerkt",
|
"updated_custom_filtering_toast": "Aangepaste regels succesvol opgeslagen",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regel verwijderd uit de aangepaste filterregels: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Regel verwijderd uit de aangepaste filterregels: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regel toegevoegd aan de aangepaste filterregels: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Regel toegevoegd aan de aangepaste filterregels: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Je moet jouw apparaten of router configureren om de DNS-server te gebruiken op de volgende adressen:",
|
"install_settings_dns_desc": "Je moet jouw apparaten of router configureren om de DNS-server te gebruiken op de volgende adressen:",
|
||||||
"install_settings_all_interfaces": "Alle interfaces",
|
"install_settings_all_interfaces": "Alle interfaces",
|
||||||
"install_auth_title": "Authenticatie",
|
"install_auth_title": "Authenticatie",
|
||||||
"install_auth_desc": "Het wordt ten zeerste aanbevolen om wachtwoordverificatie te configureren voor de AdGuard Home admin webinterface. Zelfs als het alleen toegankelijk is in uw lokale netwerk, is het nog steeds belangrijk om het te beschermen tegen onbeperkte toegang.",
|
"install_auth_desc": "Wachtwoordverificatie voor je AdGuard Home-beheerderswebinterface moet worden geconfigureerd. Zelfs als AdGuard Home alleen toegankelijk is in je lokale netwerk, is het nog steeds belangrijk om het te beschermen tegen onbeperkte toegang.",
|
||||||
"install_auth_username": "Gebruikersnaam",
|
"install_auth_username": "Gebruikersnaam",
|
||||||
"install_auth_password": "Wachtwoord",
|
"install_auth_password": "Wachtwoord",
|
||||||
"install_auth_confirm": "Bevestig wachtwoord",
|
"install_auth_confirm": "Bevestig wachtwoord",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Alle statistieken werkelijk wissen?",
|
"statistics_clear_confirm": "Alle statistieken werkelijk wissen?",
|
||||||
"statistics_retention_confirm": "Weet u zeker dat u de bewaartermijn van de statistieken wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren",
|
"statistics_retention_confirm": "Weet u zeker dat u de bewaartermijn van de statistieken wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren",
|
||||||
"statistics_cleared": "Statistieken succesvol gewist",
|
"statistics_cleared": "Statistieken succesvol gewist",
|
||||||
|
"statistics_enable": "Statistieken inschakelen",
|
||||||
"interval_hours": "{{count}} uur",
|
"interval_hours": "{{count}} uur",
|
||||||
"interval_hours_plural": "{{count}} uren",
|
"interval_hours_plural": "{{count}} uren",
|
||||||
"filters_configuration": "Filters instellingen",
|
"filters_configuration": "Filters instellingen",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Klik om queries te bekijken",
|
"click_to_view_queries": "Klik om queries te bekijken",
|
||||||
"port_53_faq_link": "Poort 53 wordt vaak gebruikt door services als DNSStubListener- of de systeem DNS-resolver. Lees a.u.b. <0>deze instructie</0> hoe dit is op te lossen.",
|
"port_53_faq_link": "Poort 53 wordt vaak gebruikt door services als DNSStubListener- of de systeem DNS-resolver. Lees a.u.b. <0>deze instructie</0> hoe dit is op te lossen.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home zal alle DNS verzoeken van deze toepassing/dit systeem negeren.",
|
"adg_will_drop_dns_queries": "AdGuard Home zal alle DNS verzoeken van deze toepassing/dit systeem negeren.",
|
||||||
"client_not_in_allowed_clients": "De toepassing is niet toegestaan omdat deze niet in de lijst \"Toegestane toepassingen\" voorkomt.",
|
"filter_allowlist": "WAARSCHUWING: Deze actie zal ook de regel \"{{disallowed_rule}}\" uitsluiten van de lijst met toegestane clients.",
|
||||||
"experimental": "Experimenteel"
|
"last_rule_in_allowlist": "Kan deze client niet weigeren omdat het uitsluiten van de regel \"{{disallowed_rule}}\" de lijst \"Toegestane clients\" zal UITSCHAKELEN.",
|
||||||
|
"experimental": "Experimenteel",
|
||||||
|
"use_saved_key": "De eerder opgeslagen sleutel gebruiken"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "możesz użyć adresu <0>DNS Stamps</0> dla protokołu <1>DNSCrypt</1> lub <2>DNS-over-HTTPS</2>",
|
"example_upstream_sdns": "możesz użyć adresu <0>DNS Stamps</0> dla protokołu <1>DNSCrypt</1> lub <2>DNS-over-HTTPS</2>",
|
||||||
"example_upstream_tcp": "zwykły DNS (przez TCP)",
|
"example_upstream_tcp": "zwykły DNS (przez TCP)",
|
||||||
"all_lists_up_to_date_toast": "Wszystkie listy są już aktualne",
|
"all_lists_up_to_date_toast": "Wszystkie listy są już aktualne",
|
||||||
"updated_upstream_dns_toast": "Główne serwery DNS zostały zaktualizowane",
|
"updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane",
|
||||||
"dns_test_ok_toast": "Określone serwery DNS działają poprawnie",
|
"dns_test_ok_toast": "Określone serwery DNS działają poprawnie",
|
||||||
"dns_test_not_ok_toast": "Serwer \"{{key}}\": nie można go użyć, sprawdź, czy napisałeś go poprawnie",
|
"dns_test_not_ok_toast": "Serwer \"{{key}}\": nie można go użyć, sprawdź, czy napisałeś go poprawnie",
|
||||||
"unblock": "Odblokuj",
|
"unblock": "Odblokuj",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Wczytuję...",
|
"loading_table_status": "Wczytuję...",
|
||||||
"page_table_footer_text": "Strona",
|
"page_table_footer_text": "Strona",
|
||||||
"rows_table_footer_text": "wierszy",
|
"rows_table_footer_text": "wierszy",
|
||||||
"updated_custom_filtering_toast": "Zaktualizowano niestandardowe reguły filtrowania",
|
"updated_custom_filtering_toast": "Reguły niestandardowe zapisane pomyślnie",
|
||||||
"rule_removed_from_custom_filtering_toast": "Reguła usunięta z niestandardowych reguł filtrowania: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Reguła usunięta z niestandardowych reguł filtrowania: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Reguła dodana do niestandardowych reguł filtrowania: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Reguła dodana do niestandardowych reguł filtrowania: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Konieczne będzie skonfigurowanie urządzenia lub routera do korzystania z serwera DNS pod następującymi adresami:",
|
"install_settings_dns_desc": "Konieczne będzie skonfigurowanie urządzenia lub routera do korzystania z serwera DNS pod następującymi adresami:",
|
||||||
"install_settings_all_interfaces": "Wszystkie interfejsy",
|
"install_settings_all_interfaces": "Wszystkie interfejsy",
|
||||||
"install_auth_title": "Uwierzytelnianie",
|
"install_auth_title": "Uwierzytelnianie",
|
||||||
"install_auth_desc": "Zalecamy skonfigurowanie strony AdGuard Home Admin, aby zweryfikować swoją tożsamość za pomocą hasła. Chociaż jest dostępny tylko w sieci lokalnej, nadal ważne jest, aby chronić go przed nieograniczonym dostępem.",
|
"install_auth_desc": "Należy skonfigurować uwierzytelnianie hasłem do interfejsu internetowego administratora AdGuard Home. Nawet jeśli AdGuard Home jest dostępny tylko w sieci lokalnej, nadal ważne jest, aby chronić go przed nieograniczonym dostępem.",
|
||||||
"install_auth_username": "Nazwa użytkownika",
|
"install_auth_username": "Nazwa użytkownika",
|
||||||
"install_auth_password": "Hasło",
|
"install_auth_password": "Hasło",
|
||||||
"install_auth_confirm": "Potwierdź hasło",
|
"install_auth_confirm": "Potwierdź hasło",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Czy na pewno chcesz wyczyścić statystyki?",
|
"statistics_clear_confirm": "Czy na pewno chcesz wyczyścić statystyki?",
|
||||||
"statistics_retention_confirm": "Czy chcesz zmienić sposób przechowania statystyk? Jeżeli obniżysz wartość interwału, niektóre dane będą utracone",
|
"statistics_retention_confirm": "Czy chcesz zmienić sposób przechowania statystyk? Jeżeli obniżysz wartość interwału, niektóre dane będą utracone",
|
||||||
"statistics_cleared": "Statystyki zostały pomyślnie wyczyszczone",
|
"statistics_cleared": "Statystyki zostały pomyślnie wyczyszczone",
|
||||||
|
"statistics_enable": "Włącz statystyki",
|
||||||
"interval_hours": "{{count}} godzina",
|
"interval_hours": "{{count}} godzina",
|
||||||
"interval_hours_plural": "{{count}} godziny",
|
"interval_hours_plural": "{{count}} godziny",
|
||||||
"filters_configuration": "Konfiguracja filtrów",
|
"filters_configuration": "Konfiguracja filtrów",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Kliknij, aby wyświetlić zapytania",
|
"click_to_view_queries": "Kliknij, aby wyświetlić zapytania",
|
||||||
"port_53_faq_link": "Port 53 jest często zajęty przez usługi \"DNSStubListener\" lub \"systemd-resolved\". Przeczytaj <0>tę instrukcję</0> jak to rozwiązać.",
|
"port_53_faq_link": "Port 53 jest często zajęty przez usługi \"DNSStubListener\" lub \"systemd-resolved\". Przeczytaj <0>tę instrukcję</0> jak to rozwiązać.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home odrzuci zapytanie DNS od tego klienta.",
|
"adg_will_drop_dns_queries": "AdGuard Home odrzuci zapytanie DNS od tego klienta.",
|
||||||
"client_not_in_allowed_clients": "Klient nie jest dozwolony, ponieważ nie ma go na liście „Dozwoleni klienci”.",
|
"filter_allowlist": "OSTRZEŻENIE: To działanie spowoduje również wykluczenie reguły \"{{disallowed_rule}}\" z listy dozwolonych klientów.",
|
||||||
"experimental": "Funkcja eksperymentalna"
|
"last_rule_in_allowlist": "Nie można odrzucić tego klienta, ponieważ wykluczenie reguły \"{{disallowed_rule}}\" spowoduje WYŁĄCZENIE listy „Dozwolonych klientów”.",
|
||||||
|
"experimental": "Funkcja eksperymentalna",
|
||||||
|
"use_saved_key": "Użyj wcześniej zapisanego klucza"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "você pode usar <0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>",
|
"example_upstream_sdns": "você pode usar <0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>",
|
||||||
"example_upstream_tcp": "DNS regular (através do TCP)",
|
"example_upstream_tcp": "DNS regular (através do TCP)",
|
||||||
"all_lists_up_to_date_toast": "Todas as listas já estão atualizadas",
|
"all_lists_up_to_date_toast": "Todas as listas já estão atualizadas",
|
||||||
"updated_upstream_dns_toast": "Atualizado os servidores DNS primário",
|
"updated_upstream_dns_toast": "Servidores DNS primário salvos com sucesso",
|
||||||
"dns_test_ok_toast": "Os servidores DNS especificados estão funcionando corretamente",
|
"dns_test_ok_toast": "Os servidores DNS especificados estão funcionando corretamente",
|
||||||
"dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se você escreveu corretamente",
|
"dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se você escreveu corretamente",
|
||||||
"unblock": "Desbloquear",
|
"unblock": "Desbloquear",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Carregando",
|
"loading_table_status": "Carregando",
|
||||||
"page_table_footer_text": "Página",
|
"page_table_footer_text": "Página",
|
||||||
"rows_table_footer_text": "linhas",
|
"rows_table_footer_text": "linhas",
|
||||||
"updated_custom_filtering_toast": "Regras de filtragem personalizadas atualizadas",
|
"updated_custom_filtering_toast": "Regras personalizadas salvas com sucesso",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Você precisa configurar seu dispositivo ou roteador para usar o servidor DNS nos seguintes endereços:",
|
"install_settings_dns_desc": "Você precisa configurar seu dispositivo ou roteador para usar o servidor DNS nos seguintes endereços:",
|
||||||
"install_settings_all_interfaces": "Todas interfaces",
|
"install_settings_all_interfaces": "Todas interfaces",
|
||||||
"install_auth_title": "Autenticação",
|
"install_auth_title": "Autenticação",
|
||||||
"install_auth_desc": "É altamente recomendável configurar a autenticação por senha na interface web de administração do AdGuard Home. Mesmo que ela seja acessível somente em sua rede local, ainda assim é importante protegê-la contra acesso irrestrito.",
|
"install_auth_desc": "A autenticação de senha para a interface da web de administrador do AdGuard Home deve ser configurada. Mesmo que o AdGuard Home esteja acessível apenas em sua rede local, ainda é importante protegê-la de acesso irrestrito.",
|
||||||
"install_auth_username": "Nome de usuário",
|
"install_auth_username": "Nome de usuário",
|
||||||
"install_auth_password": "Senha",
|
"install_auth_password": "Senha",
|
||||||
"install_auth_confirm": "Confirmar senha",
|
"install_auth_confirm": "Confirmar senha",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Você tem certeza de que deseja limpar as estatísticas?",
|
"statistics_clear_confirm": "Você tem certeza de que deseja limpar as estatísticas?",
|
||||||
"statistics_retention_confirm": "Você tem certeza que quer alterar o arquivamento das estatísticas? Se diminuir o valor do intervalo, alguns dados serão perdidos",
|
"statistics_retention_confirm": "Você tem certeza que quer alterar o arquivamento das estatísticas? Se diminuir o valor do intervalo, alguns dados serão perdidos",
|
||||||
"statistics_cleared": "As estatísticas foram limpas com sucesso",
|
"statistics_cleared": "As estatísticas foram limpas com sucesso",
|
||||||
|
"statistics_enable": "Ativar estatísticas",
|
||||||
"interval_hours": "{{count}} hora",
|
"interval_hours": "{{count}} hora",
|
||||||
"interval_hours_plural": "{{count}} horas",
|
"interval_hours_plural": "{{count}} horas",
|
||||||
"filters_configuration": "Configuração de filtros",
|
"filters_configuration": "Configuração de filtros",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Clique para ver as consultas",
|
"click_to_view_queries": "Clique para ver as consultas",
|
||||||
"port_53_faq_link": "A porta 53 é frequentemente ocupada por serviços \"DNSStubListener\" ou \"systemd-resolved\". Por favor leia <0>essa instrução</0> para resolver isso.",
|
"port_53_faq_link": "A porta 53 é frequentemente ocupada por serviços \"DNSStubListener\" ou \"systemd-resolved\". Por favor leia <0>essa instrução</0> para resolver isso.",
|
||||||
"adg_will_drop_dns_queries": "O AdGuard Home descartará todas as consultas DNS deste cliente.",
|
"adg_will_drop_dns_queries": "O AdGuard Home descartará todas as consultas DNS deste cliente.",
|
||||||
"client_not_in_allowed_clients": "O cliente não é permitido porque não está na lista \"Clientes permitidos\".",
|
"filter_allowlist": "AVISO: Esta ação também excluirá a regra \"{{disallowed_rule}}\" da lista de clientes permitidos.",
|
||||||
"experimental": "Experimental"
|
"last_rule_in_allowlist": "Não é possível desautorizar este cliente porque excluir a regra \"{{disallowed_rule}}\" DESATIVARÁ a lista de \"Clientes permitidos\".",
|
||||||
|
"experimental": "Experimental",
|
||||||
|
"use_saved_key": "Use a chave salva anteriormente"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,10 +99,10 @@
|
|||||||
"homepage": "Página inicial",
|
"homepage": "Página inicial",
|
||||||
"report_an_issue": "Comunicar um problema",
|
"report_an_issue": "Comunicar um problema",
|
||||||
"privacy_policy": "Política de privacidade",
|
"privacy_policy": "Política de privacidade",
|
||||||
"enable_protection": "Ativar protecção",
|
"enable_protection": "Ativar proteção",
|
||||||
"enabled_protection": "Ativar protecção",
|
"enabled_protection": "Ativar proteção",
|
||||||
"disable_protection": "Desativar protecção",
|
"disable_protection": "Desativar proteção",
|
||||||
"disabled_protection": "Desativar protecção",
|
"disabled_protection": "Desativar proteção",
|
||||||
"refresh_statics": "Repor estatísticas",
|
"refresh_statics": "Repor estatísticas",
|
||||||
"dns_query": "Consultas de DNS",
|
"dns_query": "Consultas de DNS",
|
||||||
"blocked_by": "<0>Bloqueado por filtros</0>",
|
"blocked_by": "<0>Bloqueado por filtros</0>",
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "pode usar <0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>",
|
"example_upstream_sdns": "pode usar <0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>",
|
||||||
"example_upstream_tcp": "dNS regular (através do TCP)",
|
"example_upstream_tcp": "dNS regular (através do TCP)",
|
||||||
"all_lists_up_to_date_toast": "Todas as listas já estão atualizadas",
|
"all_lists_up_to_date_toast": "Todas as listas já estão atualizadas",
|
||||||
"updated_upstream_dns_toast": "A atualizar os servidores DNS primário",
|
"updated_upstream_dns_toast": "Servidores DNS primário guardados com sucesso",
|
||||||
"dns_test_ok_toast": "Os servidores DNS especificados estão a funcionar corretamente",
|
"dns_test_ok_toast": "Os servidores DNS especificados estão a funcionar corretamente",
|
||||||
"dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se o escreveu corretamente",
|
"dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se o escreveu corretamente",
|
||||||
"unblock": "Desbloquear",
|
"unblock": "Desbloquear",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "A carregar...",
|
"loading_table_status": "A carregar...",
|
||||||
"page_table_footer_text": "Página",
|
"page_table_footer_text": "Página",
|
||||||
"rows_table_footer_text": "linhas",
|
"rows_table_footer_text": "linhas",
|
||||||
"updated_custom_filtering_toast": "Regras de filtragem personalizadas atualizadas",
|
"updated_custom_filtering_toast": "Regras personalizadas guardadas com sucesso",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Precisa de configurar o seu dispositivo ou router para usar o servidor DNS nos seguintes endereços:",
|
"install_settings_dns_desc": "Precisa de configurar o seu dispositivo ou router para usar o servidor DNS nos seguintes endereços:",
|
||||||
"install_settings_all_interfaces": "Todas as interfaces",
|
"install_settings_all_interfaces": "Todas as interfaces",
|
||||||
"install_auth_title": "Autenticação",
|
"install_auth_title": "Autenticação",
|
||||||
"install_auth_desc": "É altamente recomendável configurar a autenticação por palavra-passe para a sua interface web de administrador do AdGuard Home. Mesmo que seja acessível apenas na sua rede local, ainda assim é importante protegê-lo contra o acesso irrestrito.",
|
"install_auth_desc": "A autenticação de palavra-passe para a interface da web de administrador do AdGuard Home deve ser configurada. Mesmo que o AdGuard Home esteja acessível apenas em sua rede local, ainda é importante protegê-la de acesso irrestrito.",
|
||||||
"install_auth_username": "Nome do utilizador",
|
"install_auth_username": "Nome do utilizador",
|
||||||
"install_auth_password": "Palavra-passe",
|
"install_auth_password": "Palavra-passe",
|
||||||
"install_auth_confirm": "Confirmar palavra-passe",
|
"install_auth_confirm": "Confirmar palavra-passe",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Tem a certeza de que deseja limpar as estatísticas?",
|
"statistics_clear_confirm": "Tem a certeza de que deseja limpar as estatísticas?",
|
||||||
"statistics_retention_confirm": "Tem a certeza que quer alterar a retenção de estatísticas? Se diminuir o valor do intervalo, alguns dados serão perdidos",
|
"statistics_retention_confirm": "Tem a certeza que quer alterar a retenção de estatísticas? Se diminuir o valor do intervalo, alguns dados serão perdidos",
|
||||||
"statistics_cleared": "As estatísticas foram apagadas com sucesso",
|
"statistics_cleared": "As estatísticas foram apagadas com sucesso",
|
||||||
|
"statistics_enable": "Ativar estatísticas",
|
||||||
"interval_hours": "{{count}} hora",
|
"interval_hours": "{{count}} hora",
|
||||||
"interval_hours_plural": "{{count}} horas",
|
"interval_hours_plural": "{{count}} horas",
|
||||||
"filters_configuration": "Definição dos filtros",
|
"filters_configuration": "Definição dos filtros",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Clique para ver as consultas",
|
"click_to_view_queries": "Clique para ver as consultas",
|
||||||
"port_53_faq_link": "A porta 53 é frequentemente ocupada por serviços \"DNSStubListener\" ou \"systemd-resolved\". Por favor leia <0>essa instrução</0> para resolver isso.",
|
"port_53_faq_link": "A porta 53 é frequentemente ocupada por serviços \"DNSStubListener\" ou \"systemd-resolved\". Por favor leia <0>essa instrução</0> para resolver isso.",
|
||||||
"adg_will_drop_dns_queries": "O AdGuard Home descartará todas as consultas DNS deste cliente.",
|
"adg_will_drop_dns_queries": "O AdGuard Home descartará todas as consultas DNS deste cliente.",
|
||||||
"client_not_in_allowed_clients": "O cliente não é permitido porque não está na lista \"Clientes permitidos\".",
|
"filter_allowlist": "AVISO: Esta ação também excluirá a regra \"{{disallowed_rule}}\" da lista de clientes permitidos.",
|
||||||
"experimental": "Experimental"
|
"last_rule_in_allowlist": "Não é possível desautorizar este cliente porque excluir a regra \"{{disallowed_rule}}\" DESATIVARÁ a lista de \"Clientes permitidos\".",
|
||||||
|
"experimental": "Experimental",
|
||||||
|
"use_saved_key": "Use a chave guardada anteriormente"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "вы можете использовать <0>DNS Stamps</0> для <1>DNSCrypt</1> или <2>DNS-over-HTTPS</2> резолверов",
|
"example_upstream_sdns": "вы можете использовать <0>DNS Stamps</0> для <1>DNSCrypt</1> или <2>DNS-over-HTTPS</2> резолверов",
|
||||||
"example_upstream_tcp": "обычный DNS (поверх TCP)",
|
"example_upstream_tcp": "обычный DNS (поверх TCP)",
|
||||||
"all_lists_up_to_date_toast": "Все списки уже обновлены",
|
"all_lists_up_to_date_toast": "Все списки уже обновлены",
|
||||||
"updated_upstream_dns_toast": "Upstream DNS-серверы обновлены",
|
"updated_upstream_dns_toast": "DNS-серверы успешно обновлены",
|
||||||
"dns_test_ok_toast": "Указанные серверы DNS работают корректно",
|
"dns_test_ok_toast": "Указанные серверы DNS работают корректно",
|
||||||
"dns_test_not_ok_toast": "Сервер «{{key}}»: невозможно использовать, проверьте правильность написания",
|
"dns_test_not_ok_toast": "Сервер «{{key}}»: невозможно использовать, проверьте правильность написания",
|
||||||
"unblock": "Разблокировать",
|
"unblock": "Разблокировать",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Загрузка…",
|
"loading_table_status": "Загрузка…",
|
||||||
"page_table_footer_text": "Страница",
|
"page_table_footer_text": "Страница",
|
||||||
"rows_table_footer_text": "строк",
|
"rows_table_footer_text": "строк",
|
||||||
"updated_custom_filtering_toast": "Внесены изменения в пользовательские правила",
|
"updated_custom_filtering_toast": "Пользовательские правила успешно сохранены",
|
||||||
"rule_removed_from_custom_filtering_toast": "Пользовательское правило удалено: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Пользовательское правило удалено: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Пользовательское правило добавлено: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Пользовательское правило добавлено: {{rule}}",
|
||||||
"query_log_response_status": "Статус: {{value}}",
|
"query_log_response_status": "Статус: {{value}}",
|
||||||
@@ -277,7 +277,7 @@
|
|||||||
"form_enter_rate_limit": "Введите rate limit",
|
"form_enter_rate_limit": "Введите rate limit",
|
||||||
"rate_limit": "Rate limit",
|
"rate_limit": "Rate limit",
|
||||||
"edns_enable": "Включить отправку EDNS Client Subnet",
|
"edns_enable": "Включить отправку EDNS Client Subnet",
|
||||||
"edns_cs_desc": "Отправлять подсети клиентов на DNS-сервера.",
|
"edns_cs_desc": "Отправлять подсети клиентов на DNS-серверы.",
|
||||||
"rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно).",
|
"rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно).",
|
||||||
"blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса",
|
"blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса",
|
||||||
"blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса",
|
"blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Вам будет нужно настроить свои устройства или роутер на использование DNS-сервера на одном из следующих адресов:",
|
"install_settings_dns_desc": "Вам будет нужно настроить свои устройства или роутер на использование DNS-сервера на одном из следующих адресов:",
|
||||||
"install_settings_all_interfaces": "Все интерфейсы",
|
"install_settings_all_interfaces": "Все интерфейсы",
|
||||||
"install_auth_title": "Авторизация",
|
"install_auth_title": "Авторизация",
|
||||||
"install_auth_desc": "Настоятельно рекомендуется настроить аутентификацию паролем для веб-интерфейса AdGuard Home. Даже если он доступен только в вашей локальной сети, важно защитить его от неограниченного доступа.",
|
"install_auth_desc": "Должна быть настроена аутентификация паролем для веб-интерфейса AdGuard Home. Даже если он доступен только в вашей локальной сети, важно защитить его от неограниченного доступа.",
|
||||||
"install_auth_username": "Имя пользователя",
|
"install_auth_username": "Имя пользователя",
|
||||||
"install_auth_password": "Пароль",
|
"install_auth_password": "Пароль",
|
||||||
"install_auth_confirm": "Подтвердить пароль",
|
"install_auth_confirm": "Подтвердить пароль",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Вы уверены, что хотите очистить статистику?",
|
"statistics_clear_confirm": "Вы уверены, что хотите очистить статистику?",
|
||||||
"statistics_retention_confirm": "Вы уверены, что хотите изменить срок хранения статистики? При сокращении интервала данные могут быть утеряны",
|
"statistics_retention_confirm": "Вы уверены, что хотите изменить срок хранения статистики? При сокращении интервала данные могут быть утеряны",
|
||||||
"statistics_cleared": "Статистика успешно очищена",
|
"statistics_cleared": "Статистика успешно очищена",
|
||||||
|
"statistics_enable": "Включить статистику",
|
||||||
"interval_hours": "{{count}} час",
|
"interval_hours": "{{count}} час",
|
||||||
"interval_hours_plural": "{{count}} часов",
|
"interval_hours_plural": "{{count}} часов",
|
||||||
"filters_configuration": "Настройка фильтров",
|
"filters_configuration": "Настройка фильтров",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Нажмите, чтобы просмотреть запросы",
|
"click_to_view_queries": "Нажмите, чтобы просмотреть запросы",
|
||||||
"port_53_faq_link": "Порт 53 часто занят службами «DNSStubListener» или «systemd-resolved». Ознакомьтесь с <0>инструкцией</0> о том, как это разрешить.",
|
"port_53_faq_link": "Порт 53 часто занят службами «DNSStubListener» или «systemd-resolved». Ознакомьтесь с <0>инструкцией</0> о том, как это разрешить.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home AdGuard Home сбросит все DNS-запросы от этого клиента.",
|
"adg_will_drop_dns_queries": "AdGuard Home AdGuard Home сбросит все DNS-запросы от этого клиента.",
|
||||||
"client_not_in_allowed_clients": "Клиент не разрешён, так как его нет в списке «Разрешённых клиентов».",
|
"filter_allowlist": "ВНИМАНИЕ: Это действие также исключит правило «{{disallowed_rule}}» из списка разрешённых клиентов.",
|
||||||
"experimental": "Экспериментальный"
|
"last_rule_in_allowlist": "Нельзя заблокировать этого клиента, так как исключение правила «{{disallowed_rule}}» ОТКЛЮЧИТ режим белого списка.",
|
||||||
|
"experimental": "Экспериментальный",
|
||||||
|
"use_saved_key": "Использовать сохранённый ранее ключ"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
"dhcp_enable": "ග.ධා.වි.කෙ. සේවාදායකය සබල කරන්න",
|
"dhcp_enable": "ග.ධා.වි.කෙ. සේවාදායකය සබල කරන්න",
|
||||||
"dhcp_disable": "ග.ධා.වි.කෙ. සේවාදායකය අබල කරන්න",
|
"dhcp_disable": "ග.ධා.වි.කෙ. සේවාදායකය අබල කරන්න",
|
||||||
"dhcp_config_saved": "ග.ධා.වි.කෙ. වින්යාසය සාර්ථකව සුරකින ලදි",
|
"dhcp_config_saved": "ග.ධා.වි.කෙ. වින්යාසය සාර්ථකව සුරකින ලදි",
|
||||||
"dhcp_ipv4_settings": "ග.ධා.වි.කෙ. අයිපීවී 4 සැකසුම්",
|
"dhcp_ipv4_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 4 සැකසුම්",
|
||||||
"dhcp_ipv6_settings": "ග.ධා.වි.කෙ. අයිපීවී 6 සැකසුම්",
|
"dhcp_ipv6_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 6 සැකසුම්",
|
||||||
"form_error_required": "අවශ්ය ක්ෂේත්රයකි",
|
"form_error_required": "අවශ්ය ක්ෂේත්රයකි",
|
||||||
"form_error_ip4_format": "වලංගු නොවන IPv4 ආකෘතියකි",
|
"form_error_ip4_format": "වලංගු නොවන IPv4 ආකෘතියකි",
|
||||||
"form_error_ip6_format": "වලංගු නොවන IPv6 ආකෘතියකි",
|
"form_error_ip6_format": "වලංගු නොවන IPv6 ආකෘතියකි",
|
||||||
@@ -221,8 +221,8 @@
|
|||||||
"refused": "REFUSED",
|
"refused": "REFUSED",
|
||||||
"null_ip": "අභිශූන්යය අ.ජා. කෙ.",
|
"null_ip": "අභිශූන්යය අ.ජා. කෙ.",
|
||||||
"custom_ip": "අභිරුචි අ.ජා. කෙ.",
|
"custom_ip": "අභිරුචි අ.ජා. කෙ.",
|
||||||
"blocking_ipv4": "අයි.පී.වී.4 අවහිර කිරීම\n",
|
"blocking_ipv4": "අ.ජා.කෙ.4 අවහිර කිරීම",
|
||||||
"blocking_ipv6": "අයි.පී.වී.6 අවහිර කිරීම",
|
"blocking_ipv6": "අ.ජා.කෙ.6 අවහිර කිරීම",
|
||||||
"client_id": "අනුග්රාහකයේ හැඳුනුම",
|
"client_id": "අනුග්රාහකයේ හැඳුනුම",
|
||||||
"client_id_placeholder": "අනුග්රාහකයේ හැඳුනුම යොදන්න",
|
"client_id_placeholder": "අනුග්රාහකයේ හැඳුනුම යොදන්න",
|
||||||
"download_mobileconfig": "වින්යාසගත ගොනුව බාගන්න",
|
"download_mobileconfig": "වින්යාසගත ගොනුව බාගන්න",
|
||||||
@@ -230,8 +230,8 @@
|
|||||||
"form_enter_rate_limit": "අනුපාත සීමාව ඇතුල් කරන්න",
|
"form_enter_rate_limit": "අනුපාත සීමාව ඇතුල් කරන්න",
|
||||||
"rate_limit": "අනුපාත සීමාව",
|
"rate_limit": "අනුපාත සීමාව",
|
||||||
"rate_limit_desc": "එක් අනුග්රාහකයකට ඉඩ දී ඇති තත්පරයට ඉල්ලීම් ගණන. එය 0 ලෙස සැකසීම යනුවෙන් අදහස් කරන්නේ සීමාවක් නැති බවයි.",
|
"rate_limit_desc": "එක් අනුග්රාහකයකට ඉඩ දී ඇති තත්පරයට ඉල්ලීම් ගණන. එය 0 ලෙස සැකසීම යනුවෙන් අදහස් කරන්නේ සීමාවක් නැති බවයි.",
|
||||||
"blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා. කෙ. (IP) ලිපිනය",
|
"blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
|
||||||
"blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා. කෙ. (IP) ලිපිනය",
|
"blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
|
||||||
"blocking_mode_default": "පොදු: දැන්වීම් අවහිර කරන ආකාරයේ නීතියක් මගින් අවහිර කළ විට REFUSED සමඟ ප්රතිචාර දක්වයි; /etc/host-style ආකාරයේ නීතියක් මගින් අවහිර කළ විට නීතියේ දක්වා ඇති අ.ජා. කෙ. ලිපිනය සමඟ ප්රතිචාර දක්වයි",
|
"blocking_mode_default": "පොදු: දැන්වීම් අවහිර කරන ආකාරයේ නීතියක් මගින් අවහිර කළ විට REFUSED සමඟ ප්රතිචාර දක්වයි; /etc/host-style ආකාරයේ නීතියක් මගින් අවහිර කළ විට නීතියේ දක්වා ඇති අ.ජා. කෙ. ලිපිනය සමඟ ප්රතිචාර දක්වයි",
|
||||||
"blocking_mode_refused": "REFUSED: REFUSED කේතය සමඟ ප්රතිචාර දක්වයි",
|
"blocking_mode_refused": "REFUSED: REFUSED කේතය සමඟ ප්රතිචාර දක්වයි",
|
||||||
"blocking_mode_nxdomain": "නොපවතින වසම (NXDOMAIN): NXDOMAIN කේතය සමඟ ප්රතිචාර දක්වයි",
|
"blocking_mode_nxdomain": "නොපවතින වසම (NXDOMAIN): NXDOMAIN කේතය සමඟ ප්රතිචාර දක්වයි",
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
"form_enter_subnet_ip": "\"{{cidr}}\" අනුජාලයෙහි අ.ජා. කෙ. ලිපිනයක් යොදන්න.",
|
"form_enter_subnet_ip": "\"{{cidr}}\" අනුජාලයෙහි අ.ජා. කෙ. ලිපිනයක් යොදන්න.",
|
||||||
"form_enter_mac": "මා.ප්ර.පා. (MAC) ඇතුල් කරන්න",
|
"form_enter_mac": "මා.ප්ර.පා. (MAC) ඇතුල් කරන්න",
|
||||||
"form_enter_id": "හඳුන්වනය ඇතුල් කරන්න",
|
"form_enter_id": "හඳුන්වනය ඇතුල් කරන්න",
|
||||||
"form_add_id": "හඳුන්වනයක් එක් කරන්න",
|
"form_add_id": "හඳුන්වනයක් එකතු කරන්න",
|
||||||
"form_client_name": "අනුග්රාහකයේ නම ඇතුල් කරන්න",
|
"form_client_name": "අනුග්රාහකයේ නම ඇතුල් කරන්න",
|
||||||
"name": "නම",
|
"name": "නම",
|
||||||
"client_global_settings": "ගෝලීය සැකසුම් භාවිතා කරන්න",
|
"client_global_settings": "ගෝලීය සැකසුම් භාවිතා කරන්න",
|
||||||
@@ -465,7 +465,7 @@
|
|||||||
"rewrite_ip_address": "අ.ජා. කෙ. ලිපිනය: මෙම අ.ජා. කෙටුම්පත A හෝ AAAA ප්රතිචාරයකට භාවිතා කරන්න",
|
"rewrite_ip_address": "අ.ජා. කෙ. ලිපිනය: මෙම අ.ජා. කෙටුම්පත A හෝ AAAA ප්රතිචාරයකට භාවිතා කරන්න",
|
||||||
"rewrite_domain_name": "වසම් නාමය: අන්. නා. (CNAME) වාර්තාවක් එක් කරන්න",
|
"rewrite_domain_name": "වසම් නාමය: අන්. නා. (CNAME) වාර්තාවක් එක් කරන්න",
|
||||||
"disable_ipv6": "IPv6 ලිපින විසඳීම අබල කරන්න",
|
"disable_ipv6": "IPv6 ලිපින විසඳීම අබල කරන්න",
|
||||||
"disable_ipv6_desc": "අ.ජා. කෙ. අනු. 6 ලිපින (AAAA වර්ගය) සඳහා වන සියලුම ව.නා.ප. විමසුම් අතහැර දමනු ලැබේ.",
|
"disable_ipv6_desc": "අ.ජා.කෙ. අනු. 6 ලිපින (AAAA වර්ගය) සඳහා වන සියලුම ව.නා.ප. විමසුම් අතහැර දමනු ලැබේ.",
|
||||||
"fastest_addr": "වේගවත්ම අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය",
|
"fastest_addr": "වේගවත්ම අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය",
|
||||||
"fastest_addr_desc": "සියලුම ව.නා.ප. සේවාදායකයන්ගෙන් විමසා සියලු ප්රතිචාර අතරින් වේගවත්ම අ.ජා. කෙ. ලිපිනය ලබා දෙයි. සියලුම ව.නා.ප. සේවාදායකයන්ගේ ප්රතිචාර සඳහා ඇඩ්ගාර්ඩ් හෝම් රැඳී සිටිය යුතු බැවින් මෙය ව.නා.ප. විමසුම් මන්දගාමී කරන නමුත් සමස්ත සම්බන්ධතාවය වැඩි දියුණු කරයි.",
|
"fastest_addr_desc": "සියලුම ව.නා.ප. සේවාදායකයන්ගෙන් විමසා සියලු ප්රතිචාර අතරින් වේගවත්ම අ.ජා. කෙ. ලිපිනය ලබා දෙයි. සියලුම ව.නා.ප. සේවාදායකයන්ගේ ප්රතිචාර සඳහා ඇඩ්ගාර්ඩ් හෝම් රැඳී සිටිය යුතු බැවින් මෙය ව.නා.ප. විමසුම් මන්දගාමී කරන නමුත් සමස්ත සම්බන්ධතාවය වැඩි දියුණු කරයි.",
|
||||||
"autofix_warning_text": "ඔබ \"නිරාකරණය කරන්න\" බොත්තම එබුවහොත්, ඔබගේ පද්ධතිය ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය භාවිතා කිරීමට වින්යාසගත කරනු ඇත.",
|
"autofix_warning_text": "ඔබ \"නිරාකරණය කරන්න\" බොත්තම එබුවහොත්, ඔබගේ පද්ධතිය ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය භාවිතා කිරීමට වින්යාසගත කරනු ඇත.",
|
||||||
@@ -537,5 +537,6 @@
|
|||||||
"port_53_faq_link": "53 වන කෙවෙනිය බොහෝ විට \"DNSStubListener\" හෝ \"systemd-resolved\" සේවාවන් භාවිතයට ගනු ලැබේ. කරුණාකර මෙය විසඳන්නේ කෙසේද යන්න පිළිබඳ <0>මෙම උපදෙස්</0> කියවන්න.",
|
"port_53_faq_link": "53 වන කෙවෙනිය බොහෝ විට \"DNSStubListener\" හෝ \"systemd-resolved\" සේවාවන් භාවිතයට ගනු ලැබේ. කරුණාකර මෙය විසඳන්නේ කෙසේද යන්න පිළිබඳ <0>මෙම උපදෙස්</0> කියවන්න.",
|
||||||
"adg_will_drop_dns_queries": "ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම අනුග්රාහකයේ සියලුම ව.නා.ප. විමසුම් අතහැර දමනු ඇත.",
|
"adg_will_drop_dns_queries": "ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම අනුග්රාහකයේ සියලුම ව.නා.ප. විමසුම් අතහැර දමනු ඇත.",
|
||||||
"client_not_in_allowed_clients": "\"ඉඩ දුන් අනුග්රාහකයින්\" ලැයිස්තුවේ නැති නිසා අනුග්රාහකයට ඉඩ දී නැත.",
|
"client_not_in_allowed_clients": "\"ඉඩ දුන් අනුග්රාහකයින්\" ලැයිස්තුවේ නැති නිසා අනුග්රාහකයට ඉඩ දී නැත.",
|
||||||
"experimental": "පරීක්ෂණාත්මක"
|
"experimental": "පරීක්ෂණාත්මක",
|
||||||
|
"use_saved_key": "පෙර සුරැකි යතුර භාවිතා කරන්න"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "môžete použiť <0>DNS pečiatky</0> pre <1>DNSCrypt</1> alebo <2>DNS-over-HTTPS</2>",
|
"example_upstream_sdns": "môžete použiť <0>DNS pečiatky</0> pre <1>DNSCrypt</1> alebo <2>DNS-over-HTTPS</2>",
|
||||||
"example_upstream_tcp": "radová DNS (cez TCP)",
|
"example_upstream_tcp": "radová DNS (cez TCP)",
|
||||||
"all_lists_up_to_date_toast": "Všetky zoznamy sú už aktuálne",
|
"all_lists_up_to_date_toast": "Všetky zoznamy sú už aktuálne",
|
||||||
"updated_upstream_dns_toast": "Aktualizované upstream DNS servery",
|
"updated_upstream_dns_toast": "Upstream servery boli úspešne uložené",
|
||||||
"dns_test_ok_toast": "Špecifikované DNS servery pracujú korektne",
|
"dns_test_ok_toast": "Špecifikované DNS servery pracujú korektne",
|
||||||
"dns_test_not_ok_toast": "Server \"{{key}}\": nemohol byť použitý, skontrolujte, či ste ho správne napísali",
|
"dns_test_not_ok_toast": "Server \"{{key}}\": nemohol byť použitý, skontrolujte, či ste ho správne napísali",
|
||||||
"unblock": "Odblokovať",
|
"unblock": "Odblokovať",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Načítavam...",
|
"loading_table_status": "Načítavam...",
|
||||||
"page_table_footer_text": "Stránka",
|
"page_table_footer_text": "Stránka",
|
||||||
"rows_table_footer_text": "riadky",
|
"rows_table_footer_text": "riadky",
|
||||||
"updated_custom_filtering_toast": "Aktualizované vlastné filtračné pravidlá",
|
"updated_custom_filtering_toast": "Vlastné pravidlá boli úspešne uložené",
|
||||||
"rule_removed_from_custom_filtering_toast": "Pravidlo odstránené z vlastných filtračných pravidiel: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Pravidlo odstránené z vlastných filtračných pravidiel: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Pravidlo pridané do vlastných filtračných pravidiel: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Pravidlo pridané do vlastných filtračných pravidiel: {{rule}}",
|
||||||
"query_log_response_status": "Stav: {{value}}",
|
"query_log_response_status": "Stav: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Budete musieť konfigurovať Vaše zariadenia alebo smerovač, aby používali DNS server na nasledujúcich adresách:",
|
"install_settings_dns_desc": "Budete musieť konfigurovať Vaše zariadenia alebo smerovač, aby používali DNS server na nasledujúcich adresách:",
|
||||||
"install_settings_all_interfaces": "Všetky rozhrania",
|
"install_settings_all_interfaces": "Všetky rozhrania",
|
||||||
"install_auth_title": "Overenie identity",
|
"install_auth_title": "Overenie identity",
|
||||||
"install_auth_desc": "Odporúčame Vám nakonfigurovať na administrátorskom webovom rozhraní AdGuard Home overenie Vašej identity heslom. Aj keď je prístupné iba vo Vašej lokálnej sieti, je stále dôležité chrániť ho pred neobmedzeným prístupom.",
|
"install_auth_desc": "Je potrebné nakonfigurovať autentifikáciu heslom do administrátorského webového rozhrania AdGuard Home. Aj keď je AdGuard Home prístupný iba vo Vašej lokálnej sieti, je stále dôležité chrániť ho pred neobmedzeným prístupom.",
|
||||||
"install_auth_username": "Meno používateľa",
|
"install_auth_username": "Meno používateľa",
|
||||||
"install_auth_password": "Heslo",
|
"install_auth_password": "Heslo",
|
||||||
"install_auth_confirm": "Potvrdenie hesla",
|
"install_auth_confirm": "Potvrdenie hesla",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Naozaj chcete vynulovať štatistiku?",
|
"statistics_clear_confirm": "Naozaj chcete vynulovať štatistiku?",
|
||||||
"statistics_retention_confirm": "Naozaj chcete zmeniť uchovávanie štatistík? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
|
"statistics_retention_confirm": "Naozaj chcete zmeniť uchovávanie štatistík? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
|
||||||
"statistics_cleared": "Štatistika bola úspešne vynulovaná",
|
"statistics_cleared": "Štatistika bola úspešne vynulovaná",
|
||||||
|
"statistics_enable": "Zapnúť štatistiku",
|
||||||
"interval_hours": "{{count}} hodina",
|
"interval_hours": "{{count}} hodina",
|
||||||
"interval_hours_plural": "{{count}} hodín",
|
"interval_hours_plural": "{{count}} hodín",
|
||||||
"filters_configuration": "Konfigurácia filtrov",
|
"filters_configuration": "Konfigurácia filtrov",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Kliknite pre zobrazenie dopytov",
|
"click_to_view_queries": "Kliknite pre zobrazenie dopytov",
|
||||||
"port_53_faq_link": "Port 53 je často obsadený službami \"DNSStubListener\" alebo \"systemd-resolved\". Prečítajte si <0>tento návod</0> o tom, ako to vyriešiť.",
|
"port_53_faq_link": "Port 53 je často obsadený službami \"DNSStubListener\" alebo \"systemd-resolved\". Prečítajte si <0>tento návod</0> o tom, ako to vyriešiť.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home zruší všetky DNS dopyty od tohto klienta.",
|
"adg_will_drop_dns_queries": "AdGuard Home zruší všetky DNS dopyty od tohto klienta.",
|
||||||
"client_not_in_allowed_clients": "Klient nemá povolenie, pretože sa nenachádza v zozname „Povolení klienti“.",
|
"filter_allowlist": "UPOZORNENIE: Táto akcia tiež vylúči pravidlo \"\"{{disallowed_rule}}\"\" zo zoznamu povolených klientov.",
|
||||||
"experimental": "Experimentálne"
|
"last_rule_in_allowlist": "Nemôžete zakázať tohto klienta, pretože vylúčenie pravidla \"{{disallowed_rule}}\" zakáže zoznam \"povolených klientov\".",
|
||||||
|
"experimental": "Experimentálne",
|
||||||
|
"use_saved_key": "Použiť predtým uložený kľúč"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "lahko uporabite <0>DNS Žige</0> za reševalce <1>DNSCrypt</1> ali <2>DNS-prek-HTTPS</2>",
|
"example_upstream_sdns": "lahko uporabite <0>DNS Žige</0> za reševalce <1>DNSCrypt</1> ali <2>DNS-prek-HTTPS</2>",
|
||||||
"example_upstream_tcp": "redni DNS (nad TCP)",
|
"example_upstream_tcp": "redni DNS (nad TCP)",
|
||||||
"all_lists_up_to_date_toast": "Vsi seznami so že posodobljeni",
|
"all_lists_up_to_date_toast": "Vsi seznami so že posodobljeni",
|
||||||
"updated_upstream_dns_toast": "Posodobljeni Zagonske strežnike DNS",
|
"updated_upstream_dns_toast": "Gorvodni trežniki so uspešno shranjeni",
|
||||||
"dns_test_ok_toast": "Navedeni strežniki DNS delujejo pravilno",
|
"dns_test_ok_toast": "Navedeni strežniki DNS delujejo pravilno",
|
||||||
"dns_test_not_ok_toast": "Ni mogoče uporabiti: strežnika \"{{key}}\". Preverite, ali ste ga pravilno napisali",
|
"dns_test_not_ok_toast": "Ni mogoče uporabiti: strežnika \"{{key}}\". Preverite, ali ste ga pravilno napisali",
|
||||||
"unblock": "Omogoči",
|
"unblock": "Omogoči",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Nalaganje...",
|
"loading_table_status": "Nalaganje...",
|
||||||
"page_table_footer_text": "Stran",
|
"page_table_footer_text": "Stran",
|
||||||
"rows_table_footer_text": "vrstic",
|
"rows_table_footer_text": "vrstic",
|
||||||
"updated_custom_filtering_toast": "Posodobljena pravila filtriranja po meri",
|
"updated_custom_filtering_toast": "Pravila po meri so uspešno shranjena",
|
||||||
"rule_removed_from_custom_filtering_toast": "Pravilo je odstranjeno iz pravil filtriranja po meri: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Pravilo je odstranjeno iz pravil filtriranja po meri: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Pravilo je dodano pravilom filtriranja po meri: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Pravilo je dodano pravilom filtriranja po meri: {{rule}}",
|
||||||
"query_log_response_status": "Stanje: {{value}}",
|
"query_log_response_status": "Stanje: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Vaše naprave ali usmerjevalnik boste morali konfigurirati za uporabo strežnika DNS na naslednjih naslovih:",
|
"install_settings_dns_desc": "Vaše naprave ali usmerjevalnik boste morali konfigurirati za uporabo strežnika DNS na naslednjih naslovih:",
|
||||||
"install_settings_all_interfaces": "Vsi vmesniki",
|
"install_settings_all_interfaces": "Vsi vmesniki",
|
||||||
"install_auth_title": "Preverjanje pristnosti",
|
"install_auth_title": "Preverjanje pristnosti",
|
||||||
"install_auth_desc": "Zelo priporočljivo je, da konfigurirate overovitev pristnosti gesla za vaš skrbniški spletni vmesnik AdGuard Home. Tudi če je dostopen v vašem lokalnem omrežju, je še vedno pomembno, da ga zaščitite pred neomejenim dostopom.",
|
"install_auth_desc": "Nastavljeno mora biti preverjanje pristnosti gesla za skrbniški spletni vmesnik AdGuard Home. Tudi če je AdGuard Home dostopen samo v vašem lokalnem omrežju, je še vedno pomembno, da ga zaščitite pred neomejenim dostopom.",
|
||||||
"install_auth_username": "Uporabniško ime",
|
"install_auth_username": "Uporabniško ime",
|
||||||
"install_auth_password": "Geslo",
|
"install_auth_password": "Geslo",
|
||||||
"install_auth_confirm": "Potrdite geslo",
|
"install_auth_confirm": "Potrdite geslo",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Ali ste prepričani, da želite počistiti statistiko?",
|
"statistics_clear_confirm": "Ali ste prepričani, da želite počistiti statistiko?",
|
||||||
"statistics_retention_confirm": "Ali ste prepričani, da želite spremeniti zadrževanje statistike? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
|
"statistics_retention_confirm": "Ali ste prepričani, da želite spremeniti zadrževanje statistike? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
|
||||||
"statistics_cleared": "Statistika je bila uspešno počiščena",
|
"statistics_cleared": "Statistika je bila uspešno počiščena",
|
||||||
|
"statistics_enable": "Omogoči statistiko",
|
||||||
"interval_hours": "{{count}} ur",
|
"interval_hours": "{{count}} ur",
|
||||||
"interval_hours_plural": "{{count}} ur",
|
"interval_hours_plural": "{{count}} ur",
|
||||||
"filters_configuration": "Nastavitve filtrov",
|
"filters_configuration": "Nastavitve filtrov",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Kliknite za prikaz poizvedb",
|
"click_to_view_queries": "Kliknite za prikaz poizvedb",
|
||||||
"port_53_faq_link": "Vrata 53 pogosto zasedajo storitve 'DNSStubListener' ali 'Sistemsko razrešene storitve'. Preberite <0>to navodilo</0> o tem, kako to rešiti.",
|
"port_53_faq_link": "Vrata 53 pogosto zasedajo storitve 'DNSStubListener' ali 'Sistemsko razrešene storitve'. Preberite <0>to navodilo</0> o tem, kako to rešiti.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home bo izpustil vse poizvedbe DNS iz tega odjemalca.",
|
"adg_will_drop_dns_queries": "AdGuard Home bo izpustil vse poizvedbe DNS iz tega odjemalca.",
|
||||||
"client_not_in_allowed_clients": "Odjemalec ni dovoljen, ker ga ni na seznamu \"Dovoljeni odjemalci\".",
|
"filter_allowlist": "OPOZORILO: S to akcijo bo pravilo \"{{disallowed_rule}}\" izključeno s seznama dovoljenih odjemalcev.",
|
||||||
"experimental": "Eksperimentalno"
|
"last_rule_in_allowlist": "Tega odjemalca ni mogoče onemogočiti, ker izključitev pravila \"{{disallowed_rule}}\" bo ONEMOGOČILO seznam 'Dovoljeni odjemalci'.",
|
||||||
|
"experimental": "Eksperimentalno",
|
||||||
|
"use_saved_key": "Uporabi prej shranjeni ključ"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
"client_settings": "Klientinställningar",
|
"client_settings": "Klientinställningar",
|
||||||
"bootstrap_dns": "Bootstrap-DNS-servrar",
|
"bootstrap_dns": "Bootstrap-DNS-servrar",
|
||||||
"bootstrap_dns_desc": "Bootstrap-DNS-servrar används för att slå upp DoH/DoT-resolvrarnas IP-adresser som du specificerat som uppström.",
|
"bootstrap_dns_desc": "Bootstrap-DNS-servrar används för att slå upp DoH/DoT-resolvrarnas IP-adresser som du specificerat som uppström.",
|
||||||
|
"local_ptr_placeholder": "Ange en serveradress per rad",
|
||||||
"check_dhcp_servers": "Letar efter DHCP-servrar",
|
"check_dhcp_servers": "Letar efter DHCP-servrar",
|
||||||
|
"save_config": "Spara konfiguration",
|
||||||
"enabled_dhcp": "DHCP-server aktiverad",
|
"enabled_dhcp": "DHCP-server aktiverad",
|
||||||
"disabled_dhcp": "Dhcp-server avaktiverad",
|
"disabled_dhcp": "Dhcp-server avaktiverad",
|
||||||
"dhcp_title": "DHCP-server (experimentell)",
|
"dhcp_title": "DHCP-server (experimentell)",
|
||||||
@@ -102,7 +104,7 @@
|
|||||||
"last_time_updated_table_header": "Uppdaterades senast",
|
"last_time_updated_table_header": "Uppdaterades senast",
|
||||||
"actions_table_header": "Åtgärder",
|
"actions_table_header": "Åtgärder",
|
||||||
"edit_table_action": "Redigera",
|
"edit_table_action": "Redigera",
|
||||||
"delete_table_action": "Ta bort",
|
"delete_table_action": "Radera",
|
||||||
"filters_and_hosts_hint": "AdGuard tillämpar grundläggande annonsblockeringsregler och värdfiltersyntaxer",
|
"filters_and_hosts_hint": "AdGuard tillämpar grundläggande annonsblockeringsregler och värdfiltersyntaxer",
|
||||||
"cancel_btn": "Avbryt",
|
"cancel_btn": "Avbryt",
|
||||||
"enter_name_hint": "Skriv in namn",
|
"enter_name_hint": "Skriv in namn",
|
||||||
@@ -175,7 +177,7 @@
|
|||||||
"install_auth_username": "Användarnamn",
|
"install_auth_username": "Användarnamn",
|
||||||
"install_auth_password": "Lösenord",
|
"install_auth_password": "Lösenord",
|
||||||
"install_auth_confirm": "Bekräfta lösenord",
|
"install_auth_confirm": "Bekräfta lösenord",
|
||||||
"install_auth_username_enter": "Skriv in användarnamn",
|
"install_auth_username_enter": "Ange användarnamn",
|
||||||
"install_auth_password_enter": "Skriv in lösenord",
|
"install_auth_password_enter": "Skriv in lösenord",
|
||||||
"install_step": "Steg",
|
"install_step": "Steg",
|
||||||
"install_devices_title": "Ställ in dina enheter",
|
"install_devices_title": "Ställ in dina enheter",
|
||||||
@@ -265,6 +267,7 @@
|
|||||||
"form_enter_ip": "Skriv in IP",
|
"form_enter_ip": "Skriv in IP",
|
||||||
"form_enter_mac": "Skriv in MAC",
|
"form_enter_mac": "Skriv in MAC",
|
||||||
"form_client_name": "Skriv in klientnamn",
|
"form_client_name": "Skriv in klientnamn",
|
||||||
|
"name": "Namn",
|
||||||
"client_global_settings": "Använda globala inställningar",
|
"client_global_settings": "Använda globala inställningar",
|
||||||
"client_deleted": "Klient \"{{key}}\" har raderats",
|
"client_deleted": "Klient \"{{key}}\" har raderats",
|
||||||
"client_added": "Klient \"{{key}}\" har lagts till",
|
"client_added": "Klient \"{{key}}\" har lagts till",
|
||||||
@@ -301,14 +304,18 @@
|
|||||||
"setup_dns_notice": "För att kunna använda <1>DNS-över-HTTPS</1> eller <1>DNS-över-TLS</1>, behöver du <0>konfigurera Kryptering</0> i AdGuard Home-inställningar.",
|
"setup_dns_notice": "För att kunna använda <1>DNS-över-HTTPS</1> eller <1>DNS-över-TLS</1>, behöver du <0>konfigurera Kryptering</0> i AdGuard Home-inställningar.",
|
||||||
"rewrite_added": "DNS-omskrivning för \"{{key}}\" lyckad",
|
"rewrite_added": "DNS-omskrivning för \"{{key}}\" lyckad",
|
||||||
"rewrite_deleted": "DNS-omskrivning för \"{{key}}\" har tagits bort",
|
"rewrite_deleted": "DNS-omskrivning för \"{{key}}\" har tagits bort",
|
||||||
|
"interval_6_hour": "6 timmar",
|
||||||
"interval_24_hour": "24 timmar",
|
"interval_24_hour": "24 timmar",
|
||||||
|
"interval_days": "{{count}} dag",
|
||||||
|
"interval_days_plural": "{{count}} dagar",
|
||||||
"domain": "Domän",
|
"domain": "Domän",
|
||||||
"answer": "Svar",
|
"answer": "Svar",
|
||||||
"statistics_retention_desc": "Om du minskar intervallet kommer viss data att gå förlorad",
|
"statistics_retention_desc": "Om du minskar intervallet kommer viss data att gå förlorad",
|
||||||
"statistics_clear": " Rensa statistik",
|
"statistics_clear": "Rensa statistik",
|
||||||
"statistics_clear_confirm": "Är du säker på att du vill radera statistiken?",
|
"statistics_clear_confirm": "Är du säker på att du vill radera statistiken?",
|
||||||
"statistics_retention_confirm": "Är du säker på att du vill ändra retentionstiden för statistik? Om du minskar intervallet kommer viss data att gå förlorad",
|
"statistics_retention_confirm": "Är du säker på att du vill ändra retentionstiden för statistik? Om du minskar intervallet kommer viss data att gå förlorad",
|
||||||
"statistics_cleared": "Statistiken har rensats",
|
"statistics_cleared": "Statistiken har rensats",
|
||||||
|
"statistics_enable": "Aktivera statistik",
|
||||||
"interval_hours": "{{count}} timme",
|
"interval_hours": "{{count}} timme",
|
||||||
"interval_hours_plural": "{{count}} timmar",
|
"interval_hours_plural": "{{count}} timmar",
|
||||||
"filters_configuration": "Filterinställningar",
|
"filters_configuration": "Filterinställningar",
|
||||||
@@ -329,7 +336,9 @@
|
|||||||
"descr": "Beskrivning",
|
"descr": "Beskrivning",
|
||||||
"whois": "Whois",
|
"whois": "Whois",
|
||||||
"filtering_rules_learn_more": "<0>Mer info</0> om att skapa dina egna blockeringslistor för värdar.",
|
"filtering_rules_learn_more": "<0>Mer info</0> om att skapa dina egna blockeringslistor för värdar.",
|
||||||
|
"try_again": "Försök igen",
|
||||||
"show_blocked_responses": "Blockerade",
|
"show_blocked_responses": "Blockerade",
|
||||||
"blocked_adult_websites": "Blockerade vuxensajter",
|
"blocked_adult_websites": "Blockerade vuxensajter",
|
||||||
"blocked_threats": "Blockerade hot"
|
"blocked_threats": "Blockerade hot",
|
||||||
|
"use_saved_key": "Använd den tidigare sparade nyckeln"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,8 +95,8 @@
|
|||||||
"protocol": "Protokol",
|
"protocol": "Protokol",
|
||||||
"on": "AÇIK",
|
"on": "AÇIK",
|
||||||
"off": "KAPALI",
|
"off": "KAPALI",
|
||||||
"copyright": "Telif hakkı",
|
"copyright": "Telif Hakkı",
|
||||||
"homepage": "Ana sayfa",
|
"homepage": "Ana Sayfa",
|
||||||
"report_an_issue": "Bir sorun bildir",
|
"report_an_issue": "Bir sorun bildir",
|
||||||
"privacy_policy": "Gizlilik Politikası",
|
"privacy_policy": "Gizlilik Politikası",
|
||||||
"enable_protection": "Korumayı etkinleştir",
|
"enable_protection": "Korumayı etkinleştir",
|
||||||
@@ -105,9 +105,9 @@
|
|||||||
"disabled_protection": "Koruma durduruldu",
|
"disabled_protection": "Koruma durduruldu",
|
||||||
"refresh_statics": "İstatistikleri yenile",
|
"refresh_statics": "İstatistikleri yenile",
|
||||||
"dns_query": "DNS Sorguları",
|
"dns_query": "DNS Sorguları",
|
||||||
"blocked_by": "<0>Filtreler tarafından engellendi</0>",
|
"blocked_by": "<0>Filtreler tarafından engellenen</0>",
|
||||||
"stats_malware_phishing": "Kötü amaçlı yazılım ve kimlik avı engellendi",
|
"stats_malware_phishing": "Engellenen kötü amaçlı yazılım ve kimlik avı",
|
||||||
"stats_adult": "Yetişkin içerikli site engellendi",
|
"stats_adult": "Engellenen yetişkin içerikli siteler",
|
||||||
"stats_query_domain": "En fazla sorgulanan alan adları",
|
"stats_query_domain": "En fazla sorgulanan alan adları",
|
||||||
"for_last_24_hours": "son 24 saat içindekiler",
|
"for_last_24_hours": "son 24 saat içindekiler",
|
||||||
"for_last_days": "son {{count}} gün boyunca",
|
"for_last_days": "son {{count}} gün boyunca",
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
"enforced_save_search": "Uygulanan güvenli arama",
|
"enforced_save_search": "Uygulanan güvenli arama",
|
||||||
"number_of_dns_query_to_safe_search": "Güvenli Aramanın uygulandığı arama motorlarına gönderilen DNS isteklerinin sayısı",
|
"number_of_dns_query_to_safe_search": "Güvenli Aramanın uygulandığı arama motorlarına gönderilen DNS isteklerinin sayısı",
|
||||||
"average_processing_time": "Ortalama işlem süresi",
|
"average_processing_time": "Ortalama işlem süresi",
|
||||||
"average_processing_time_hint": "Bir DNS isteğinin mili saniye cinsinden ortalama işlem süresi",
|
"average_processing_time_hint": "Bir DNS isteğinin milisaniye cinsinden ortalama işlem süresi",
|
||||||
"block_domain_use_filters_and_hosts": "Filtre ve ana bilgisayar listelerini kullanarak alan adlarını engelle",
|
"block_domain_use_filters_and_hosts": "Filtre ve ana bilgisayar listelerini kullanarak alan adlarını engelle",
|
||||||
"filters_block_toggle_hint": "<a>Filtreler</a> sayfasından engelleme kurallarını ayarlayabilirsiniz.",
|
"filters_block_toggle_hint": "<a>Filtreler</a> sayfasından engelleme kurallarını ayarlayabilirsiniz.",
|
||||||
"use_adguard_browsing_sec": "AdGuard gezinti koruması web hizmetini kullan",
|
"use_adguard_browsing_sec": "AdGuard gezinti koruması web hizmetini kullan",
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "<1>DNSCrypt</1> veya <2>DNS-over-HTTPS</2> çözümleyicileri için <0>DNS Damgaları</0> kullanabilirsiniz",
|
"example_upstream_sdns": "<1>DNSCrypt</1> veya <2>DNS-over-HTTPS</2> çözümleyicileri için <0>DNS Damgaları</0> kullanabilirsiniz",
|
||||||
"example_upstream_tcp": "normal DNS (TCP üzerinden)",
|
"example_upstream_tcp": "normal DNS (TCP üzerinden)",
|
||||||
"all_lists_up_to_date_toast": "Tüm listeler güncel durumda",
|
"all_lists_up_to_date_toast": "Tüm listeler güncel durumda",
|
||||||
"updated_upstream_dns_toast": "Üst DNS sunucuları güncellendi",
|
"updated_upstream_dns_toast": "Üst sunucular başarıyla kaydedildi",
|
||||||
"dns_test_ok_toast": "Belirtilen DNS sunucuları düzgün çalışıyor",
|
"dns_test_ok_toast": "Belirtilen DNS sunucuları düzgün çalışıyor",
|
||||||
"dns_test_not_ok_toast": "Sunucu \"{{key}}\": kullanılamıyor, lütfen doğru yazdığınızdan emin olun",
|
"dns_test_not_ok_toast": "Sunucu \"{{key}}\": kullanılamıyor, lütfen doğru yazdığınızdan emin olun",
|
||||||
"unblock": "Engeli kaldır",
|
"unblock": "Engeli kaldır",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "Yükleniyor...",
|
"loading_table_status": "Yükleniyor...",
|
||||||
"page_table_footer_text": "Sayfa",
|
"page_table_footer_text": "Sayfa",
|
||||||
"rows_table_footer_text": "satır",
|
"rows_table_footer_text": "satır",
|
||||||
"updated_custom_filtering_toast": "Özel filtreleme kuralları güncellendi",
|
"updated_custom_filtering_toast": "Özel kurallar başarıyla kaydedildi",
|
||||||
"rule_removed_from_custom_filtering_toast": "Özel filtreleme kurallarından kaldırıldı: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Özel filtreleme kurallarından kaldırıldı: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Özel filtreleme kurallarına eklendi: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Özel filtreleme kurallarına eklendi: {{rule}}",
|
||||||
"query_log_response_status": "Durum: {{value}}",
|
"query_log_response_status": "Durum: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Cihazlarınızı veya yönlendiricinizi şu adresteki DNS sunucusunu kullanması için ayarlamanız gerekecek:",
|
"install_settings_dns_desc": "Cihazlarınızı veya yönlendiricinizi şu adresteki DNS sunucusunu kullanması için ayarlamanız gerekecek:",
|
||||||
"install_settings_all_interfaces": "Tüm arayüzler",
|
"install_settings_all_interfaces": "Tüm arayüzler",
|
||||||
"install_auth_title": "Kimlik Doğrulama",
|
"install_auth_title": "Kimlik Doğrulama",
|
||||||
"install_auth_desc": "AdGuard Home yönetici web arayüzüne erişim için kullanıcı adı ve şifre yapılandırmanız önemle tavsiye edilir. Yalnızca yerel ağınızdan erişilebilir olsa bile izinsiz erişimden korumak yine de önemlidir.",
|
"install_auth_desc": "AdGuard Home yönetim web arayüzü için şifre doğrulaması yapılandırılmalıdır. AdGuard Home'a yalnızca yerel ağınızdan erişilebilir olsa bile, onu sınırsız erişimden korumak yine de önemlidir.",
|
||||||
"install_auth_username": "Kullanıcı adı",
|
"install_auth_username": "Kullanıcı adı",
|
||||||
"install_auth_password": "Parola",
|
"install_auth_password": "Parola",
|
||||||
"install_auth_confirm": "Parolayı onayla",
|
"install_auth_confirm": "Parolayı onayla",
|
||||||
@@ -352,7 +352,7 @@
|
|||||||
"encryption_config_saved": "Şifreleme yapılandırması kaydedildi",
|
"encryption_config_saved": "Şifreleme yapılandırması kaydedildi",
|
||||||
"encryption_server": "Sunucu adı",
|
"encryption_server": "Sunucu adı",
|
||||||
"encryption_server_enter": "Alan adınızı girin",
|
"encryption_server_enter": "Alan adınızı girin",
|
||||||
"encryption_server_desc": "HTTPS kullanmak için SSL sertifikanızla veya Willcard sertifikanızla eşleşen sunucu adını girmeniz gerekir. Bu alan ayarlanmazsa, herhangi bir alan adının TLS bağlantılarını kabul eder.",
|
"encryption_server_desc": "HTTPS kullanmak için SSL sertifikanızla veya joker sertifikanızla eşleşen sunucu adını girmeniz gerekir. Bu alan ayarlanmazsa, herhangi bir alan adının TLS bağlantılarını kabul eder.",
|
||||||
"encryption_redirect": "Otomatik olarak HTTPS'e yönlendir",
|
"encryption_redirect": "Otomatik olarak HTTPS'e yönlendir",
|
||||||
"encryption_redirect_desc": "Etkinleştirirseniz, AdGuard Home sizi HTTP adresi yerine HTTPS adresine yönlendirir.",
|
"encryption_redirect_desc": "Etkinleştirirseniz, AdGuard Home sizi HTTP adresi yerine HTTPS adresine yönlendirir.",
|
||||||
"encryption_https": "HTTPS bağlantı noktası",
|
"encryption_https": "HTTPS bağlantı noktası",
|
||||||
@@ -362,7 +362,7 @@
|
|||||||
"encryption_doq": "DNS-over-QUIC bağlantı noktası",
|
"encryption_doq": "DNS-over-QUIC bağlantı noktası",
|
||||||
"encryption_doq_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, DNS-over-QUIC sunucusunu bu bağlantı noktası üzerinden çalıştıracaktır. Bu özellik deneme aşamasındadır ve güvenilir olmayabilir. Ayrıca, şu anda bu özelliği destekleyen çok fazla istemci yok.",
|
"encryption_doq_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, DNS-over-QUIC sunucusunu bu bağlantı noktası üzerinden çalıştıracaktır. Bu özellik deneme aşamasındadır ve güvenilir olmayabilir. Ayrıca, şu anda bu özelliği destekleyen çok fazla istemci yok.",
|
||||||
"encryption_certificates": "Sertifikalar",
|
"encryption_certificates": "Sertifikalar",
|
||||||
"encryption_certificates_desc": "Şifrelemeyi kullanmak için alan adınız için geçerli bir SSL sertifika zinciri sağlamanız gerekir. <0>{{link}}</0> adresinden ücretsiz bir sertifika alabilir veya güvenilir Sertifika Yetkililerinden satın alabilirsiniz.",
|
"encryption_certificates_desc": "Şifrelemeyi kullanmak için alan adınıza geçerli bir SSL sertifika zinciri sağlamanız gerekir. <0>{{link}}</0> adresinden ücretsiz bir sertifika alabilir veya güvenilir Sertifika Yetkililerinden satın alabilirsiniz.",
|
||||||
"encryption_certificates_input": "PEM biçimindeki sertifikalarınızı kopyalayıp buraya yapıştırın.",
|
"encryption_certificates_input": "PEM biçimindeki sertifikalarınızı kopyalayıp buraya yapıştırın.",
|
||||||
"encryption_status": "Durum",
|
"encryption_status": "Durum",
|
||||||
"encryption_expire": "Bitiş tarihi",
|
"encryption_expire": "Bitiş tarihi",
|
||||||
@@ -476,7 +476,7 @@
|
|||||||
"blocked_services_saved": "Engellenen hizmetler başarıyla kaydedildi",
|
"blocked_services_saved": "Engellenen hizmetler başarıyla kaydedildi",
|
||||||
"blocked_services_global": "Genel olarak engellenen hizmetleri kullan",
|
"blocked_services_global": "Genel olarak engellenen hizmetleri kullan",
|
||||||
"blocked_service": "Engellenen hizmet",
|
"blocked_service": "Engellenen hizmet",
|
||||||
"block_all": "Hepsini engelle",
|
"block_all": "Tümünü engelle",
|
||||||
"unblock_all": "Tüm engellemeyi kaldır",
|
"unblock_all": "Tüm engellemeyi kaldır",
|
||||||
"encryption_certificate_path": "Sertifika yolu",
|
"encryption_certificate_path": "Sertifika yolu",
|
||||||
"encryption_private_key_path": "Özel anahtar yolu",
|
"encryption_private_key_path": "Özel anahtar yolu",
|
||||||
@@ -503,11 +503,12 @@
|
|||||||
"statistics_clear_confirm": "İstatistikleri temizlemek istediğinizden emin misiniz?",
|
"statistics_clear_confirm": "İstatistikleri temizlemek istediğinizden emin misiniz?",
|
||||||
"statistics_retention_confirm": "İstatistik saklama süresini değiştirmek istediğinizden emin misiniz? Zaman değerini azaltırsanız, bazı veriler kaybolacaktır",
|
"statistics_retention_confirm": "İstatistik saklama süresini değiştirmek istediğinizden emin misiniz? Zaman değerini azaltırsanız, bazı veriler kaybolacaktır",
|
||||||
"statistics_cleared": "İstatistikler başarıyla temizlendi",
|
"statistics_cleared": "İstatistikler başarıyla temizlendi",
|
||||||
|
"statistics_enable": "İstatistikleri etkinleştir",
|
||||||
"interval_hours": "{{count}} saat",
|
"interval_hours": "{{count}} saat",
|
||||||
"interval_hours_plural": "{{count}} saat",
|
"interval_hours_plural": "{{count}} saat",
|
||||||
"filters_configuration": "Filtre yapılandırması",
|
"filters_configuration": "Filtre yapılandırması",
|
||||||
"filters_enable": "Filtreleri etkinleştir",
|
"filters_enable": "Filtreleri etkinleştir",
|
||||||
"filters_interval": "Filtreleri güncelleme sıklığı",
|
"filters_interval": "Filtre güncelleme sıklığı",
|
||||||
"disabled": "Devre dışı",
|
"disabled": "Devre dışı",
|
||||||
"username_label": "Kullanıcı adı",
|
"username_label": "Kullanıcı adı",
|
||||||
"username_placeholder": "Kullanıcı adını girin",
|
"username_placeholder": "Kullanıcı adını girin",
|
||||||
@@ -575,13 +576,13 @@
|
|||||||
"dnssec_enable_desc": "Giden DNS sorguları için DNSSEC özelliğini etkinleştir ve sonucu kontrol et (DNSSEC özellikli çözümleyici gerekli).",
|
"dnssec_enable_desc": "Giden DNS sorguları için DNSSEC özelliğini etkinleştir ve sonucu kontrol et (DNSSEC özellikli çözümleyici gerekli).",
|
||||||
"validated_with_dnssec": "DNSSEC ile doğrulandı",
|
"validated_with_dnssec": "DNSSEC ile doğrulandı",
|
||||||
"all_queries": "Tüm sorgular",
|
"all_queries": "Tüm sorgular",
|
||||||
"show_blocked_responses": "Engellenen",
|
"show_blocked_responses": "Engellendi",
|
||||||
"show_whitelisted_responses": "İzin verilen",
|
"show_whitelisted_responses": "İzin verilen",
|
||||||
"show_processed_responses": "İşlenen",
|
"show_processed_responses": "İşlendi",
|
||||||
"blocked_safebrowsing": "Güvenli gezinti tarafından engellendi",
|
"blocked_safebrowsing": "Güvenli gezinti tarafından engellendi",
|
||||||
"blocked_adult_websites": "Yetişkin içerikli site engellendi",
|
"blocked_adult_websites": "Engellenen yetişkin içerikli siteler",
|
||||||
"blocked_threats": "Engellenen Tehditler",
|
"blocked_threats": "Engellenen tehditler",
|
||||||
"allowed": "İzin verilen",
|
"allowed": "İzin verildi",
|
||||||
"filtered": "Filtrelenen",
|
"filtered": "Filtrelenen",
|
||||||
"rewritten": "Yeniden yazılan",
|
"rewritten": "Yeniden yazılan",
|
||||||
"safe_search": "Güvenli arama",
|
"safe_search": "Güvenli arama",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "Sorguları görmek için tıklayın",
|
"click_to_view_queries": "Sorguları görmek için tıklayın",
|
||||||
"port_53_faq_link": "53 numaralı bağlantı noktası genellikle \"DNSStubListener\" veya \"systemd-resolved\" hizmetleri tarafından kullanılır. Lütfen bu sorunun nasıl çözüleceğine ilişkin <0>bu talimatı</0> okuyun.",
|
"port_53_faq_link": "53 numaralı bağlantı noktası genellikle \"DNSStubListener\" veya \"systemd-resolved\" hizmetleri tarafından kullanılır. Lütfen bu sorunun nasıl çözüleceğine ilişkin <0>bu talimatı</0> okuyun.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home, bu istemciden gelen tüm DNS sorgularını yok sayar.",
|
"adg_will_drop_dns_queries": "AdGuard Home, bu istemciden gelen tüm DNS sorgularını yok sayar.",
|
||||||
"client_not_in_allowed_clients": "\"İzin verilen istemciler\" listesinde olmadığı için istemciye izin verilmiyor.",
|
"filter_allowlist": "UYARI: Bu işlem ayrıca \"{{disallowed_rule}}\" kuralını izin verilen istemciler listesinden hariç tutacaktır.",
|
||||||
"experimental": "Deneysel"
|
"last_rule_in_allowlist": "\"{{disallowed_rule}}\" kuralı hariç tutulduğunda \"İzin verilen istemciler\" listesi DEVRE DIŞI bırakılacağı için bu istemciye izin verilemez.",
|
||||||
|
"experimental": "Deneysel",
|
||||||
|
"use_saved_key": "Önceden kaydedilmiş anahtarı kullan"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,8 @@
|
|||||||
"for_last_24_hours": "trong 24 giờ qua",
|
"for_last_24_hours": "trong 24 giờ qua",
|
||||||
"for_last_days": "trong {{count}} ngày qua",
|
"for_last_days": "trong {{count}} ngày qua",
|
||||||
"for_last_days_plural": "trong {{count}} ngày qua",
|
"for_last_days_plural": "trong {{count}} ngày qua",
|
||||||
|
"stats_disabled": "Số liệu thống kê đã bị vô hiệu hóa. Bạn có thể bật nó từ <0> trang cài đặt </0>.",
|
||||||
|
"stats_disabled_short": "Số liệu thống kê đã bị vô hiệu hóa",
|
||||||
"no_domains_found": "Không có tên miền nào",
|
"no_domains_found": "Không có tên miền nào",
|
||||||
"requests_count": "Số lần yêu cầu",
|
"requests_count": "Số lần yêu cầu",
|
||||||
"top_blocked_domains": "Tên miền chặn nhiều",
|
"top_blocked_domains": "Tên miền chặn nhiều",
|
||||||
@@ -206,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "bạn có thể sử dụng <0>DNS Stamps</0> for <1>DNSCrypt</1> hoặc <2>DNS-over-HTTPS</2> ",
|
"example_upstream_sdns": "bạn có thể sử dụng <0>DNS Stamps</0> for <1>DNSCrypt</1> hoặc <2>DNS-over-HTTPS</2> ",
|
||||||
"example_upstream_tcp": "DNS thông thường(dùng TCP)",
|
"example_upstream_tcp": "DNS thông thường(dùng TCP)",
|
||||||
"all_lists_up_to_date_toast": "Tất cả danh sách đã ở phiên bản mới nhất",
|
"all_lists_up_to_date_toast": "Tất cả danh sách đã ở phiên bản mới nhất",
|
||||||
"updated_upstream_dns_toast": "Đã cập nhật máy chủ DNS tìm kiếm",
|
"updated_upstream_dns_toast": "Các máy chủ thượng nguồn đã được lưu thành công",
|
||||||
"dns_test_ok_toast": "Máy chủ DNS có thể sử dụng",
|
"dns_test_ok_toast": "Máy chủ DNS có thể sử dụng",
|
||||||
"dns_test_not_ok_toast": "Máy chủ \"\"': không thể sử dụng, vui lòng kiểm tra lại",
|
"dns_test_not_ok_toast": "Máy chủ \"\"': không thể sử dụng, vui lòng kiểm tra lại",
|
||||||
"unblock": "Bỏ chặn",
|
"unblock": "Bỏ chặn",
|
||||||
@@ -233,7 +235,7 @@
|
|||||||
"loading_table_status": "Đang tải...",
|
"loading_table_status": "Đang tải...",
|
||||||
"page_table_footer_text": "Trang",
|
"page_table_footer_text": "Trang",
|
||||||
"rows_table_footer_text": "hàng",
|
"rows_table_footer_text": "hàng",
|
||||||
"updated_custom_filtering_toast": "Đã cập nhật bộ lọc tùy chỉnh",
|
"updated_custom_filtering_toast": "Đã cập nhật quy tắc lọc tuỳ chỉnh",
|
||||||
"rule_removed_from_custom_filtering_toast": "Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh: {{rule}}",
|
||||||
"query_log_response_status": "Trạng thái: {{value}}",
|
"query_log_response_status": "Trạng thái: {{value}}",
|
||||||
@@ -501,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "Bạn có chắc chắn muốn xóa số liệu thống kê?",
|
"statistics_clear_confirm": "Bạn có chắc chắn muốn xóa số liệu thống kê?",
|
||||||
"statistics_retention_confirm": "Bạn có chắc chắn muốn thay đổi lưu giữ số liệu thống kê? Nếu bạn giảm giá trị khoảng, một số dữ liệu sẽ bị mất",
|
"statistics_retention_confirm": "Bạn có chắc chắn muốn thay đổi lưu giữ số liệu thống kê? Nếu bạn giảm giá trị khoảng, một số dữ liệu sẽ bị mất",
|
||||||
"statistics_cleared": "Xoá thống kê thành công",
|
"statistics_cleared": "Xoá thống kê thành công",
|
||||||
|
"statistics_enable": "Bật thống kê",
|
||||||
"interval_hours": "{{count}} giờ",
|
"interval_hours": "{{count}} giờ",
|
||||||
"interval_hours_plural": "{{count}} giờ",
|
"interval_hours_plural": "{{count}} giờ",
|
||||||
"filters_configuration": "Cấu hình bộ lọc",
|
"filters_configuration": "Cấu hình bộ lọc",
|
||||||
@@ -608,6 +611,7 @@
|
|||||||
"click_to_view_queries": "Nhấp để xem truy xuất",
|
"click_to_view_queries": "Nhấp để xem truy xuất",
|
||||||
"port_53_faq_link": "Cổng 53 thường được sử dụng \"DNSStubListener\" hoặc \"systemd-resolved\". Vui lòng đọc <0>hướng dẫn</0> để giải quyết vấn đề này.",
|
"port_53_faq_link": "Cổng 53 thường được sử dụng \"DNSStubListener\" hoặc \"systemd-resolved\". Vui lòng đọc <0>hướng dẫn</0> để giải quyết vấn đề này.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home sẽ loại bỏ tất cả các truy vấn DNS từ ứng dụng khách này.",
|
"adg_will_drop_dns_queries": "AdGuard Home sẽ loại bỏ tất cả các truy vấn DNS từ ứng dụng khách này.",
|
||||||
"client_not_in_allowed_clients": "Ứng dụng khách không được phép vì nó không có trong danh sách \"Ứng dụng khách được phép\".",
|
"filter_allowlist": "CẢNH BÁO: Hành động này cũng sẽ loại trừ quy tắc \"{{disallowed_rule}}\" khỏi danh sách các ứng dụng khách được phép.",
|
||||||
|
"last_rule_in_allowlist": "Không thể không cho phép ứng dụng khách này vì việc loại trừ quy tắc \"{{disallowed_rule}}\" sẽ TẮT danh sách \"Ứng dụng khách được phép\".",
|
||||||
"experimental": "Thử nghiệm"
|
"experimental": "Thử nghiệm"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "你可以使用 <1>DNSCrypt</1> 的 <0>DNS Stamps</0> 或者 <2>DNS-over-HTTPS</2> 解析器",
|
"example_upstream_sdns": "你可以使用 <1>DNSCrypt</1> 的 <0>DNS Stamps</0> 或者 <2>DNS-over-HTTPS</2> 解析器",
|
||||||
"example_upstream_tcp": "常规 DNS(基于 TCP )",
|
"example_upstream_tcp": "常规 DNS(基于 TCP )",
|
||||||
"all_lists_up_to_date_toast": "所有列表都是最新的",
|
"all_lists_up_to_date_toast": "所有列表都是最新的",
|
||||||
"updated_upstream_dns_toast": "上游 DNS 已更新",
|
"updated_upstream_dns_toast": "上游服务器保存成功",
|
||||||
"dns_test_ok_toast": "指定的 DNS 服务器现已正常运行",
|
"dns_test_ok_toast": "指定的 DNS 服务器现已正常运行",
|
||||||
"dns_test_not_ok_toast": "服务器 \"{{key}}\":无法使用,请检查你输入的是否正确",
|
"dns_test_not_ok_toast": "服务器 \"{{key}}\":无法使用,请检查你输入的是否正确",
|
||||||
"unblock": "放行",
|
"unblock": "放行",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "加载中……",
|
"loading_table_status": "加载中……",
|
||||||
"page_table_footer_text": "页",
|
"page_table_footer_text": "页",
|
||||||
"rows_table_footer_text": "行",
|
"rows_table_footer_text": "行",
|
||||||
"updated_custom_filtering_toast": "自定义过滤规则已更新",
|
"updated_custom_filtering_toast": "自定义规则保存成功",
|
||||||
"rule_removed_from_custom_filtering_toast": "规则已从自定义过滤规则列表中移除 {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "规则已从自定义过滤规则列表中移除 {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "规则已添加到自定义过滤规则列表中 {{rule}}",
|
"rule_added_to_custom_filtering_toast": "规则已添加到自定义过滤规则列表中 {{rule}}",
|
||||||
"query_log_response_status": "状态: {{value}}",
|
"query_log_response_status": "状态: {{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "您将需要使用以下地址来设置您的设备或路由器的 DNS 服务器:",
|
"install_settings_dns_desc": "您将需要使用以下地址来设置您的设备或路由器的 DNS 服务器:",
|
||||||
"install_settings_all_interfaces": "所有接口",
|
"install_settings_all_interfaces": "所有接口",
|
||||||
"install_auth_title": "身份认证",
|
"install_auth_title": "身份认证",
|
||||||
"install_auth_desc": "我们强烈建议您为 AdGuard Home 的网页管理界面配置密码。即使该页面只能通过您的本地网络访问,但避免它被不加限制地访问仍十分重要。",
|
"install_auth_desc": "您需要对 AdGuard Home 的网页管理界面配置密码认证。即使该 AdGuard Home 只能通过您的本地网络访问,避免 AdGuard Home 被不受限制地访问依旧十分重要。",
|
||||||
"install_auth_username": "用户名",
|
"install_auth_username": "用户名",
|
||||||
"install_auth_password": "密码",
|
"install_auth_password": "密码",
|
||||||
"install_auth_confirm": "确认密码",
|
"install_auth_confirm": "确认密码",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "您确定要清除统计数据?",
|
"statistics_clear_confirm": "您确定要清除统计数据?",
|
||||||
"statistics_retention_confirm": "您确定要更改统计记录保留时间吗? 如果您减少间隔时间的值, 某些数据可能会丢失。",
|
"statistics_retention_confirm": "您确定要更改统计记录保留时间吗? 如果您减少间隔时间的值, 某些数据可能会丢失。",
|
||||||
"statistics_cleared": "统计数据已成功清除",
|
"statistics_cleared": "统计数据已成功清除",
|
||||||
|
"statistics_enable": "启用统计数据",
|
||||||
"interval_hours": "{{count}} 小时",
|
"interval_hours": "{{count}} 小时",
|
||||||
"interval_hours_plural": "{{count}} 小时",
|
"interval_hours_plural": "{{count}} 小时",
|
||||||
"filters_configuration": "过滤器配置",
|
"filters_configuration": "过滤器配置",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "点击查看查询",
|
"click_to_view_queries": "点击查看查询",
|
||||||
"port_53_faq_link": "53端口常被DNSStubListener或systemdn解析的服务占用。请阅读<0>这份关于如何解决这一问题的说明</0>",
|
"port_53_faq_link": "53端口常被DNSStubListener或systemdn解析的服务占用。请阅读<0>这份关于如何解决这一问题的说明</0>",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home 会终止所有来自此客户端的DNS查询。",
|
"adg_will_drop_dns_queries": "AdGuard Home 会终止所有来自此客户端的DNS查询。",
|
||||||
"client_not_in_allowed_clients": "此客户端不被允许,因为它不在“允许的客户端”列表中。",
|
"filter_allowlist": "警告:此操作将把规则 \"{{disallowed_rule}}\" 排除在允许客户端的列表之外。",
|
||||||
"experimental": "实验性的"
|
"last_rule_in_allowlist": "无法禁止此客户端,因为排除 “{{disallowed_rule}}” 规则将禁用“允许客户端”的列表。",
|
||||||
|
"experimental": "实验性的",
|
||||||
|
"use_saved_key": "使用之前保存的密钥"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "您可使用關於 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2> 解析器之 <0>DNS 戳記</0>",
|
"example_upstream_sdns": "您可使用關於 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2> 解析器之 <0>DNS 戳記</0>",
|
||||||
"example_upstream_tcp": "一般的 DNS(透過 TCP)",
|
"example_upstream_tcp": "一般的 DNS(透過 TCP)",
|
||||||
"all_lists_up_to_date_toast": "所有的清單已是最新的",
|
"all_lists_up_to_date_toast": "所有的清單已是最新的",
|
||||||
"updated_upstream_dns_toast": "已更新上游的 DNS 伺服器",
|
"updated_upstream_dns_toast": "上游的伺服器被成功地儲存",
|
||||||
"dns_test_ok_toast": "已明確指定的 DNS 伺服器正在正確地運作",
|
"dns_test_ok_toast": "已明確指定的 DNS 伺服器正在正確地運作",
|
||||||
"dns_test_not_ok_toast": "伺服器 \"{{key}}\":無法被使用,請檢查您已正確地填寫它",
|
"dns_test_not_ok_toast": "伺服器 \"{{key}}\":無法被使用,請檢查您已正確地填寫它",
|
||||||
"unblock": "解除封鎖",
|
"unblock": "解除封鎖",
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"loading_table_status": "正在載入…",
|
"loading_table_status": "正在載入…",
|
||||||
"page_table_footer_text": "頁面",
|
"page_table_footer_text": "頁面",
|
||||||
"rows_table_footer_text": "列",
|
"rows_table_footer_text": "列",
|
||||||
"updated_custom_filtering_toast": "已更新自訂的過濾規則",
|
"updated_custom_filtering_toast": "自訂的規則被成功地儲存",
|
||||||
"rule_removed_from_custom_filtering_toast": "從自訂的過濾規則中被移除的規則:{{rule}}",
|
"rule_removed_from_custom_filtering_toast": "從自訂的過濾規則中被移除的規則:{{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "被加至自訂的過濾規則中的規則:{{rule}}",
|
"rule_added_to_custom_filtering_toast": "被加至自訂的過濾規則中的規則:{{rule}}",
|
||||||
"query_log_response_status": "狀態:{{value}}",
|
"query_log_response_status": "狀態:{{value}}",
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "您將需要配置您的裝置或路由器以使用於下列的位址上之 DNS 伺服器:",
|
"install_settings_dns_desc": "您將需要配置您的裝置或路由器以使用於下列的位址上之 DNS 伺服器:",
|
||||||
"install_settings_all_interfaces": "所有的介面",
|
"install_settings_all_interfaces": "所有的介面",
|
||||||
"install_auth_title": "驗證",
|
"install_auth_title": "驗證",
|
||||||
"install_auth_desc": "配置屬於您的 AdGuard Home 管理員網路介面之密碼驗證是被非常建議的。即使它僅在您的區域網路中為可存取的,保護它免於不受限制的存取為仍然重要的。",
|
"install_auth_desc": "往您的 AdGuard Home 管理員網路介面之密碼驗證必須被配置。即使 AdGuard Home 僅在您的區域網路中為可存取的,保護它免於不受限制的存取為仍然重要的。",
|
||||||
"install_auth_username": "使用者名稱",
|
"install_auth_username": "使用者名稱",
|
||||||
"install_auth_password": "密碼",
|
"install_auth_password": "密碼",
|
||||||
"install_auth_confirm": "確認密碼",
|
"install_auth_confirm": "確認密碼",
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
"statistics_clear_confirm": "您確定您想要清除統計資料嗎?",
|
"statistics_clear_confirm": "您確定您想要清除統計資料嗎?",
|
||||||
"statistics_retention_confirm": "您確定您想要更改統計資料保留嗎?如果您減少該間隔值,某些資料將被丟失",
|
"statistics_retention_confirm": "您確定您想要更改統計資料保留嗎?如果您減少該間隔值,某些資料將被丟失",
|
||||||
"statistics_cleared": "統計資料被成功地清除",
|
"statistics_cleared": "統計資料被成功地清除",
|
||||||
|
"statistics_enable": "啟用統計資料",
|
||||||
"interval_hours": "{{count}} 小時",
|
"interval_hours": "{{count}} 小時",
|
||||||
"interval_hours_plural": "{{count}} 小時",
|
"interval_hours_plural": "{{count}} 小時",
|
||||||
"filters_configuration": "過濾器配置",
|
"filters_configuration": "過濾器配置",
|
||||||
@@ -612,6 +613,8 @@
|
|||||||
"click_to_view_queries": "點擊以檢視查詢",
|
"click_to_view_queries": "點擊以檢視查詢",
|
||||||
"port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。",
|
"port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home 將持續排除來自此用戶端之所有的 DNS 查詢。",
|
"adg_will_drop_dns_queries": "AdGuard Home 將持續排除來自此用戶端之所有的 DNS 查詢。",
|
||||||
"client_not_in_allowed_clients": "該用戶端未被允許,因為它不在\"已允許的用戶端\"清單中。",
|
"filter_allowlist": "警告:此操作將把 \"{{disallowed_rule}}\" 規則排除在已允許用戶端的清單之外。",
|
||||||
"experimental": "實驗性的"
|
"last_rule_in_allowlist": "無法禁止此用戶端,因為排除 “{{disallowed_rule}}” 規則將禁用“已允許用戶端”的清單。",
|
||||||
|
"experimental": "實驗性的",
|
||||||
|
"use_saved_key": "使用該先前已儲存的金鑰"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,25 +52,34 @@ export const toggleClientBlock = (ip, disallowed, disallowed_rule) => async (dis
|
|||||||
dispatch(toggleClientBlockRequest());
|
dispatch(toggleClientBlockRequest());
|
||||||
try {
|
try {
|
||||||
const accessList = await apiClient.getAccessList();
|
const accessList = await apiClient.getAccessList();
|
||||||
const allowed_clients = accessList.allowed_clients ?? [];
|
|
||||||
const blocked_hosts = accessList.blocked_hosts ?? [];
|
const blocked_hosts = accessList.blocked_hosts ?? [];
|
||||||
const disallowed_clients = accessList.disallowed_clients ?? [];
|
let allowed_clients = accessList.allowed_clients ?? [];
|
||||||
|
let disallowed_clients = accessList.disallowed_clients ?? [];
|
||||||
const updatedDisallowedClients = disallowed
|
|
||||||
? disallowed_clients.filter((client) => client !== disallowed_rule)
|
|
||||||
: disallowed_clients.concat(ip);
|
|
||||||
|
|
||||||
|
if (disallowed) {
|
||||||
|
if (!disallowed_rule) {
|
||||||
|
allowed_clients = allowed_clients.concat(ip);
|
||||||
|
} else {
|
||||||
|
disallowed_clients = disallowed_clients
|
||||||
|
.filter((client) => client !== disallowed_rule);
|
||||||
|
}
|
||||||
|
} else if (allowed_clients.length > 1) {
|
||||||
|
allowed_clients = allowed_clients
|
||||||
|
.filter((client) => client !== disallowed_rule);
|
||||||
|
} else {
|
||||||
|
disallowed_clients = disallowed_clients.concat(ip);
|
||||||
|
}
|
||||||
const values = {
|
const values = {
|
||||||
allowed_clients,
|
allowed_clients,
|
||||||
blocked_hosts,
|
blocked_hosts,
|
||||||
disallowed_clients: updatedDisallowedClients,
|
disallowed_clients,
|
||||||
};
|
};
|
||||||
|
|
||||||
await apiClient.setAccessList(values);
|
await apiClient.setAccessList(values);
|
||||||
dispatch(toggleClientBlockSuccess(values));
|
dispatch(toggleClientBlockSuccess(values));
|
||||||
|
|
||||||
if (disallowed) {
|
if (disallowed) {
|
||||||
dispatch(addSuccessToast(i18next.t('client_unblocked', { ip: disallowed_rule })));
|
dispatch(addSuccessToast(i18next.t('client_unblocked', { ip: disallowed_rule || ip })));
|
||||||
} else {
|
} else {
|
||||||
dispatch(addSuccessToast(i18next.t('client_blocked', { ip })));
|
dispatch(addSuccessToast(i18next.t('client_blocked', { ip })));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -451,7 +451,6 @@ export const findActiveDhcp = (name) => async (dispatch, getState) => {
|
|||||||
dispatch(addErrorToast({ error: 'dhcp_static_ip_error' }));
|
dispatch(addErrorToast({ error: 'dhcp_static_ip_error' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
dispatch(addErrorToast({ error: 'dhcp_error' }));
|
dispatch(addErrorToast({ error: 'dhcp_error' }));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import CustomRules from '../../containers/CustomRules';
|
|||||||
import Services from '../Filters/Services';
|
import Services from '../Filters/Services';
|
||||||
import Logs from '../Logs';
|
import Logs from '../Logs';
|
||||||
|
|
||||||
|
|
||||||
const ROUTES = [
|
const ROUTES = [
|
||||||
{
|
{
|
||||||
path: MENU_URLS.root,
|
path: MENU_URLS.root,
|
||||||
|
|||||||
@@ -38,15 +38,23 @@ const renderBlockingButton = (ip, disallowed, disallowed_rule) => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const processingSet = useSelector((state) => state.access.processingSet);
|
const processingSet = useSelector((state) => state.access.processingSet);
|
||||||
|
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
|
||||||
|
|
||||||
const buttonClass = classNames('button-action button-action--main', {
|
const buttonClass = classNames('button-action button-action--main', {
|
||||||
'button-action--unblock': disallowed,
|
'button-action--unblock': disallowed,
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleClientStatus = async (ip, disallowed, disallowed_rule) => {
|
const toggleClientStatus = async (ip, disallowed, disallowed_rule) => {
|
||||||
const confirmMessage = disallowed
|
let confirmMessage;
|
||||||
? t('client_confirm_unblock', { ip: disallowed_rule })
|
|
||||||
: `${t('adg_will_drop_dns_queries')} ${t('client_confirm_block', { ip })}`;
|
if (disallowed) {
|
||||||
|
confirmMessage = t('client_confirm_unblock', { ip: disallowed_rule || ip });
|
||||||
|
} else {
|
||||||
|
confirmMessage = `${t('adg_will_drop_dns_queries')} ${t('client_confirm_block', { ip })}`;
|
||||||
|
if (allowedСlients.length > 0) {
|
||||||
|
confirmMessage = confirmMessage.concat(`\n\n${t('filter_allowlist', { disallowed_rule })}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (window.confirm(confirmMessage)) {
|
if (window.confirm(confirmMessage)) {
|
||||||
await dispatch(toggleClientBlock(ip, disallowed, disallowed_rule));
|
await dispatch(toggleClientBlock(ip, disallowed, disallowed_rule));
|
||||||
@@ -58,15 +66,16 @@ const renderBlockingButton = (ip, disallowed, disallowed_rule) => {
|
|||||||
|
|
||||||
const text = disallowed ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
const text = disallowed ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
||||||
|
|
||||||
const isNotInAllowedList = disallowed && disallowed_rule === '';
|
const lastRuleInAllowlist = !disallowed && allowedСlients === disallowed_rule;
|
||||||
|
const disabled = processingSet || lastRuleInAllowlist;
|
||||||
return (
|
return (
|
||||||
<div className="table__action pl-4">
|
<div className="table__action pl-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={buttonClass}
|
className={buttonClass}
|
||||||
onClick={isNotInAllowedList ? undefined : onClick}
|
onClick={onClick}
|
||||||
disabled={isNotInAllowedList || processingSet}
|
disabled={disabled}
|
||||||
title={t(isNotInAllowedList ? 'client_not_in_allowed_clients' : text)}
|
title={lastRuleInAllowlist ? t('last_rule_in_allowlist', { disallowed_rule }) : ''}
|
||||||
>
|
>
|
||||||
<Trans>{text}</Trans>
|
<Trans>{text}</Trans>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -16,18 +16,31 @@ const Row = ({
|
|||||||
? <LogsSearchLink response_status={response_status}>{formatNumber(count)}</LogsSearchLink>
|
? <LogsSearchLink response_status={response_status}>{formatNumber(count)}</LogsSearchLink>
|
||||||
: count;
|
: count;
|
||||||
|
|
||||||
return <tr key={label}>
|
return (
|
||||||
<td>
|
<div className="counters__row" key={label}>
|
||||||
<Trans components={translationComponents}>{label}</Trans>
|
<div className="counters__column">
|
||||||
<Tooltip content={tooltipTitle} placement="top"
|
<span className="counters__title">
|
||||||
className="tooltip-container tooltip-custom--narrow text-center">
|
<Trans components={translationComponents}>
|
||||||
<svg className="icons icon--20 icon--lightgray ml-2">
|
{label}
|
||||||
<use xlinkHref="#question" />
|
</Trans>
|
||||||
</svg>
|
</span>
|
||||||
</Tooltip>
|
<span className="counters__tooltip">
|
||||||
</td>
|
<Tooltip
|
||||||
<td className="text-right"><strong>{content}</strong></td>
|
content={tooltipTitle}
|
||||||
</tr>;
|
placement="top"
|
||||||
|
className="tooltip-container tooltip-custom--narrow text-center"
|
||||||
|
>
|
||||||
|
<svg className="icons icon--20 icon--lightgray ml-2">
|
||||||
|
<use xlinkHref="#question" />
|
||||||
|
</svg>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="counters__column counters__column--value">
|
||||||
|
<strong>{content}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Counters = ({ refreshButton, subtitle }) => {
|
const Counters = ({ refreshButton, subtitle }) => {
|
||||||
@@ -88,9 +101,9 @@ const Counters = ({ refreshButton, subtitle }) => {
|
|||||||
bodyType="card-table"
|
bodyType="card-table"
|
||||||
refresh={refreshButton}
|
refresh={refreshButton}
|
||||||
>
|
>
|
||||||
<table className="table card-table">
|
<div className="counters">
|
||||||
<tbody>{rows.map(Row)}</tbody>
|
{rows.map(Row)}
|
||||||
</table>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,3 +49,39 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.counters__row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counters__column--value {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.counters__column {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counters__title {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counters__tooltip {
|
||||||
|
display: block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ const renderLink = ({ url, name }) => <a
|
|||||||
<strong>{name}</strong>
|
<strong>{name}</strong>
|
||||||
</a>;
|
</a>;
|
||||||
|
|
||||||
|
|
||||||
const getTrackerInfo = (trackerData) => [{
|
const getTrackerInfo = (trackerData) => [{
|
||||||
key: 'name_table_header',
|
key: 'name_table_header',
|
||||||
value: trackerData,
|
value: trackerData,
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ const ClientCell = ({
|
|||||||
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
||||||
const processingRules = useSelector((state) => state.filtering.processingRules);
|
const processingRules = useSelector((state) => state.filtering.processingRules);
|
||||||
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||||
|
const processingSet = useSelector((state) => state.access.processingSet);
|
||||||
|
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
|
||||||
const [isOptionsOpened, setOptionsOpened] = useState(false);
|
const [isOptionsOpened, setOptionsOpened] = useState(false);
|
||||||
|
|
||||||
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
||||||
@@ -71,11 +73,12 @@ const ClientCell = ({
|
|||||||
const {
|
const {
|
||||||
confirmMessage,
|
confirmMessage,
|
||||||
buttonKey: blockingClientKey,
|
buttonKey: blockingClientKey,
|
||||||
isNotInAllowedList,
|
lastRuleInAllowlist,
|
||||||
} = getBlockClientInfo(
|
} = getBlockClientInfo(
|
||||||
client,
|
client,
|
||||||
client_info?.disallowed || false,
|
client_info?.disallowed || false,
|
||||||
client_info?.disallowed_rule || '',
|
client_info?.disallowed_rule || '',
|
||||||
|
allowedСlients,
|
||||||
);
|
);
|
||||||
|
|
||||||
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
|
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
|
||||||
@@ -100,7 +103,7 @@ const ClientCell = ({
|
|||||||
await dispatch(updateLogs());
|
await dispatch(updateLogs());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
disabled: isNotInAllowedList,
|
disabled: processingSet || lastRuleInAllowlist,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,24 @@ import i18next from 'i18next';
|
|||||||
|
|
||||||
export const BUTTON_PREFIX = 'btn_';
|
export const BUTTON_PREFIX = 'btn_';
|
||||||
|
|
||||||
export const getBlockClientInfo = (ip, disallowed, disallowed_rule) => {
|
export const getBlockClientInfo = (ip, disallowed, disallowed_rule, allowedСlients) => {
|
||||||
const confirmMessage = disallowed
|
let confirmMessage;
|
||||||
? i18next.t('client_confirm_unblock', { ip: disallowed_rule })
|
|
||||||
: `${i18next.t('adg_will_drop_dns_queries')} ${i18next.t('client_confirm_block', { ip })}`;
|
if (disallowed) {
|
||||||
|
confirmMessage = i18next.t('client_confirm_unblock', { ip: disallowed_rule || ip });
|
||||||
|
} else {
|
||||||
|
confirmMessage = `${i18next.t('adg_will_drop_dns_queries')} ${i18next.t('client_confirm_block', { ip })}`;
|
||||||
|
if (allowedСlients.length > 0) {
|
||||||
|
confirmMessage = confirmMessage.concat(`\n\n${i18next.t('filter_allowlist', { disallowed_rule })}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const buttonKey = i18next.t(disallowed ? 'allow_this_client' : 'disallow_this_client');
|
const buttonKey = i18next.t(disallowed ? 'allow_this_client' : 'disallow_this_client');
|
||||||
const isNotInAllowedList = disallowed && disallowed_rule === '';
|
const lastRuleInAllowlist = !disallowed && allowedСlients === disallowed_rule;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
confirmMessage,
|
confirmMessage,
|
||||||
buttonKey,
|
buttonKey,
|
||||||
isNotInAllowedList,
|
lastRuleInAllowlist,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ const Row = memo(({
|
|||||||
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
|
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
|
||||||
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
|
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
|
||||||
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
||||||
|
const processingSet = useSelector((state) => state.access.processingSet);
|
||||||
|
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
|
||||||
|
|
||||||
const clients = useSelector((state) => state.dashboard.clients);
|
const clients = useSelector((state) => state.dashboard.clients);
|
||||||
|
|
||||||
@@ -104,11 +106,12 @@ const Row = memo(({
|
|||||||
const {
|
const {
|
||||||
confirmMessage,
|
confirmMessage,
|
||||||
buttonKey: blockingClientKey,
|
buttonKey: blockingClientKey,
|
||||||
isNotInAllowedList,
|
lastRuleInAllowlist,
|
||||||
} = getBlockClientInfo(
|
} = getBlockClientInfo(
|
||||||
client,
|
client,
|
||||||
client_info?.disallowed || false,
|
client_info?.disallowed || false,
|
||||||
client_info?.disallowed_rule || '',
|
client_info?.disallowed_rule || '',
|
||||||
|
allowedСlients,
|
||||||
);
|
);
|
||||||
|
|
||||||
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
|
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
|
||||||
@@ -147,7 +150,7 @@ const Row = memo(({
|
|||||||
const blockClientButton = <button
|
const blockClientButton = <button
|
||||||
className='text-center font-weight-bold py-2 button-action--arrow-option'
|
className='text-center font-weight-bold py-2 button-action--arrow-option'
|
||||||
onClick={onBlockingClientClick}
|
onClick={onBlockingClientClick}
|
||||||
disabled={isNotInAllowedList}>
|
disabled={processingSet || lastRuleInAllowlist}>
|
||||||
{t(blockingClientKey)}
|
{t(blockingClientKey)}
|
||||||
</button>;
|
</button>;
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import Disabled from './Disabled';
|
|||||||
import { getFilteringStatus } from '../../actions/filtering';
|
import { getFilteringStatus } from '../../actions/filtering';
|
||||||
import { getClients } from '../../actions';
|
import { getClients } from '../../actions';
|
||||||
import { getDnsConfig } from '../../actions/dnsConfig';
|
import { getDnsConfig } from '../../actions/dnsConfig';
|
||||||
|
import { getAccessList } from '../../actions/access';
|
||||||
import {
|
import {
|
||||||
getLogsConfig,
|
getLogsConfig,
|
||||||
resetFilteredLogs,
|
resetFilteredLogs,
|
||||||
@@ -76,8 +77,8 @@ const Logs = () => {
|
|||||||
const filter = useSelector((state) => state.queryLogs.filter, shallowEqual);
|
const filter = useSelector((state) => state.queryLogs.filter, shallowEqual);
|
||||||
const logs = useSelector((state) => state.queryLogs.logs, shallowEqual);
|
const logs = useSelector((state) => state.queryLogs.logs, shallowEqual);
|
||||||
|
|
||||||
const search = filter?.search || search_url_param || '';
|
const search = search_url_param || filter?.search || '';
|
||||||
const response_status = filter?.response_status || response_status_url_param || '';
|
const response_status = response_status_url_param || filter?.response_status || '';
|
||||||
|
|
||||||
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < SMALL_SCREEN_SIZE);
|
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < SMALL_SCREEN_SIZE);
|
||||||
const [detailedDataCurrent, setDetailedDataCurrent] = useState({});
|
const [detailedDataCurrent, setDetailedDataCurrent] = useState({});
|
||||||
@@ -126,6 +127,7 @@ const Logs = () => {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
dispatch(getLogsConfig()),
|
dispatch(getLogsConfig()),
|
||||||
dispatch(getDnsConfig()),
|
dispatch(getDnsConfig()),
|
||||||
|
dispatch(getAccessList()),
|
||||||
]);
|
]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ const renderInterfaces = (interfaces) => Object.keys(interfaces)
|
|||||||
return <option value={name} key={name}>{optionContent}</option>;
|
return <option value={name} key={name}>{optionContent}</option>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const getInterfaceValues = ({
|
const getInterfaceValues = ({
|
||||||
gateway_ip,
|
gateway_ip,
|
||||||
hardware_address,
|
hardware_address,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Field, reduxForm } from 'redux-form';
|
import { connect } from 'react-redux';
|
||||||
|
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, withTranslation } from 'react-i18next';
|
||||||
import flow from 'lodash/flow';
|
import flow from 'lodash/flow';
|
||||||
import { renderTextareaField } from '../../../../helpers/form';
|
import { renderTextareaField } from '../../../../helpers/form';
|
||||||
@@ -31,16 +32,20 @@ const fields = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Form = (props) => {
|
let Form = (props) => {
|
||||||
const {
|
const {
|
||||||
handleSubmit, submitting, invalid, processingSet,
|
allowedClients, handleSubmit, submitting, invalid, processingSet,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const renderField = ({
|
const renderField = ({
|
||||||
id, title, subtitle, disabled = processingSet, normalizeOnBlur,
|
id, title, subtitle, disabled = false, processingSet, normalizeOnBlur,
|
||||||
}) => <div key={id} className="form__group mb-5">
|
}) => <div key={id} className="form__group mb-5">
|
||||||
<label className="form__label form__label--with-desc" htmlFor={id}>
|
<label className="form__label form__label--with-desc" htmlFor={id}>
|
||||||
<Trans>{title}</Trans>
|
<Trans>{title}</Trans>
|
||||||
|
{disabled && <>
|
||||||
|
<span> </span>
|
||||||
|
(<Trans>disabled</Trans>)
|
||||||
|
</>}
|
||||||
</label>
|
</label>
|
||||||
<div className="form__desc form__desc--top">
|
<div className="form__desc form__desc--top">
|
||||||
<Trans>{subtitle}</Trans>
|
<Trans>{subtitle}</Trans>
|
||||||
@@ -51,7 +56,7 @@ const Form = (props) => {
|
|||||||
component={renderTextareaField}
|
component={renderTextareaField}
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control--textarea font-monospace"
|
className="form-control form-control--textarea font-monospace"
|
||||||
disabled={disabled}
|
disabled={disabled || processingSet}
|
||||||
normalizeOnBlur={normalizeOnBlur}
|
normalizeOnBlur={normalizeOnBlur}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
@@ -66,7 +71,15 @@ const Form = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{fields.map(renderField)}
|
{
|
||||||
|
fields.map((f) => {
|
||||||
|
const props = { ...f };
|
||||||
|
if (allowedClients && f.id === 'disallowed_clients') {
|
||||||
|
props.disabled = true;
|
||||||
|
}
|
||||||
|
return renderField(props);
|
||||||
|
})
|
||||||
|
}
|
||||||
<div className="card-actions">
|
<div className="card-actions">
|
||||||
<div className="btn-list">
|
<div className="btn-list">
|
||||||
<button
|
<button
|
||||||
@@ -90,6 +103,21 @@ Form.propTypes = {
|
|||||||
processingSet: PropTypes.bool.isRequired,
|
processingSet: PropTypes.bool.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
textarea: PropTypes.bool,
|
textarea: PropTypes.bool,
|
||||||
|
allowedClients: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default flow([withTranslation(), reduxForm({ form: FORM_NAME.ACCESS })])(Form);
|
const selector = formValueSelector(FORM_NAME.ACCESS);
|
||||||
|
|
||||||
|
Form = connect((state) => {
|
||||||
|
const allowedClients = selector(state, 'allowed_clients');
|
||||||
|
return {
|
||||||
|
allowedClients,
|
||||||
|
};
|
||||||
|
})(Form);
|
||||||
|
|
||||||
|
export default flow([
|
||||||
|
withTranslation(),
|
||||||
|
reduxForm({
|
||||||
|
form: FORM_NAME.ACCESS,
|
||||||
|
}),
|
||||||
|
])(Form);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import i18n from '../../../i18n';
|
|||||||
import KeyStatus from './KeyStatus';
|
import KeyStatus from './KeyStatus';
|
||||||
import CertificateStatus from './CertificateStatus';
|
import CertificateStatus from './CertificateStatus';
|
||||||
import {
|
import {
|
||||||
DNS_OVER_QUIC_PORT, DNS_OVER_TLS_PORT, FORM_NAME, STANDARD_HTTPS_PORT,
|
DNS_OVER_QUIC_PORT, DNS_OVER_TLS_PORT, FORM_NAME, STANDARD_HTTPS_PORT, ENCRYPTION_SOURCE,
|
||||||
} from '../../../helpers/constants';
|
} from '../../../helpers/constants';
|
||||||
|
|
||||||
const validate = (values) => {
|
const validate = (values) => {
|
||||||
@@ -46,6 +46,7 @@ const clearFields = (change, setTlsConfig, t) => {
|
|||||||
server_name: '',
|
server_name: '',
|
||||||
force_https: false,
|
force_https: false,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
private_key_saved: false,
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
if (window.confirm(t('encryption_reset'))) {
|
if (window.confirm(t('encryption_reset'))) {
|
||||||
@@ -83,6 +84,7 @@ let Form = (props) => {
|
|||||||
setTlsConfig,
|
setTlsConfig,
|
||||||
certificateSource,
|
certificateSource,
|
||||||
privateKeySource,
|
privateKeySource,
|
||||||
|
privateKeySaved,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isSavingDisabled = invalid
|
const isSavingDisabled = invalid
|
||||||
@@ -265,7 +267,7 @@ let Form = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{certificateSource === 'content' && (
|
{certificateSource === ENCRYPTION_SOURCE.CONTENT && (
|
||||||
<Field
|
<Field
|
||||||
id="certificate_chain"
|
id="certificate_chain"
|
||||||
name="certificate_chain"
|
name="certificate_chain"
|
||||||
@@ -277,7 +279,7 @@ let Form = (props) => {
|
|||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{certificateSource === 'path' && (
|
{certificateSource === ENCRYPTION_SOURCE.PATH && (
|
||||||
<Field
|
<Field
|
||||||
id="certificate_path"
|
id="certificate_path"
|
||||||
name="certificate_path"
|
name="certificate_path"
|
||||||
@@ -318,7 +320,7 @@ let Form = (props) => {
|
|||||||
component={renderRadioField}
|
component={renderRadioField}
|
||||||
type="radio"
|
type="radio"
|
||||||
className="form-control mr-2"
|
className="form-control mr-2"
|
||||||
value="path"
|
value={ENCRYPTION_SOURCE.PATH}
|
||||||
placeholder={t('encryption_key_source_path')}
|
placeholder={t('encryption_key_source_path')}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
@@ -327,28 +329,15 @@ let Form = (props) => {
|
|||||||
component={renderRadioField}
|
component={renderRadioField}
|
||||||
type="radio"
|
type="radio"
|
||||||
className="form-control mr-2"
|
className="form-control mr-2"
|
||||||
value="content"
|
value={ENCRYPTION_SOURCE.CONTENT}
|
||||||
placeholder={t('encryption_key_source_content')}
|
placeholder={t('encryption_key_source_content')}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{privateKeySource === 'content' && (
|
{privateKeySource === ENCRYPTION_SOURCE.PATH && (
|
||||||
<Field
|
<Field
|
||||||
id="private_key"
|
|
||||||
name="private_key"
|
|
||||||
component="textarea"
|
|
||||||
type="text"
|
|
||||||
className="form-control form-control--textarea"
|
|
||||||
placeholder={t('encryption_key_input')}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={!isEnabled}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{privateKeySource === 'path' && (
|
|
||||||
<Field
|
|
||||||
id="private_key_path"
|
|
||||||
name="private_key_path"
|
name="private_key_path"
|
||||||
component={renderInputField}
|
component={renderInputField}
|
||||||
type="text"
|
type="text"
|
||||||
@@ -358,6 +347,36 @@ let Form = (props) => {
|
|||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{privateKeySource === ENCRYPTION_SOURCE.CONTENT && [
|
||||||
|
<Field
|
||||||
|
key="private_key_saved"
|
||||||
|
name="private_key_saved"
|
||||||
|
type="checkbox"
|
||||||
|
className="form__group form__group--settings mb-2"
|
||||||
|
component={CheckboxField}
|
||||||
|
disabled={!isEnabled}
|
||||||
|
placeholder={t('use_saved_key')}
|
||||||
|
onChange={(event) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
change('private_key', '');
|
||||||
|
}
|
||||||
|
if (handleChange) {
|
||||||
|
handleChange(event);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
<Field
|
||||||
|
id="private_key"
|
||||||
|
key="private_key"
|
||||||
|
name="private_key"
|
||||||
|
component="textarea"
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control--textarea"
|
||||||
|
placeholder={t('encryption_key_input')}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={!isEnabled || privateKeySaved}
|
||||||
|
/>,
|
||||||
|
]}
|
||||||
</div>
|
</div>
|
||||||
<div className="form__status">
|
<div className="form__status">
|
||||||
{(privateKey || privateKeyPath) && (
|
{(privateKey || privateKeyPath) && (
|
||||||
@@ -422,6 +441,7 @@ Form.propTypes = {
|
|||||||
setTlsConfig: PropTypes.func.isRequired,
|
setTlsConfig: PropTypes.func.isRequired,
|
||||||
certificateSource: PropTypes.string,
|
certificateSource: PropTypes.string,
|
||||||
privateKeySource: PropTypes.string,
|
privateKeySource: PropTypes.string,
|
||||||
|
privateKeySaved: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
const selector = formValueSelector(FORM_NAME.ENCRYPTION);
|
const selector = formValueSelector(FORM_NAME.ENCRYPTION);
|
||||||
@@ -434,6 +454,7 @@ Form = connect((state) => {
|
|||||||
const privateKeyPath = selector(state, 'private_key_path');
|
const privateKeyPath = selector(state, 'private_key_path');
|
||||||
const certificateSource = selector(state, 'certificate_source');
|
const certificateSource = selector(state, 'certificate_source');
|
||||||
const privateKeySource = selector(state, 'key_source');
|
const privateKeySource = selector(state, 'key_source');
|
||||||
|
const privateKeySaved = selector(state, 'private_key_saved');
|
||||||
return {
|
return {
|
||||||
isEnabled,
|
isEnabled,
|
||||||
certificateChain,
|
certificateChain,
|
||||||
@@ -442,6 +463,7 @@ Form = connect((state) => {
|
|||||||
privateKeyPath,
|
privateKeyPath,
|
||||||
certificateSource,
|
certificateSource,
|
||||||
privateKeySource,
|
privateKeySource,
|
||||||
|
privateKeySaved,
|
||||||
};
|
};
|
||||||
})(Form);
|
})(Form);
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,13 @@ class Encryption extends Component {
|
|||||||
}, DEBOUNCE_TIMEOUT);
|
}, DEBOUNCE_TIMEOUT);
|
||||||
|
|
||||||
getInitialValues = (data) => {
|
getInitialValues = (data) => {
|
||||||
const { certificate_chain, private_key } = data;
|
const { certificate_chain, private_key, private_key_saved } = data;
|
||||||
const certificate_source = certificate_chain ? 'content' : 'path';
|
const certificate_source = certificate_chain
|
||||||
const key_source = private_key ? 'content' : 'path';
|
? ENCRYPTION_SOURCE.CONTENT
|
||||||
|
: ENCRYPTION_SOURCE.PATH;
|
||||||
|
const key_source = private_key || private_key_saved
|
||||||
|
? ENCRYPTION_SOURCE.CONTENT
|
||||||
|
: ENCRYPTION_SOURCE.PATH;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
@@ -41,7 +45,9 @@ class Encryption extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getSubmitValues = (values) => {
|
getSubmitValues = (values) => {
|
||||||
const { certificate_source, key_source, ...config } = values;
|
const {
|
||||||
|
certificate_source, key_source, private_key_saved, ...config
|
||||||
|
} = values;
|
||||||
|
|
||||||
if (certificate_source === ENCRYPTION_SOURCE.PATH) {
|
if (certificate_source === ENCRYPTION_SOURCE.PATH) {
|
||||||
config.certificate_chain = '';
|
config.certificate_chain = '';
|
||||||
@@ -49,10 +55,15 @@ class Encryption extends Component {
|
|||||||
config.certificate_path = '';
|
config.certificate_path = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.key_source === ENCRYPTION_SOURCE.PATH) {
|
if (key_source === ENCRYPTION_SOURCE.PATH) {
|
||||||
config.private_key = '';
|
config.private_key = '';
|
||||||
} else {
|
} else {
|
||||||
config.private_key_path = '';
|
config.private_key_path = '';
|
||||||
|
|
||||||
|
if (private_key_saved) {
|
||||||
|
config.private_key = '';
|
||||||
|
config.private_key_saved = private_key_saved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
@@ -71,6 +82,7 @@ class Encryption extends Component {
|
|||||||
private_key,
|
private_key,
|
||||||
certificate_path,
|
certificate_path,
|
||||||
private_key_path,
|
private_key_path,
|
||||||
|
private_key_saved,
|
||||||
} = encryption;
|
} = encryption;
|
||||||
|
|
||||||
const initialValues = this.getInitialValues({
|
const initialValues = this.getInitialValues({
|
||||||
@@ -84,6 +96,7 @@ class Encryption extends Component {
|
|||||||
private_key,
|
private_key,
|
||||||
certificate_path,
|
certificate_path,
|
||||||
private_key_path,
|
private_key_path,
|
||||||
|
private_key_saved,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ import { Field, reduxForm } from 'redux-form';
|
|||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, withTranslation } from 'react-i18next';
|
||||||
import flow from 'lodash/flow';
|
import flow from 'lodash/flow';
|
||||||
|
|
||||||
import { renderRadioField, toNumber } from '../../../helpers/form';
|
import { renderRadioField, toNumber, CheckboxField } from '../../../helpers/form';
|
||||||
import { FORM_NAME, STATS_INTERVALS_DAYS } from '../../../helpers/constants';
|
import { FORM_NAME, STATS_INTERVALS_DAYS, DISABLED_STATS_INTERVAL } from '../../../helpers/constants';
|
||||||
import '../FormButton.css';
|
import '../FormButton.css';
|
||||||
|
|
||||||
const getIntervalTitle = (interval, t) => {
|
const getIntervalTitle = (interval, t) => {
|
||||||
switch (interval) {
|
switch (interval) {
|
||||||
case 0:
|
|
||||||
return t('disabled');
|
|
||||||
case 1:
|
case 1:
|
||||||
return t('interval_24_hour');
|
return t('interval_24_hour');
|
||||||
default:
|
default:
|
||||||
@@ -19,24 +17,36 @@ const getIntervalTitle = (interval, t) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIntervalFields = (processing, t, toNumber) => STATS_INTERVALS_DAYS.map((interval) => <Field
|
|
||||||
key={interval}
|
|
||||||
name="interval"
|
|
||||||
type="radio"
|
|
||||||
component={renderRadioField}
|
|
||||||
value={interval}
|
|
||||||
placeholder={getIntervalTitle(interval, t)}
|
|
||||||
normalize={toNumber}
|
|
||||||
disabled={processing}
|
|
||||||
/>);
|
|
||||||
|
|
||||||
const Form = (props) => {
|
const Form = (props) => {
|
||||||
const {
|
const {
|
||||||
handleSubmit, processing, submitting, invalid, handleReset, processingReset, t,
|
handleSubmit,
|
||||||
|
change,
|
||||||
|
processing,
|
||||||
|
submitting,
|
||||||
|
invalid,
|
||||||
|
handleReset,
|
||||||
|
processingReset,
|
||||||
|
t,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="form__group form__group--settings">
|
||||||
|
<Field
|
||||||
|
name="enabled"
|
||||||
|
type="checkbox"
|
||||||
|
component={CheckboxField}
|
||||||
|
placeholder={t('statistics_enable')}
|
||||||
|
disabled={processing}
|
||||||
|
onChange={(event) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
change('interval', STATS_INTERVALS_DAYS[0]);
|
||||||
|
} else {
|
||||||
|
change('interval', DISABLED_STATS_INTERVAL);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<label className="form__label form__label--with-desc">
|
<label className="form__label form__label--with-desc">
|
||||||
<Trans>statistics_retention</Trans>
|
<Trans>statistics_retention</Trans>
|
||||||
</label>
|
</label>
|
||||||
@@ -45,7 +55,23 @@ const Form = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form__group form__group--settings mt-2">
|
<div className="form__group form__group--settings mt-2">
|
||||||
<div className="custom-controls-stacked">
|
<div className="custom-controls-stacked">
|
||||||
{getIntervalFields(processing, t, toNumber)}
|
{STATS_INTERVALS_DAYS.map((interval) => (
|
||||||
|
<Field
|
||||||
|
key={interval}
|
||||||
|
name="interval"
|
||||||
|
type="radio"
|
||||||
|
component={renderRadioField}
|
||||||
|
value={interval}
|
||||||
|
placeholder={getIntervalTitle(interval, t)}
|
||||||
|
normalize={toNumber}
|
||||||
|
disabled={processing}
|
||||||
|
onChange={(event) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
change('enabled', true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ import Form from './Form';
|
|||||||
class StatsConfig extends Component {
|
class StatsConfig extends Component {
|
||||||
handleFormSubmit = (values) => {
|
handleFormSubmit = (values) => {
|
||||||
const { t, interval: prevInterval } = this.props;
|
const { t, interval: prevInterval } = this.props;
|
||||||
|
const config = { interval: values.interval };
|
||||||
|
|
||||||
if (values.interval < prevInterval) {
|
if (config.interval < prevInterval) {
|
||||||
if (window.confirm(t('statistics_retention_confirm'))) {
|
if (window.confirm(t('statistics_retention_confirm'))) {
|
||||||
this.props.setStatsConfig(values);
|
this.props.setStatsConfig(config);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.props.setStatsConfig(values);
|
this.props.setStatsConfig(config);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,7 +40,10 @@ class StatsConfig extends Component {
|
|||||||
>
|
>
|
||||||
<div className="form">
|
<div className="form">
|
||||||
<Form
|
<Form
|
||||||
initialValues={{ interval }}
|
initialValues={{
|
||||||
|
interval,
|
||||||
|
enabled: !!interval,
|
||||||
|
}}
|
||||||
onSubmit={this.handleFormSubmit}
|
onSubmit={this.handleFormSubmit}
|
||||||
processing={processing}
|
processing={processing}
|
||||||
processingReset={processingReset}
|
processingReset={processingReset}
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export const FILTERS_URLS = {
|
|||||||
export const SERVICES = [
|
export const SERVICES = [
|
||||||
{
|
{
|
||||||
id: '9gag',
|
id: '9gag',
|
||||||
name: '9Gag',
|
name: '9GAG',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'amazon',
|
id: 'amazon',
|
||||||
@@ -355,7 +355,8 @@ export const ENCRYPTION_SOURCE = {
|
|||||||
export const FILTERED = 'Filtered';
|
export const FILTERED = 'Filtered';
|
||||||
export const NOT_FILTERED = 'NotFiltered';
|
export const NOT_FILTERED = 'NotFiltered';
|
||||||
|
|
||||||
export const STATS_INTERVALS_DAYS = [0, 1, 7, 30, 90];
|
export const DISABLED_STATS_INTERVAL = 0;
|
||||||
|
export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];
|
||||||
|
|
||||||
export const QUERY_LOG_INTERVALS_DAYS = [0.25, 1, 7, 30, 90];
|
export const QUERY_LOG_INTERVALS_DAYS = [0.25, 1, 7, 30, 90];
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import twosky from 'MainRoot/.twosky.json';
|
import twosky from 'MainRoot/.twosky.json';
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ Submit = connect((state) => {
|
|||||||
};
|
};
|
||||||
})(Submit);
|
})(Submit);
|
||||||
|
|
||||||
|
|
||||||
export default flow([
|
export default flow([
|
||||||
withTranslation(),
|
withTranslation(),
|
||||||
reduxForm({
|
reduxForm({
|
||||||
|
|||||||
16
go.mod
16
go.mod
@@ -3,11 +3,11 @@ module github.com/AdguardTeam/AdGuardHome
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.39.2
|
github.com/AdguardTeam/dnsproxy v0.39.8
|
||||||
github.com/AdguardTeam/golibs v0.9.1
|
github.com/AdguardTeam/golibs v0.9.3
|
||||||
github.com/AdguardTeam/urlfilter v0.14.6
|
github.com/AdguardTeam/urlfilter v0.14.6
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.2.1
|
github.com/ameshkov/dnscrypt/v2 v2.2.2
|
||||||
github.com/digineo/go-ipset/v2 v2.2.1
|
github.com/digineo/go-ipset/v2 v2.2.1
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020
|
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020
|
||||||
@@ -18,15 +18,15 @@ require (
|
|||||||
github.com/lucas-clemente/quic-go v0.21.1
|
github.com/lucas-clemente/quic-go v0.21.1
|
||||||
github.com/mdlayher/netlink v1.4.0
|
github.com/mdlayher/netlink v1.4.0
|
||||||
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf // indirect
|
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf // indirect
|
||||||
github.com/miekg/dns v1.1.42
|
github.com/miekg/dns v1.1.43
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/stretchr/objx v0.1.1 // indirect
|
github.com/stretchr/objx v0.1.1 // indirect
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/ti-mo/netfilter v0.4.0
|
github.com/ti-mo/netfilter v0.4.0
|
||||||
go.etcd.io/bbolt v1.3.5
|
go.etcd.io/bbolt v1.3.6
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a
|
||||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015
|
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06
|
howett.net/plist v0.0.0-20201203080718-1454fab16a06
|
||||||
|
|||||||
37
go.sum
37
go.sum
@@ -9,13 +9,13 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
|
|||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/AdguardTeam/dhcp v0.0.0-20210519141215-51808c73c0bf h1:gc042VRSIRSUzZ+Px6xQCRWNJZTaPkomisDfUZmoFNk=
|
github.com/AdguardTeam/dhcp v0.0.0-20210519141215-51808c73c0bf h1:gc042VRSIRSUzZ+Px6xQCRWNJZTaPkomisDfUZmoFNk=
|
||||||
github.com/AdguardTeam/dhcp v0.0.0-20210519141215-51808c73c0bf/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
|
github.com/AdguardTeam/dhcp v0.0.0-20210519141215-51808c73c0bf/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
|
||||||
github.com/AdguardTeam/dnsproxy v0.39.2 h1:GqsR1S4fFfVsVCSrdrfa0RfsQ2u+MeNUMqDkxdTD3gU=
|
github.com/AdguardTeam/dnsproxy v0.39.8 h1:miRhkZBx/19Rs1o10r3QC0D0Zc2J2Id/cqXwfvLOyM0=
|
||||||
github.com/AdguardTeam/dnsproxy v0.39.2/go.mod h1:aNXKNdTyKfgAG2OS712SYSaGIM9AasZsZxfiY4YiR/0=
|
github.com/AdguardTeam/dnsproxy v0.39.8/go.mod h1:eDpJKAdkHORRwAedjuERv+7SWlcz4cn+5uwrbUAWHRY=
|
||||||
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.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
github.com/AdguardTeam/golibs v0.8.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.9.2/go.mod h1:fCAMwPBJ8S7YMYbTWvYS+eeTLblP5E04IDtNAo7y7IY=
|
||||||
github.com/AdguardTeam/golibs v0.9.1 h1:mHSN4LfaY1uGmHPsl97paAND/VeSnM5r9XQ7pSYx93o=
|
github.com/AdguardTeam/golibs v0.9.3 h1:noeKHJEzrSwxzX0Zi3USM3cXf1qQV99SO772jet/uEY=
|
||||||
github.com/AdguardTeam/golibs v0.9.1/go.mod h1:fCAMwPBJ8S7YMYbTWvYS+eeTLblP5E04IDtNAo7y7IY=
|
github.com/AdguardTeam/golibs v0.9.3/go.mod h1:fCAMwPBJ8S7YMYbTWvYS+eeTLblP5E04IDtNAo7y7IY=
|
||||||
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.14.6 h1:emqoKZElooHACYehRBYENeKVN1a/rspxiqTIMYLuoIo=
|
github.com/AdguardTeam/urlfilter v0.14.6 h1:emqoKZElooHACYehRBYENeKVN1a/rspxiqTIMYLuoIo=
|
||||||
github.com/AdguardTeam/urlfilter v0.14.6/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
|
github.com/AdguardTeam/urlfilter v0.14.6/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
|
||||||
@@ -29,9 +29,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
|
|||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.1.3/go.mod h1:+8SbPbVXpxxcUsgGi8eodkqWPo1MyNHxKYC8hDpqLSo=
|
github.com/ameshkov/dnscrypt/v2 v2.2.2 h1:lxtS1iSA2EjTOMToSi+2+rwspNA+b/wG5/JpccvE9CU=
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.2.1 h1:+cApRxzeBZqjUNsN26TTz7r5A8U+buON3kJgIYE3QWQ=
|
github.com/ameshkov/dnscrypt/v2 v2.2.2/go.mod h1:+8SbPbVXpxxcUsgGi8eodkqWPo1MyNHxKYC8hDpqLSo=
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.2.1/go.mod h1:+8SbPbVXpxxcUsgGi8eodkqWPo1MyNHxKYC8hDpqLSo=
|
|
||||||
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||||
@@ -171,8 +170,8 @@ github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf/go.mod h1:7EpbotpCmVZ
|
|||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY=
|
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||||
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
@@ -250,8 +249,8 @@ github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/R
|
|||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
@@ -263,8 +262,8 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
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-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@@ -300,8 +299,9 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
|
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw=
|
||||||
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@@ -337,6 +337,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -354,15 +355,15 @@ golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c=
|
||||||
|
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ func TestEtcHostsContainerFSNotify(t *testing.T) {
|
|||||||
assertWriting(t, f, "127.0.0.2 newhost\n")
|
assertWriting(t, f, "127.0.0.2 newhost\n")
|
||||||
require.NoError(t, f.Sync())
|
require.NoError(t, f.Sync())
|
||||||
|
|
||||||
// Wait until fsnotify has triggerred and processed the
|
// Wait until fsnotify has triggered and processed the file-modification
|
||||||
// file-modification event.
|
// event.
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
t.Run("notified", func(t *testing.T) {
|
t.Run("notified", func(t *testing.T) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/digineo/go-ipset/v2"
|
"github.com/digineo/go-ipset/v2"
|
||||||
"github.com/mdlayher/netlink"
|
"github.com/mdlayher/netlink"
|
||||||
"github.com/ti-mo/netfilter"
|
"github.com/ti-mo/netfilter"
|
||||||
@@ -67,6 +68,15 @@ type ipsetProps struct {
|
|||||||
// unit is a convenient alias for struct{}.
|
// unit is a convenient alias for struct{}.
|
||||||
type unit = struct{}
|
type unit = struct{}
|
||||||
|
|
||||||
|
// ipsInIpset is the type of a set of IP-address-to-ipset mappings.
|
||||||
|
type ipsInIpset map[ipInIpsetEntry]unit
|
||||||
|
|
||||||
|
// ipInIpsetEntry is the type for entries in an ipsInIpset set.
|
||||||
|
type ipInIpsetEntry struct {
|
||||||
|
ipsetName string
|
||||||
|
ipArr [net.IPv6len]byte
|
||||||
|
}
|
||||||
|
|
||||||
// ipsetMgr is the Linux Netfilter ipset manager.
|
// ipsetMgr is the Linux Netfilter ipset manager.
|
||||||
type ipsetMgr struct {
|
type ipsetMgr struct {
|
||||||
nameToIpset map[string]ipsetProps
|
nameToIpset map[string]ipsetProps
|
||||||
@@ -82,7 +92,7 @@ type ipsetMgr struct {
|
|||||||
// are either added to all corresponding ipsets or not. When that stops
|
// are either added to all corresponding ipsets or not. When that stops
|
||||||
// being the case, for example if we add dynamic reconfiguration of
|
// being the case, for example if we add dynamic reconfiguration of
|
||||||
// ipsets, this map will need to become a per-ipset-name one.
|
// ipsets, this map will need to become a per-ipset-name one.
|
||||||
addedIPs map[[16]byte]unit
|
addedIPs ipsInIpset
|
||||||
|
|
||||||
ipv4Conn ipsetConn
|
ipv4Conn ipsetConn
|
||||||
ipv6Conn ipsetConn
|
ipv6Conn ipsetConn
|
||||||
@@ -199,7 +209,7 @@ func newIpsetMgrWithDialer(ipsetConf []string, dial ipsetDialer) (mgr IpsetManag
|
|||||||
|
|
||||||
dial: dial,
|
dial: dial,
|
||||||
|
|
||||||
addedIPs: make(map[[16]byte]unit),
|
addedIPs: make(ipsInIpset),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.dialNetfilter(&netlink.Config{})
|
err = m.dialNetfilter(&netlink.Config{})
|
||||||
@@ -265,16 +275,19 @@ func (m *ipsetMgr) addIPs(host string, set ipsetProps, ips []net.IP) (n int, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
var entries []*ipset.Entry
|
var entries []*ipset.Entry
|
||||||
var newAddedIPs [][16]byte
|
var newAddedEntries []ipInIpsetEntry
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
var iparr [16]byte
|
e := ipInIpsetEntry{
|
||||||
copy(iparr[:], ip.To16())
|
ipsetName: set.name,
|
||||||
if _, added := m.addedIPs[iparr]; added {
|
}
|
||||||
|
copy(e.ipArr[:], ip.To16())
|
||||||
|
|
||||||
|
if _, added := m.addedIPs[e]; added {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
entries = append(entries, ipset.NewEntry(ipset.EntryIP(ip)))
|
entries = append(entries, ipset.NewEntry(ipset.EntryIP(ip)))
|
||||||
newAddedIPs = append(newAddedIPs, iparr)
|
newAddedEntries = append(newAddedEntries, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
n = len(entries)
|
n = len(entries)
|
||||||
@@ -299,8 +312,8 @@ func (m *ipsetMgr) addIPs(host string, set ipsetProps, ips []net.IP) (n int, err
|
|||||||
|
|
||||||
// Only add these to the cache once we're sure that all of them were
|
// Only add these to the cache once we're sure that all of them were
|
||||||
// actually sent to the ipset.
|
// actually sent to the ipset.
|
||||||
for _, iparr := range newAddedIPs {
|
for _, e := range newAddedEntries {
|
||||||
m.addedIPs[iparr] = unit{}
|
m.addedIPs[e] = unit{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return n, nil
|
return n, nil
|
||||||
@@ -330,6 +343,8 @@ func (m *ipsetMgr) addToSets(
|
|||||||
return n, fmt.Errorf("unexpected family %s for ipset %q", set.family, set.name)
|
return n, fmt.Errorf("unexpected family %s for ipset %q", set.family, set.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug("ipset: added %d ips to set %s", nn, set.name)
|
||||||
|
|
||||||
n += nn
|
n += nn
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +361,8 @@ func (m *ipsetMgr) Add(host string, ip4s, ip6s []net.IP) (n int, err error) {
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug("ipset: found %d sets", len(sets))
|
||||||
|
|
||||||
return m.addToSets(host, ip4s, ip6s, sets)
|
return m.addToSets(host, ip4s, ip6s, sets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
@@ -101,7 +99,13 @@ func scanAddrs(s *bufio.Scanner) (addrs []string) {
|
|||||||
// local resolvers addresses on Windows. We execute the external command for
|
// local resolvers addresses on Windows. We execute the external command for
|
||||||
// now that is not the most accurate way.
|
// now that is not the most accurate way.
|
||||||
func (sr *systemResolvers) getAddrs() (addrs []string, err error) {
|
func (sr *systemResolvers) getAddrs() (addrs []string, err error) {
|
||||||
cmd := exec.Command("nslookup")
|
var cmdPath string
|
||||||
|
cmdPath, err = exec.LookPath("nslookup.exe")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("looking up cmd path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(cmdPath)
|
||||||
|
|
||||||
var stdin io.WriteCloser
|
var stdin io.WriteCloser
|
||||||
stdin, err = cmd.StdinPipe()
|
stdin, err = cmd.StdinPipe()
|
||||||
@@ -115,12 +119,6 @@ func (sr *systemResolvers) getAddrs() (addrs []string, err error) {
|
|||||||
return nil, fmt.Errorf("getting the command's stdout pipe: %w", err)
|
return nil, fmt.Errorf("getting the command's stdout pipe: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var stdoutLimited io.Reader
|
|
||||||
stdoutLimited, err = aghio.LimitReader(stdout, aghos.MaxCmdOutputSize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("limiting stdout reader: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go writeExit(stdin)
|
go writeExit(stdin)
|
||||||
|
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
@@ -128,7 +126,7 @@ func (sr *systemResolvers) getAddrs() (addrs []string, err error) {
|
|||||||
return nil, fmt.Errorf("start command executing: %w", err)
|
return nil, fmt.Errorf("start command executing: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := bufio.NewScanner(stdoutLimited)
|
s := bufio.NewScanner(stdout)
|
||||||
addrs = scanAddrs(s)
|
addrs = scanAddrs(s)
|
||||||
|
|
||||||
err = cmd.Wait()
|
err = cmd.Wait()
|
||||||
|
|||||||
@@ -4,10 +4,17 @@
|
|||||||
package aghos
|
package aghos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"syscall"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnsupportedError is returned by functions and methods when a particular
|
// UnsupportedError is returned by functions and methods when a particular
|
||||||
@@ -43,11 +50,6 @@ func HaveAdminRights() (bool, error) {
|
|||||||
return haveAdminRights()
|
return haveAdminRights()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendProcessSignal sends signal to a process.
|
|
||||||
func SendProcessSignal(pid int, sig syscall.Signal) error {
|
|
||||||
return sendProcessSignal(pid, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxCmdOutputSize is the maximum length of performed shell command output.
|
// MaxCmdOutputSize is the maximum length of performed shell command output.
|
||||||
const MaxCmdOutputSize = 2 * 1024
|
const MaxCmdOutputSize = 2 * 1024
|
||||||
|
|
||||||
@@ -58,13 +60,101 @@ func RunCommand(command string, arguments ...string) (int, string, error) {
|
|||||||
if len(out) > MaxCmdOutputSize {
|
if len(out) > MaxCmdOutputSize {
|
||||||
out = out[:MaxCmdOutputSize]
|
out = out[:MaxCmdOutputSize]
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return 1, "", fmt.Errorf("exec.Command(%s) failed: %v: %s", command, err, string(out))
|
if errors.As(err, new(*exec.ExitError)) {
|
||||||
|
return cmd.ProcessState.ExitCode(), string(out), nil
|
||||||
|
} else if err != nil {
|
||||||
|
return 1, "", fmt.Errorf("exec.Command(%s) failed: %w: %s", command, err, string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd.ProcessState.ExitCode(), string(out), nil
|
return cmd.ProcessState.ExitCode(), string(out), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PIDByCommand searches for process named command and returns its PID ignoring
|
||||||
|
// the PIDs from except. If no processes found, the error returned.
|
||||||
|
func PIDByCommand(command string, except ...int) (pid int, err error) {
|
||||||
|
// Don't use -C flag here since it's a feature of linux's ps
|
||||||
|
// implementation. Use POSIX-compatible flags instead.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/3457.
|
||||||
|
cmd := exec.Command("ps", "-A", "-o", "pid=", "-o", "comm=")
|
||||||
|
|
||||||
|
var stdout io.ReadCloser
|
||||||
|
if stdout, err = cmd.StdoutPipe(); err != nil {
|
||||||
|
return 0, fmt.Errorf("getting the command's stdout pipe: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cmd.Start(); err != nil {
|
||||||
|
return 0, fmt.Errorf("start command executing: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var instNum int
|
||||||
|
pid, instNum, err = parsePSOutput(stdout, command, except...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cmd.Wait(); err != nil {
|
||||||
|
return 0, fmt.Errorf("executing the command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch instNum {
|
||||||
|
case 0:
|
||||||
|
// TODO(e.burkov): Use constant error.
|
||||||
|
return 0, fmt.Errorf("no %s instances found", command)
|
||||||
|
case 1:
|
||||||
|
// Go on.
|
||||||
|
default:
|
||||||
|
log.Info("warning: %d %s instances found", instNum, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
if code := cmd.ProcessState.ExitCode(); code != 0 {
|
||||||
|
return 0, fmt.Errorf("ps finished with code %d", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePSOutput scans the output of ps searching the largest PID of the process
|
||||||
|
// associated with cmdName ignoring PIDs from ignore. Valid r's line shoud be
|
||||||
|
// like:
|
||||||
|
//
|
||||||
|
// 123 ./example-cmd
|
||||||
|
// 1230 some/base/path/example-cmd
|
||||||
|
// 3210 example-cmd
|
||||||
|
//
|
||||||
|
func parsePSOutput(r io.Reader, cmdName string, ignore ...int) (largest, instNum int, err error) {
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
ScanLoop:
|
||||||
|
for s.Scan() {
|
||||||
|
fields := strings.Fields(s.Text())
|
||||||
|
if len(fields) != 2 || path.Base(fields[1]) != cmdName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cur, aerr := strconv.Atoi(fields[0])
|
||||||
|
if aerr != nil || cur < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pid := range ignore {
|
||||||
|
if cur == pid {
|
||||||
|
continue ScanLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instNum++
|
||||||
|
if cur > largest {
|
||||||
|
largest = cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = s.Err(); err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("scanning stdout: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return largest, instNum, nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsOpenWrt returns true if host OS is OpenWrt.
|
// IsOpenWrt returns true if host OS is OpenWrt.
|
||||||
func IsOpenWrt() (ok bool) {
|
func IsOpenWrt() (ok bool) {
|
||||||
return isOpenWrt()
|
return isOpenWrt()
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ func haveAdminRights() (bool, error) {
|
|||||||
return os.Getuid() == 0, nil
|
return os.Getuid() == 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendProcessSignal(pid int, sig syscall.Signal) error {
|
|
||||||
return syscall.Kill(pid, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isOpenWrt() (ok bool) {
|
func isOpenWrt() (ok bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ func haveAdminRights() (bool, error) {
|
|||||||
return os.Getuid() == 0, nil
|
return os.Getuid() == 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendProcessSignal(pid int, sig syscall.Signal) error {
|
|
||||||
return syscall.Kill(pid, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isOpenWrt() (ok bool) {
|
func isOpenWrt() (ok bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ func haveAdminRights() (bool, error) {
|
|||||||
return os.Getuid() == 0, nil
|
return os.Getuid() == 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendProcessSignal(pid int, sig syscall.Signal) error {
|
|
||||||
return syscall.Kill(pid, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isOpenWrt() (ok bool) {
|
func isOpenWrt() (ok bool) {
|
||||||
var err error
|
var err error
|
||||||
ok, err = FileWalker(func(r io.Reader) (_ []string, cont bool, err error) {
|
ok, err = FileWalker(func(r io.Reader) (_ []string, cont bool, err error) {
|
||||||
|
|||||||
98
internal/aghos/os_test.go
Normal file
98
internal/aghos/os_test.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package aghos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLargestLabeled(t *testing.T) {
|
||||||
|
const (
|
||||||
|
comm = `command-name`
|
||||||
|
nl = "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
wantPID int
|
||||||
|
wantInstNum int
|
||||||
|
}{{
|
||||||
|
name: "success",
|
||||||
|
data: []byte(nl +
|
||||||
|
` 123 not-a-` + comm + nl +
|
||||||
|
` 321 ` + comm + nl,
|
||||||
|
),
|
||||||
|
wantPID: 321,
|
||||||
|
wantInstNum: 1,
|
||||||
|
}, {
|
||||||
|
name: "several",
|
||||||
|
data: []byte(nl +
|
||||||
|
`1 ` + comm + nl +
|
||||||
|
`5 /` + comm + nl +
|
||||||
|
`2 /some/path/` + comm + nl +
|
||||||
|
`4 ./` + comm + nl +
|
||||||
|
`3 ` + comm + nl +
|
||||||
|
`10 .` + comm + nl,
|
||||||
|
),
|
||||||
|
wantPID: 5,
|
||||||
|
wantInstNum: 5,
|
||||||
|
}, {
|
||||||
|
name: "no_any",
|
||||||
|
data: []byte(nl +
|
||||||
|
`1 ` + `not-a-` + comm + nl +
|
||||||
|
`2 ` + `not-a-` + comm + nl +
|
||||||
|
`3 ` + `not-a-` + comm + nl,
|
||||||
|
),
|
||||||
|
wantPID: 0,
|
||||||
|
wantInstNum: 0,
|
||||||
|
}, {
|
||||||
|
name: "weird_input",
|
||||||
|
data: []byte(nl +
|
||||||
|
`abc ` + comm + nl +
|
||||||
|
`-1 ` + comm + nl,
|
||||||
|
),
|
||||||
|
wantPID: 0,
|
||||||
|
wantInstNum: 0,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
r := bytes.NewReader(tc.data)
|
||||||
|
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
pid, instNum, err := parsePSOutput(r, comm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.wantPID, pid)
|
||||||
|
assert.Equal(t, tc.wantInstNum, instNum)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("scanner_fail", func(t *testing.T) {
|
||||||
|
lr, err := aghio.LimitReader(bytes.NewReader([]byte{1, 2, 3}), 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
target := &aghio.LimitReachedError{}
|
||||||
|
_, _, err = parsePSOutput(lr, "")
|
||||||
|
require.ErrorAs(t, err, &target)
|
||||||
|
|
||||||
|
assert.EqualValues(t, 0, target.Limit)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ignore", func(t *testing.T) {
|
||||||
|
r := bytes.NewReader([]byte(nl +
|
||||||
|
`1 ` + comm + nl +
|
||||||
|
`2 ` + comm + nl +
|
||||||
|
`3` + comm + nl,
|
||||||
|
))
|
||||||
|
|
||||||
|
pid, instances, err := parsePSOutput(r, comm, 1, 3)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 2, pid)
|
||||||
|
assert.Equal(t, 1, instances)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -4,8 +4,6 @@
|
|||||||
package aghos
|
package aghos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,10 +32,6 @@ func haveAdminRights() (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendProcessSignal(pid int, sig syscall.Signal) error {
|
|
||||||
return Unsupported("kill")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isOpenWrt() (ok bool) {
|
func isOpenWrt() (ok bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ func (u *TestBlockUpstream) RequestsCount() int {
|
|||||||
// TestErrUpstream implements upstream.Upstream interface for replacing real
|
// TestErrUpstream implements upstream.Upstream interface for replacing real
|
||||||
// upstream in tests.
|
// upstream in tests.
|
||||||
type TestErrUpstream struct {
|
type TestErrUpstream struct {
|
||||||
// The error returned by Exchange may be unwraped to the Err.
|
// The error returned by Exchange may be unwrapped to the Err.
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
package home
|
// Package aghtime defines some types for convenient work with time values.
|
||||||
|
package aghtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
@@ -23,22 +24,30 @@ type Duration struct {
|
|||||||
//
|
//
|
||||||
func (d Duration) String() (str string) {
|
func (d Duration) String() (str string) {
|
||||||
str = d.Duration.String()
|
str = d.Duration.String()
|
||||||
secs := d.Seconds()
|
|
||||||
var secsInt int
|
|
||||||
if secsInt = int(secs); float64(secsInt) != secs || secsInt%60 != 0 {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tailMin = len(`0s`)
|
tailMin = len(`0s`)
|
||||||
tailMinSec = len(`0m0s`)
|
tailMinSec = len(`0m0s`)
|
||||||
|
|
||||||
|
secsInHour = time.Hour / time.Second
|
||||||
|
minsInHour = time.Hour / time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
if (secsInt%3600)/60 != 0 {
|
switch rounded := d.Duration / time.Second; {
|
||||||
return str[:len(str)-tailMin]
|
case
|
||||||
}
|
rounded == 0,
|
||||||
|
rounded*time.Second != d.Duration,
|
||||||
|
rounded%60 != 0:
|
||||||
|
// Return the uncut value if it's either equal to zero or has
|
||||||
|
// fractions of a second or even whole seconds in it.
|
||||||
|
return str
|
||||||
|
|
||||||
return str[:len(str)-tailMinSec]
|
case (rounded%secsInHour)/minsInHour != 0:
|
||||||
|
return str[:len(str)-tailMin]
|
||||||
|
|
||||||
|
default:
|
||||||
|
return str[:len(str)-tailMinSec]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalText implements the encoding.TextMarshaler interface for Duration.
|
// MarshalText implements the encoding.TextMarshaler interface for Duration.
|
||||||
@@ -51,7 +60,7 @@ func (d Duration) MarshalText() (text []byte, err error) {
|
|||||||
//
|
//
|
||||||
// TODO(e.burkov): Make it able to parse larger units like days.
|
// TODO(e.burkov): Make it able to parse larger units like days.
|
||||||
func (d *Duration) UnmarshalText(b []byte) (err error) {
|
func (d *Duration) UnmarshalText(b []byte) (err error) {
|
||||||
defer func() { err = errors.Annotate(err, "unmarshalling duration: %w") }()
|
defer func() { err = errors.Annotate(err, "unmarshaling duration: %w") }()
|
||||||
|
|
||||||
d.Duration, err = time.ParseDuration(string(b))
|
d.Duration, err = time.ParseDuration(string(b))
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package home
|
package aghtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -46,6 +46,9 @@ func TestDuration_String(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "1m1.001s",
|
name: "1m1.001s",
|
||||||
val: time.Minute + time.Second + time.Millisecond,
|
val: time.Minute + time.Second + time.Millisecond,
|
||||||
|
}, {
|
||||||
|
name: "0s",
|
||||||
|
val: 0,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -57,7 +60,7 @@ func TestDuration_String(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// durationEncodingTester is a helper struct to simplify testing different
|
// durationEncodingTester is a helper struct to simplify testing different
|
||||||
// Duration marshalling and unmarshalling cases.
|
// Duration marshalling and unmarshaling cases.
|
||||||
type durationEncodingTester struct {
|
type durationEncodingTester struct {
|
||||||
PtrMap map[string]*Duration `json:"ptr_map" yaml:"ptr_map"`
|
PtrMap map[string]*Duration `json:"ptr_map" yaml:"ptr_map"`
|
||||||
PtrSlice []*Duration `json:"ptr_slice" yaml:"ptr_slice"`
|
PtrSlice []*Duration `json:"ptr_slice" yaml:"ptr_slice"`
|
||||||
@@ -101,7 +104,7 @@ const (
|
|||||||
// Duration.
|
// Duration.
|
||||||
const defaultTestDur = time.Millisecond
|
const defaultTestDur = time.Millisecond
|
||||||
|
|
||||||
// checkFields verifies m's fields. It expects the m to be unmarshalled from
|
// checkFields verifies m's fields. It expects the m to be unmarshaled from
|
||||||
// one of the constant strings above.
|
// one of the constant strings above.
|
||||||
func (m *durationEncodingTester) checkFields(t *testing.T, d Duration) {
|
func (m *durationEncodingTester) checkFields(t *testing.T, d Duration) {
|
||||||
t.Run("pointers_map", func(t *testing.T) {
|
t.Run("pointers_map", func(t *testing.T) {
|
||||||
37
internal/dhcpd/broadcast_bsd.go
Normal file
37
internal/dhcpd/broadcast_bsd.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//go:build freebsd || openbsd
|
||||||
|
// +build freebsd openbsd
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// broadcast sends resp to the broadcast address specific for network interface.
|
||||||
|
func (s *v4Server) broadcast(peer net.Addr, conn net.PacketConn, resp *dhcpv4.DHCPv4) {
|
||||||
|
// peer is expected to be of type *net.UDPConn as the server4.NewServer
|
||||||
|
// initializes it.
|
||||||
|
udpPeer, ok := peer.(*net.UDPAddr)
|
||||||
|
if !ok {
|
||||||
|
log.Error("dhcpv4: peer is of unexpected type %T", peer)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Despite the fact that server4.NewIPv4UDPConn explicitly sets socket
|
||||||
|
// options to allow broadcasting, it also binds the connection to a
|
||||||
|
// specific interface. On FreeBSD and OpenBSD conn.WriteTo causes
|
||||||
|
// errors while writing to the addresses that belong to another
|
||||||
|
// interface. So, use the broadcast address specific for the binded
|
||||||
|
// interface.
|
||||||
|
udpPeer.IP = s.conf.broadcastIP
|
||||||
|
|
||||||
|
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
||||||
|
|
||||||
|
if _, err := conn.WriteTo(resp.ToBytes(), peer); err != nil {
|
||||||
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
87
internal/dhcpd/broadcast_bsd_test.go
Normal file
87
internal/dhcpd/broadcast_bsd_test.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
//go:build freebsd || openbsd
|
||||||
|
// +build freebsd openbsd
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestV4Server_Send_broadcast(t *testing.T) {
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
var peer *net.UDPAddr
|
||||||
|
|
||||||
|
conn := &fakePacketConn{
|
||||||
|
writeTo: func(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
udpPeer, ok := addr.(*net.UDPAddr)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
peer = cloneUDPAddr(udpPeer)
|
||||||
|
|
||||||
|
n, err = b.Write(p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultPeer := &net.UDPAddr{
|
||||||
|
IP: net.IP{1, 2, 3, 4},
|
||||||
|
// Use neither client nor server port.
|
||||||
|
Port: 1234,
|
||||||
|
}
|
||||||
|
s := &v4Server{
|
||||||
|
conf: V4ServerConf{
|
||||||
|
broadcastIP: net.IP{1, 2, 3, 255},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
req *dhcpv4.DHCPv4
|
||||||
|
resp *dhcpv4.DHCPv4
|
||||||
|
}{{
|
||||||
|
name: "nak",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
|
},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "fully_unspecified",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
|
ClientIPAddr: netutil.IPv4Zero(),
|
||||||
|
},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
s.send(cloneUDPAddr(defaultPeer), conn, tc.req, tc.resp)
|
||||||
|
assert.EqualValues(t, tc.resp.ToBytes(), b.Bytes())
|
||||||
|
assert.Equal(t, &net.UDPAddr{
|
||||||
|
IP: s.conf.broadcastIP,
|
||||||
|
Port: defaultPeer.Port,
|
||||||
|
Zone: defaultPeer.Zone,
|
||||||
|
}, peer)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
peer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
51
internal/dhcpd/broadcast_others.go
Normal file
51
internal/dhcpd/broadcast_others.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//go:build aix || darwin || dragonfly || linux || netbsd || solaris
|
||||||
|
// +build aix darwin dragonfly linux netbsd solaris
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// broadcast sends resp to the broadcast address specific for network interface.
|
||||||
|
func (s *v4Server) broadcast(peer net.Addr, conn net.PacketConn, resp *dhcpv4.DHCPv4) {
|
||||||
|
respData := resp.ToBytes()
|
||||||
|
|
||||||
|
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
||||||
|
|
||||||
|
// This write to 0xffffffff reverts some behavior changes made in
|
||||||
|
// https://github.com/AdguardTeam/AdGuardHome/issues/3289. The DHCP
|
||||||
|
// server should broadcast the message to 0xffffffff but it's
|
||||||
|
// inconsistent with the actual mental model of DHCP implementation
|
||||||
|
// which requires the network interface selection to bind to.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/3480 and
|
||||||
|
// https://github.com/AdguardTeam/AdGuardHome/issues/3366.
|
||||||
|
//
|
||||||
|
// See also https://github.com/AdguardTeam/AdGuardHome/issues/3539.
|
||||||
|
if _, err := conn.WriteTo(respData, peer); err != nil {
|
||||||
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// peer is expected to be of type *net.UDPConn as the server4.NewServer
|
||||||
|
// initializes it.
|
||||||
|
udpPeer, ok := peer.(*net.UDPAddr)
|
||||||
|
if !ok {
|
||||||
|
log.Error("dhcpv4: peer is of unexpected type %T", peer)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast the message one more time using the interface-specific
|
||||||
|
// broadcast address.
|
||||||
|
udpPeer.IP = s.conf.broadcastIP
|
||||||
|
|
||||||
|
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
||||||
|
|
||||||
|
if _, err := conn.WriteTo(respData, peer); err != nil {
|
||||||
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
96
internal/dhcpd/broadcast_others_test.go
Normal file
96
internal/dhcpd/broadcast_others_test.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
//go:build aix || darwin || dragonfly || linux || netbsd || solaris
|
||||||
|
// +build aix darwin dragonfly linux netbsd solaris
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestV4Server_Send_broadcast(t *testing.T) {
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
var peers []*net.UDPAddr
|
||||||
|
|
||||||
|
conn := &fakePacketConn{
|
||||||
|
writeTo: func(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
udpPeer, ok := addr.(*net.UDPAddr)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
peers = append(peers, cloneUDPAddr(udpPeer))
|
||||||
|
|
||||||
|
n, err = b.Write(p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultPeer := &net.UDPAddr{
|
||||||
|
IP: net.IP{1, 2, 3, 4},
|
||||||
|
// Use neither client nor server port.
|
||||||
|
Port: 1234,
|
||||||
|
}
|
||||||
|
s := &v4Server{
|
||||||
|
conf: V4ServerConf{
|
||||||
|
broadcastIP: net.IP{1, 2, 3, 255},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
req *dhcpv4.DHCPv4
|
||||||
|
resp *dhcpv4.DHCPv4
|
||||||
|
}{{
|
||||||
|
name: "nak",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
|
},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "fully_unspecified",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
|
ClientIPAddr: netutil.IPv4Zero(),
|
||||||
|
},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
s.send(cloneUDPAddr(defaultPeer), conn, tc.req, tc.resp)
|
||||||
|
|
||||||
|
// The same response is written twice.
|
||||||
|
respData := tc.resp.ToBytes()
|
||||||
|
assert.EqualValues(t, append(respData, respData...), b.Bytes())
|
||||||
|
|
||||||
|
require.Len(t, peers, 2)
|
||||||
|
|
||||||
|
assert.Equal(t, &net.UDPAddr{
|
||||||
|
IP: defaultPeer.IP,
|
||||||
|
Port: defaultPeer.Port,
|
||||||
|
}, peers[0])
|
||||||
|
assert.Equal(t, &net.UDPAddr{
|
||||||
|
IP: s.conf.broadcastIP,
|
||||||
|
Port: defaultPeer.Port,
|
||||||
|
}, peers[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
peers = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -136,3 +137,12 @@ func TestNormalizeLeases(t *testing.T) {
|
|||||||
assert.Equal(t, leases[1].HWAddr, staticLeases[1].HWAddr)
|
assert.Equal(t, leases[1].HWAddr, staticLeases[1].HWAddr)
|
||||||
assert.Equal(t, leases[2].HWAddr, dynLeases[1].HWAddr)
|
assert.Equal(t, leases[2].HWAddr, dynLeases[1].HWAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cloneUDPAddr returns a deep copy of a.
|
||||||
|
func cloneUDPAddr(a *net.UDPAddr) (copy *net.UDPAddr) {
|
||||||
|
return &net.UDPAddr{
|
||||||
|
IP: netutil.CloneIP(a.IP),
|
||||||
|
Port: a.Port,
|
||||||
|
Zone: a.Zone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
package dhcpd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// hexDHCPOptionParserHandler parses a DHCP option as a hex-encoded string.
|
|
||||||
// For example:
|
|
||||||
//
|
|
||||||
// 252 hex 736f636b733a2f2f70726f78792e6578616d706c652e6f7267
|
|
||||||
//
|
|
||||||
func hexDHCPOptionParserHandler(s string) (data []byte, err error) {
|
|
||||||
data, err = hex.DecodeString(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("decoding hex: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ipDHCPOptionParserHandler parses a DHCP option as a single IP address.
|
|
||||||
// For example:
|
|
||||||
//
|
|
||||||
// 6 ip 192.168.1.1
|
|
||||||
//
|
|
||||||
func ipDHCPOptionParserHandler(s string) (data []byte, err error) {
|
|
||||||
ip := net.ParseIP(s)
|
|
||||||
if ip == nil {
|
|
||||||
return nil, errors.Error("invalid ip")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Most DHCP options require IPv4, so do not put the 16-byte
|
|
||||||
// version if we can. Otherwise, the clients will receive weird
|
|
||||||
// data that looks like four IPv4 addresses.
|
|
||||||
//
|
|
||||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2688.
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
data = ip4
|
|
||||||
} else {
|
|
||||||
data = ip
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// textDHCPOptionParserHandler parses a DHCP option as a simple UTF-8 encoded
|
|
||||||
// text. For example:
|
|
||||||
//
|
|
||||||
// 252 text http://192.168.1.1/wpad.dat
|
|
||||||
//
|
|
||||||
func ipsDHCPOptionParserHandler(s string) (data []byte, err error) {
|
|
||||||
ipStrs := strings.Split(s, ",")
|
|
||||||
for i, ipStr := range ipStrs {
|
|
||||||
var ipData []byte
|
|
||||||
ipData, err = ipDHCPOptionParserHandler(ipStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing ip at index %d: %w", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data = append(data, ipData...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ipsDHCPOptionParserHandler parses a DHCP option as a comma-separates list of
|
|
||||||
// IP addresses. For example:
|
|
||||||
//
|
|
||||||
// 6 ips 192.168.1.1,192.168.1.2
|
|
||||||
//
|
|
||||||
func textDHCPOptionParserHandler(s string) (data []byte, err error) {
|
|
||||||
return []byte(s), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dhcpOptionParserHandler is a parser for a single dhcp option type.
|
|
||||||
type dhcpOptionParserHandler func(s string) (data []byte, err error)
|
|
||||||
|
|
||||||
// dhcpOptionParser parses DHCP options.
|
|
||||||
type dhcpOptionParser struct {
|
|
||||||
handlers map[string]dhcpOptionParserHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// newDHCPOptionParser returns a new dhcpOptionParser.
|
|
||||||
func newDHCPOptionParser() (p *dhcpOptionParser) {
|
|
||||||
return &dhcpOptionParser{
|
|
||||||
handlers: map[string]dhcpOptionParserHandler{
|
|
||||||
"hex": hexDHCPOptionParserHandler,
|
|
||||||
"ip": ipDHCPOptionParserHandler,
|
|
||||||
"ips": ipsDHCPOptionParserHandler,
|
|
||||||
"text": textDHCPOptionParserHandler,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse parses an option. See the handlers' documentation for more info.
|
|
||||||
func (p *dhcpOptionParser) parse(s string) (code uint8, data []byte, err error) {
|
|
||||||
defer func() { err = errors.Annotate(err, "invalid option string %q: %w", s) }()
|
|
||||||
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
parts := strings.SplitN(s, " ", 3)
|
|
||||||
if len(parts) < 3 {
|
|
||||||
return 0, nil, errors.Error("need at least three fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
codeStr := parts[0]
|
|
||||||
typ := parts[1]
|
|
||||||
val := parts[2]
|
|
||||||
|
|
||||||
var code64 uint64
|
|
||||||
code64, err = strconv.ParseUint(codeStr, 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, fmt.Errorf("parsing option code: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
code = uint8(code64)
|
|
||||||
|
|
||||||
h, ok := p.handlers[typ]
|
|
||||||
if !ok {
|
|
||||||
return 0, nil, fmt.Errorf("unknown option type %q", typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err = h(val)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return code, data, nil
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package dhcpd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDHCPOptionParser(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
in string
|
|
||||||
wantErrMsg string
|
|
||||||
wantData []byte
|
|
||||||
wantCode uint8
|
|
||||||
}{{
|
|
||||||
name: "hex_success",
|
|
||||||
in: "6 hex c0a80101c0a80102",
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantData: []byte{0xC0, 0xA8, 0x01, 0x01, 0xC0, 0xA8, 0x01, 0x02},
|
|
||||||
wantCode: 6,
|
|
||||||
}, {
|
|
||||||
name: "ip_success",
|
|
||||||
in: "6 ip 1.2.3.4",
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantData: []byte{0x01, 0x02, 0x03, 0x04},
|
|
||||||
wantCode: 6,
|
|
||||||
}, {
|
|
||||||
name: "ip_success_v6",
|
|
||||||
in: "6 ip ::1234",
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantData: []byte{
|
|
||||||
0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x12, 0x34,
|
|
||||||
},
|
|
||||||
wantCode: 6,
|
|
||||||
}, {
|
|
||||||
name: "ips_success",
|
|
||||||
in: "6 ips 192.168.1.1,192.168.1.2",
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantData: []byte{0xC0, 0xA8, 0x01, 0x01, 0xC0, 0xA8, 0x01, 0x02},
|
|
||||||
wantCode: 6,
|
|
||||||
}, {
|
|
||||||
name: "text_success",
|
|
||||||
in: "252 text http://192.168.1.1/",
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantData: []byte("http://192.168.1.1/"),
|
|
||||||
wantCode: 252,
|
|
||||||
}, {
|
|
||||||
name: "bad_parts",
|
|
||||||
in: "6 ip",
|
|
||||||
wantErrMsg: `invalid option string "6 ip": need at least three fields`,
|
|
||||||
wantCode: 0,
|
|
||||||
wantData: nil,
|
|
||||||
}, {
|
|
||||||
name: "bad_code",
|
|
||||||
in: "256 ip 1.1.1.1",
|
|
||||||
wantErrMsg: `invalid option string "256 ip 1.1.1.1": parsing option code: ` +
|
|
||||||
`strconv.ParseUint: parsing "256": value out of range`,
|
|
||||||
wantCode: 0,
|
|
||||||
wantData: nil,
|
|
||||||
}, {
|
|
||||||
name: "bad_type",
|
|
||||||
in: "6 bad 1.1.1.1",
|
|
||||||
wantErrMsg: `invalid option string "6 bad 1.1.1.1": unknown option type "bad"`,
|
|
||||||
wantCode: 0,
|
|
||||||
wantData: nil,
|
|
||||||
}, {
|
|
||||||
name: "hex_error",
|
|
||||||
in: "6 hex ZZZ",
|
|
||||||
wantErrMsg: `invalid option string "6 hex ZZZ": decoding hex: ` +
|
|
||||||
`encoding/hex: invalid byte: U+005A 'Z'`,
|
|
||||||
wantData: nil,
|
|
||||||
wantCode: 0,
|
|
||||||
}, {
|
|
||||||
name: "ip_error",
|
|
||||||
in: "6 ip 1.2.3.x",
|
|
||||||
wantErrMsg: `invalid option string "6 ip 1.2.3.x": invalid ip`,
|
|
||||||
wantData: nil,
|
|
||||||
wantCode: 0,
|
|
||||||
}, {
|
|
||||||
name: "ips_error",
|
|
||||||
in: "6 ips 192.168.1.1,192.168.1.x",
|
|
||||||
wantErrMsg: `invalid option string "6 ips 192.168.1.1,192.168.1.x": ` +
|
|
||||||
`parsing ip at index 1: invalid ip`,
|
|
||||||
wantData: nil,
|
|
||||||
wantCode: 0,
|
|
||||||
}}
|
|
||||||
|
|
||||||
p := newDHCPOptionParser()
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
code, data, err := p.parse(tc.in)
|
|
||||||
if tc.wantErrMsg == "" {
|
|
||||||
assert.Nil(t, err)
|
|
||||||
} else {
|
|
||||||
require.NotNil(t, err)
|
|
||||||
assert.Equal(t, tc.wantErrMsg, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, tc.wantCode, code)
|
|
||||||
assert.Equal(t, tc.wantData, data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
192
internal/dhcpd/options_unix.go
Normal file
192
internal/dhcpd/options_unix.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The aliases for DHCP option types available for explicit declaration.
|
||||||
|
const (
|
||||||
|
hexTyp = "hex"
|
||||||
|
ipTyp = "ip"
|
||||||
|
ipsTyp = "ips"
|
||||||
|
textTyp = "text"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseDHCPOptionHex parses a DHCP option as a hex-encoded string. For
|
||||||
|
// example:
|
||||||
|
//
|
||||||
|
// 252 hex 736f636b733a2f2f70726f78792e6578616d706c652e6f7267
|
||||||
|
//
|
||||||
|
func parseDHCPOptionHex(s string) (val dhcpv4.OptionValue, err error) {
|
||||||
|
var data []byte
|
||||||
|
data, err = hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding hex: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dhcpv4.OptionGeneric{Data: data}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDHCPOptionIP parses a DHCP option as a single IP address. For example:
|
||||||
|
//
|
||||||
|
// 6 ip 192.168.1.1
|
||||||
|
//
|
||||||
|
func parseDHCPOptionIP(s string) (val dhcpv4.OptionValue, err error) {
|
||||||
|
var ip net.IP
|
||||||
|
// All DHCPv4 options require IPv4, so don't put the 16-byte version.
|
||||||
|
// Otherwise, the clients will receive weird data that looks like four
|
||||||
|
// IPv4 addresses.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2688.
|
||||||
|
if ip, err = netutil.ParseIPv4(s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dhcpv4.IP(ip), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDHCPOptionIPs parses a DHCP option as a comma-separates list of IP
|
||||||
|
// addresses. For example:
|
||||||
|
//
|
||||||
|
// 6 ips 192.168.1.1,192.168.1.2
|
||||||
|
//
|
||||||
|
func parseDHCPOptionIPs(s string) (val dhcpv4.OptionValue, err error) {
|
||||||
|
var ips dhcpv4.IPs
|
||||||
|
var ip net.IP
|
||||||
|
for i, ipStr := range strings.Split(s, ",") {
|
||||||
|
// See notes in the ipDHCPOptionParserHandler.
|
||||||
|
if ip, err = netutil.ParseIPv4(ipStr); err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing ip at index %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDHCPOptionText parses a DHCP option as a simple UTF-8 encoded
|
||||||
|
// text. For example:
|
||||||
|
//
|
||||||
|
// 252 text http://192.168.1.1/wpad.dat
|
||||||
|
//
|
||||||
|
func parseDHCPOptionText(s string) (val dhcpv4.OptionValue) {
|
||||||
|
return dhcpv4.OptionGeneric{Data: []byte(s)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDHCPOption parses an option. See the documentation of parseDHCPOption*
|
||||||
|
// for more info.
|
||||||
|
func parseDHCPOption(s string) (opt dhcpv4.Option, err error) {
|
||||||
|
defer func() { err = errors.Annotate(err, "invalid option string %q: %w", s) }()
|
||||||
|
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
parts := strings.SplitN(s, " ", 3)
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return opt, errors.Error("need at least three fields")
|
||||||
|
}
|
||||||
|
|
||||||
|
var code64 uint64
|
||||||
|
code64, err = strconv.ParseUint(parts[0], 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
return opt, fmt.Errorf("parsing option code: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var optVal dhcpv4.OptionValue
|
||||||
|
switch typ, val := parts[1], parts[2]; typ {
|
||||||
|
case hexTyp:
|
||||||
|
optVal, err = parseDHCPOptionHex(val)
|
||||||
|
case ipTyp:
|
||||||
|
optVal, err = parseDHCPOptionIP(val)
|
||||||
|
case ipsTyp:
|
||||||
|
optVal, err = parseDHCPOptionIPs(val)
|
||||||
|
case textTyp:
|
||||||
|
optVal = parseDHCPOptionText(val)
|
||||||
|
default:
|
||||||
|
return opt, fmt.Errorf("unknown option type %q", typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return opt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dhcpv4.Option{
|
||||||
|
Code: dhcpv4.GenericOptionCode(code64),
|
||||||
|
Value: optVal,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareOptions builds the set of DHCP options according to host requirements
|
||||||
|
// document and values from conf.
|
||||||
|
func prepareOptions(conf V4ServerConf) (opts dhcpv4.Options) {
|
||||||
|
opts = dhcpv4.Options{
|
||||||
|
// Set default values for host configuration parameters listed
|
||||||
|
// in Appendix A of RFC-2131. Those parameters, if requested by
|
||||||
|
// client, should be returned with values defined by Host
|
||||||
|
// Requirements Document.
|
||||||
|
//
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc2131#appendix-A.
|
||||||
|
//
|
||||||
|
// See also https://datatracker.ietf.org/doc/html/rfc1122,
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc1123, and
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc2132.
|
||||||
|
|
||||||
|
// IP-Layer Per Host
|
||||||
|
|
||||||
|
dhcpv4.OptionNonLocalSourceRouting.Code(): []byte{0},
|
||||||
|
// Set the current recommended default time to live for the
|
||||||
|
// Internet Protocol which is 64, see
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc1700.
|
||||||
|
dhcpv4.OptionDefaultIPTTL.Code(): []byte{64},
|
||||||
|
|
||||||
|
// IP-Layer Per Interface
|
||||||
|
|
||||||
|
dhcpv4.OptionPerformMaskDiscovery.Code(): []byte{0},
|
||||||
|
dhcpv4.OptionMaskSupplier.Code(): []byte{0},
|
||||||
|
dhcpv4.OptionPerformRouterDiscovery.Code(): []byte{1},
|
||||||
|
// The all-routers address is preferred wherever possible, see
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
|
||||||
|
dhcpv4.OptionRouterSolicitationAddress.Code(): net.IPv4allrouter.To4(),
|
||||||
|
dhcpv4.OptionBroadcastAddress.Code(): net.IPv4bcast.To4(),
|
||||||
|
|
||||||
|
// Link-Layer Per Interface
|
||||||
|
|
||||||
|
dhcpv4.OptionTrailerEncapsulation.Code(): []byte{0},
|
||||||
|
dhcpv4.OptionEthernetEncapsulation.Code(): []byte{0},
|
||||||
|
|
||||||
|
// TCP Per Host
|
||||||
|
|
||||||
|
dhcpv4.OptionTCPKeepaliveInterval.Code(): dhcpv4.Duration(0).ToBytes(),
|
||||||
|
dhcpv4.OptionTCPKeepaliveGarbage.Code(): []byte{0},
|
||||||
|
|
||||||
|
// Values From Configuration
|
||||||
|
|
||||||
|
dhcpv4.OptionRouter.Code(): netutil.CloneIP(conf.subnet.IP),
|
||||||
|
dhcpv4.OptionSubnetMask.Code(): dhcpv4.IPMask(conf.subnet.Mask).ToBytes(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set values for explicitly configured options.
|
||||||
|
for i, o := range conf.Options {
|
||||||
|
opt, err := parseDHCPOption(o)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("dhcpv4: bad option string at index %d: %s", i, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.Update(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
177
internal/dhcpd/options_unix_test.go
Normal file
177
internal/dhcpd/options_unix_test.go
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseOpt(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
in string
|
||||||
|
wantErrMsg string
|
||||||
|
wantOpt dhcpv4.Option
|
||||||
|
}{{
|
||||||
|
name: "hex_success",
|
||||||
|
in: "6 hex c0a80101c0a80102",
|
||||||
|
wantErrMsg: "",
|
||||||
|
wantOpt: dhcpv4.OptDNS(
|
||||||
|
net.IP{0xC0, 0xA8, 0x01, 0x01},
|
||||||
|
net.IP{0xC0, 0xA8, 0x01, 0x02},
|
||||||
|
),
|
||||||
|
}, {
|
||||||
|
name: "ip_success",
|
||||||
|
in: "6 ip 1.2.3.4",
|
||||||
|
wantErrMsg: "",
|
||||||
|
wantOpt: dhcpv4.OptDNS(
|
||||||
|
net.IP{0x01, 0x02, 0x03, 0x04},
|
||||||
|
),
|
||||||
|
}, {
|
||||||
|
name: "ip_fail_v6",
|
||||||
|
in: "6 ip ::1234",
|
||||||
|
wantErrMsg: "invalid option string \"6 ip ::1234\": bad ipv4 address \"::1234\"",
|
||||||
|
wantOpt: dhcpv4.Option{},
|
||||||
|
}, {
|
||||||
|
name: "ips_success",
|
||||||
|
in: "6 ips 192.168.1.1,192.168.1.2",
|
||||||
|
wantErrMsg: "",
|
||||||
|
wantOpt: dhcpv4.OptDNS(
|
||||||
|
net.IP{0xC0, 0xA8, 0x01, 0x01},
|
||||||
|
net.IP{0xC0, 0xA8, 0x01, 0x02},
|
||||||
|
),
|
||||||
|
}, {
|
||||||
|
name: "text_success",
|
||||||
|
in: "252 text http://192.168.1.1/",
|
||||||
|
wantErrMsg: "",
|
||||||
|
wantOpt: dhcpv4.OptGeneric(
|
||||||
|
dhcpv4.GenericOptionCode(252),
|
||||||
|
[]byte("http://192.168.1.1/"),
|
||||||
|
),
|
||||||
|
}, {
|
||||||
|
name: "bad_parts",
|
||||||
|
in: "6 ip",
|
||||||
|
wantErrMsg: `invalid option string "6 ip": need at least three fields`,
|
||||||
|
wantOpt: dhcpv4.Option{},
|
||||||
|
}, {
|
||||||
|
name: "bad_code",
|
||||||
|
in: "256 ip 1.1.1.1",
|
||||||
|
wantErrMsg: `invalid option string "256 ip 1.1.1.1": parsing option code: ` +
|
||||||
|
`strconv.ParseUint: parsing "256": value out of range`,
|
||||||
|
wantOpt: dhcpv4.Option{},
|
||||||
|
}, {
|
||||||
|
name: "bad_type",
|
||||||
|
in: "6 bad 1.1.1.1",
|
||||||
|
wantErrMsg: `invalid option string "6 bad 1.1.1.1": unknown option type "bad"`,
|
||||||
|
wantOpt: dhcpv4.Option{},
|
||||||
|
}, {
|
||||||
|
name: "hex_error",
|
||||||
|
in: "6 hex ZZZ",
|
||||||
|
wantErrMsg: `invalid option string "6 hex ZZZ": decoding hex: ` +
|
||||||
|
`encoding/hex: invalid byte: U+005A 'Z'`,
|
||||||
|
wantOpt: dhcpv4.Option{},
|
||||||
|
}, {
|
||||||
|
name: "ip_error",
|
||||||
|
in: "6 ip 1.2.3.x",
|
||||||
|
wantErrMsg: "invalid option string \"6 ip 1.2.3.x\": bad ipv4 address \"1.2.3.x\"",
|
||||||
|
wantOpt: dhcpv4.Option{},
|
||||||
|
}, {
|
||||||
|
name: "ips_error",
|
||||||
|
in: "6 ips 192.168.1.1,192.168.1.x",
|
||||||
|
wantErrMsg: "invalid option string \"6 ips 192.168.1.1,192.168.1.x\": " +
|
||||||
|
"parsing ip at index 1: bad ipv4 address \"192.168.1.x\"",
|
||||||
|
wantOpt: dhcpv4.Option{},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
opt, err := parseDHCPOption(tc.in)
|
||||||
|
if tc.wantErrMsg != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.wantOpt.Code.Code(), opt.Code.Code())
|
||||||
|
assert.Equal(t, tc.wantOpt.Value.ToBytes(), opt.Value.ToBytes())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareOptions(t *testing.T) {
|
||||||
|
allDefault := dhcpv4.Options{
|
||||||
|
dhcpv4.OptionNonLocalSourceRouting.Code(): []byte{0},
|
||||||
|
dhcpv4.OptionDefaultIPTTL.Code(): []byte{64},
|
||||||
|
dhcpv4.OptionPerformMaskDiscovery.Code(): []byte{0},
|
||||||
|
dhcpv4.OptionMaskSupplier.Code(): []byte{0},
|
||||||
|
dhcpv4.OptionPerformRouterDiscovery.Code(): []byte{1},
|
||||||
|
dhcpv4.OptionRouterSolicitationAddress.Code(): []byte{224, 0, 0, 2},
|
||||||
|
dhcpv4.OptionBroadcastAddress.Code(): []byte{255, 255, 255, 255},
|
||||||
|
dhcpv4.OptionTrailerEncapsulation.Code(): []byte{0},
|
||||||
|
dhcpv4.OptionEthernetEncapsulation.Code(): []byte{0},
|
||||||
|
dhcpv4.OptionTCPKeepaliveInterval.Code(): []byte{0, 0, 0, 0},
|
||||||
|
dhcpv4.OptionTCPKeepaliveGarbage.Code(): []byte{0},
|
||||||
|
}
|
||||||
|
oneIP, otherIP := net.IP{1, 2, 3, 4}, net.IP{5, 6, 7, 8}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
opts []string
|
||||||
|
checks dhcpv4.Options
|
||||||
|
}{{
|
||||||
|
name: "all_default",
|
||||||
|
checks: allDefault,
|
||||||
|
}, {
|
||||||
|
name: "configured_ip",
|
||||||
|
opts: []string{
|
||||||
|
fmt.Sprintf("%d ip %s", dhcpv4.OptionBroadcastAddress, oneIP),
|
||||||
|
},
|
||||||
|
checks: dhcpv4.Options{
|
||||||
|
dhcpv4.OptionBroadcastAddress.Code(): oneIP,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "configured_ips",
|
||||||
|
opts: []string{
|
||||||
|
fmt.Sprintf("%d ips %s,%s", dhcpv4.OptionDomainNameServer, oneIP, otherIP),
|
||||||
|
},
|
||||||
|
checks: dhcpv4.Options{
|
||||||
|
dhcpv4.OptionDomainNameServer.Code(): append(oneIP, otherIP...),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "configured_bad",
|
||||||
|
opts: []string{
|
||||||
|
"20 hex",
|
||||||
|
"23 hex abc",
|
||||||
|
"32 ips 1,2,3,4",
|
||||||
|
"28 256.256.256.256",
|
||||||
|
},
|
||||||
|
checks: allDefault,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
opts := prepareOptions(V4ServerConf{
|
||||||
|
// Just to avoid nil pointer dereference.
|
||||||
|
subnet: &net.IPNet{},
|
||||||
|
Options: tc.opts,
|
||||||
|
})
|
||||||
|
for c, v := range tc.checks {
|
||||||
|
optVal := opts.Get(dhcpv4.GenericOptionCode(c))
|
||||||
|
require.NotNil(t, optVal)
|
||||||
|
|
||||||
|
assert.Len(t, optVal, len(v))
|
||||||
|
assert.Equal(t, v, optVal)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,8 +72,6 @@ type V4ServerConf struct {
|
|||||||
// gateway.
|
// gateway.
|
||||||
subnet *net.IPNet
|
subnet *net.IPNet
|
||||||
|
|
||||||
options []dhcpOption
|
|
||||||
|
|
||||||
// notify is a way to signal to other components that leases have
|
// notify is a way to signal to other components that leases have
|
||||||
// change. notify must be called outside of locked sections, since the
|
// change. notify must be called outside of locked sections, since the
|
||||||
// clients might want to get the new data.
|
// clients might want to get the new data.
|
||||||
@@ -104,8 +102,3 @@ type V6ServerConf struct {
|
|||||||
// Server calls this function when leases data changes
|
// Server calls this function when leases data changes
|
||||||
notify func(uint32)
|
notify func(uint32)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dhcpOption struct {
|
|
||||||
code uint8
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ type v4Server struct {
|
|||||||
|
|
||||||
// leasesLock protects leases, leaseHosts, and leasedOffsets.
|
// leasesLock protects leases, leaseHosts, and leasedOffsets.
|
||||||
leasesLock sync.Mutex
|
leasesLock sync.Mutex
|
||||||
|
|
||||||
|
// options holds predefined DHCP options to return to clients.
|
||||||
|
options dhcpv4.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteDiskConfig4 - write configuration
|
// WriteDiskConfig4 - write configuration
|
||||||
@@ -831,6 +834,9 @@ func (s *v4Server) processRelease(req, resp *dhcpv4.DHCPv4) (err error) {
|
|||||||
func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// Include server's identifier option since any reply should contain it.
|
||||||
|
//
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc2131#page-29.
|
||||||
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0]))
|
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0]))
|
||||||
|
|
||||||
// TODO(a.garipov): Refactor this into handlers.
|
// TODO(a.garipov): Refactor this into handlers.
|
||||||
@@ -873,17 +879,29 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if l != nil {
|
if l != nil {
|
||||||
resp.YourIPAddr = make([]byte, 4)
|
resp.YourIPAddr = netutil.CloneIP(l.IP)
|
||||||
copy(resp.YourIPAddr, l.IP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set IP address lease time for all DHCPOFFER messages and DHCPACK
|
||||||
|
// messages replied for DHCPREQUEST.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Inspect why this is always set to configured value.
|
||||||
resp.UpdateOption(dhcpv4.OptIPAddressLeaseTime(s.conf.leaseTime))
|
resp.UpdateOption(dhcpv4.OptIPAddressLeaseTime(s.conf.leaseTime))
|
||||||
resp.UpdateOption(dhcpv4.OptRouter(s.conf.subnet.IP))
|
|
||||||
resp.UpdateOption(dhcpv4.OptSubnetMask(s.conf.subnet.Mask))
|
|
||||||
resp.UpdateOption(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
|
||||||
|
|
||||||
for _, opt := range s.conf.options {
|
// Update values for each explicitly configured parameter requested by
|
||||||
resp.Options[opt.code] = opt.data
|
// client.
|
||||||
|
//
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.1.
|
||||||
|
requested := req.ParameterRequestList()
|
||||||
|
for _, code := range requested {
|
||||||
|
if configured := s.options; configured.Has(code) {
|
||||||
|
resp.UpdateOption(dhcpv4.OptGeneric(code, configured.Get(code)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update the value of Domain Name Server option separately from others
|
||||||
|
// since its value is set after server's creating.
|
||||||
|
if requested.Has(dhcpv4.OptionDomainNameServer) {
|
||||||
|
resp.UpdateOption(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
@@ -897,7 +915,8 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
|||||||
log.Debug("dhcpv4: received message: %s", req.Summary())
|
log.Debug("dhcpv4: received message: %s", req.Summary())
|
||||||
|
|
||||||
switch req.MessageType() {
|
switch req.MessageType() {
|
||||||
case dhcpv4.MessageTypeDiscover,
|
case
|
||||||
|
dhcpv4.MessageTypeDiscover,
|
||||||
dhcpv4.MessageTypeRequest,
|
dhcpv4.MessageTypeRequest,
|
||||||
dhcpv4.MessageTypeDecline,
|
dhcpv4.MessageTypeDecline,
|
||||||
dhcpv4.MessageTypeRelease:
|
dhcpv4.MessageTypeRelease:
|
||||||
@@ -928,28 +947,50 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
|||||||
resp.Options.Update(dhcpv4.OptMessageType(dhcpv4.MessageTypeNak))
|
resp.Options.Update(dhcpv4.OptMessageType(dhcpv4.MessageTypeNak))
|
||||||
}
|
}
|
||||||
|
|
||||||
// peer is expected to be of type *net.UDPConn as the server4.NewServer
|
s.send(peer, conn, req, resp)
|
||||||
// initializes it.
|
}
|
||||||
udpPeer, ok := peer.(*net.UDPAddr)
|
|
||||||
if !ok {
|
// send writes resp for peer to conn considering the req's parameters according
|
||||||
log.Error("dhcpv4: peer is of unexpected type %T", peer)
|
// to RFC-2131.
|
||||||
|
//
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.1.
|
||||||
|
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
|
||||||
|
var unicast bool
|
||||||
|
if giaddr := req.GatewayIPAddr; giaddr != nil && !giaddr.IsUnspecified() {
|
||||||
|
// Send any return messages to the server port on the BOOTP
|
||||||
|
// relay agent whose address appears in giaddr.
|
||||||
|
peer = &net.UDPAddr{
|
||||||
|
IP: giaddr,
|
||||||
|
Port: dhcpv4.ServerPort,
|
||||||
|
}
|
||||||
|
unicast = true
|
||||||
|
} else if mtype := resp.MessageType(); mtype == dhcpv4.MessageTypeNak {
|
||||||
|
// Broadcast any DHCPNAK messages to 0xffffffff.
|
||||||
|
} else if ciaddr := req.ClientIPAddr; ciaddr != nil && !ciaddr.IsUnspecified() {
|
||||||
|
// Unicast DHCPOFFER and DHCPACK messages to the address in
|
||||||
|
// ciaddr.
|
||||||
|
peer = &net.UDPAddr{
|
||||||
|
IP: ciaddr,
|
||||||
|
Port: dhcpv4.ClientPort,
|
||||||
|
}
|
||||||
|
unicast = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(e.burkov): Unicast the message to the actual link-layer address
|
||||||
|
// of the client if broadcast bit is not set. Perhaps, use custom
|
||||||
|
// connection when creating the server.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/3443.
|
||||||
|
|
||||||
|
if !unicast {
|
||||||
|
s.broadcast(peer, conn, resp)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Despite the fact that server4.NewIPv4UDPConn explicitly sets socket
|
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
||||||
// options to allow broadcasting, it also binds the connection to a
|
|
||||||
// specific interface. On FreeBSD conn.WriteTo causes errors while
|
|
||||||
// writing to the addresses that belong to another interface. So, use
|
|
||||||
// the broadcast address specific for the binded interface in case
|
|
||||||
// server4.Server.Serve sets it to net.IPv4Bcast.
|
|
||||||
if udpPeer.IP.Equal(net.IPv4bcast) {
|
|
||||||
udpPeer.IP = s.conf.broadcastIP
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("dhcpv4: sending: %s", resp.Summary())
|
_, err := conn.WriteTo(resp.ToBytes(), peer)
|
||||||
|
|
||||||
_, err = conn.WriteTo(resp.ToBytes(), peer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
}
|
}
|
||||||
@@ -1082,25 +1123,7 @@ func v4Create(conf V4ServerConf) (srv DHCPServer, err error) {
|
|||||||
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := newDHCPOptionParser()
|
s.options = prepareOptions(s.conf)
|
||||||
|
|
||||||
for i, o := range conf.Options {
|
|
||||||
var code uint8
|
|
||||||
var data []byte
|
|
||||||
code, data, err = p.parse(o)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("dhcpv4: bad option string at index %d: %s", i, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
opt := dhcpOption{
|
|
||||||
code: code,
|
|
||||||
data: data,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.conf.options = append(s.conf.options, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
package dhcpd
|
package dhcpd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -146,7 +148,9 @@ func TestV4StaticLease_Get(t *testing.T) {
|
|||||||
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||||
|
|
||||||
t.Run("discover", func(t *testing.T) {
|
t.Run("discover", func(t *testing.T) {
|
||||||
req, err = dhcpv4.NewDiscovery(mac)
|
req, err = dhcpv4.NewDiscovery(mac, dhcpv4.WithRequestedOptions(
|
||||||
|
dhcpv4.OptionDomainNameServer,
|
||||||
|
))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||||
@@ -229,7 +233,10 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
|||||||
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||||
|
|
||||||
t.Run("discover", func(t *testing.T) {
|
t.Run("discover", func(t *testing.T) {
|
||||||
req, err = dhcpv4.NewDiscovery(mac)
|
req, err = dhcpv4.NewDiscovery(mac, dhcpv4.WithRequestedOptions(
|
||||||
|
dhcpv4.OptionFQDN,
|
||||||
|
dhcpv4.OptionRelayAgentInformation,
|
||||||
|
))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||||
@@ -255,9 +262,11 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||||
assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)])
|
assert.Equal(t, []byte("012"), resp.Options.Get(dhcpv4.OptionFQDN))
|
||||||
|
|
||||||
assert.Equal(t, net.IP{1, 2, 3, 4}, net.IP(resp.RelayAgentInfo().ToBytes()))
|
rai := resp.RelayAgentInfo()
|
||||||
|
require.NotNil(t, rai)
|
||||||
|
assert.Equal(t, net.IP{1, 2, 3, 4}, net.IP(rai.ToBytes()))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("request", func(t *testing.T) {
|
t.Run("request", func(t *testing.T) {
|
||||||
@@ -365,3 +374,83 @@ func TestNormalizeHostname(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fakePacketConn is a mock implementation of net.PacketConn to simplify
|
||||||
|
// testing.
|
||||||
|
type fakePacketConn struct {
|
||||||
|
// writeTo is used to substitute net.PacketConn's WriteTo method.
|
||||||
|
writeTo func(p []byte, addr net.Addr) (n int, err error)
|
||||||
|
// net.PacketConn is embedded here simply to make *fakePacketConn a
|
||||||
|
// net.PacketConn without actually implementing all methods.
|
||||||
|
net.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo implements net.PacketConn interface for *fakePacketConn.
|
||||||
|
func (fc *fakePacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
return fc.writeTo(p, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV4Server_Send_unicast(t *testing.T) {
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
var peer *net.UDPAddr
|
||||||
|
|
||||||
|
conn := &fakePacketConn{
|
||||||
|
writeTo: func(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
udpPeer, ok := addr.(*net.UDPAddr)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
peer = cloneUDPAddr(udpPeer)
|
||||||
|
|
||||||
|
n, err = b.Write(p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultPeer := &net.UDPAddr{
|
||||||
|
IP: net.IP{1, 2, 3, 4},
|
||||||
|
// Use neither client nor server port.
|
||||||
|
Port: 1234,
|
||||||
|
}
|
||||||
|
defaultResp := &dhcpv4.DHCPv4{
|
||||||
|
OpCode: dhcpv4.OpcodeBootReply,
|
||||||
|
}
|
||||||
|
s := &v4Server{}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
req *dhcpv4.DHCPv4
|
||||||
|
wantPeer net.Addr
|
||||||
|
}{{
|
||||||
|
name: "relay_agent",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: defaultPeer.IP,
|
||||||
|
},
|
||||||
|
wantPeer: &net.UDPAddr{
|
||||||
|
IP: defaultPeer.IP,
|
||||||
|
Port: dhcpv4.ServerPort,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "known_client",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
|
ClientIPAddr: net.IP{2, 3, 4, 5},
|
||||||
|
},
|
||||||
|
wantPeer: &net.UDPAddr{
|
||||||
|
IP: net.IP{2, 3, 4, 5},
|
||||||
|
Port: dhcpv4.ClientPort,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
s.send(defaultPeer, conn, tc.req, defaultResp)
|
||||||
|
assert.EqualValues(t, defaultResp.ToBytes(), b.Bytes())
|
||||||
|
assert.Equal(t, tc.wantPeer, peer)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
peer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
@@ -192,6 +193,60 @@ func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isUniq(slice []string) (ok bool, uniqueMap map[string]unit) {
|
||||||
|
exists := make(map[string]unit)
|
||||||
|
for _, key := range slice {
|
||||||
|
if _, has := exists[key]; has {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
exists[key] = unit{}
|
||||||
|
}
|
||||||
|
return true, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func intersect(mapA, mapB map[string]unit) bool {
|
||||||
|
for key := range mapA {
|
||||||
|
if _, has := mapB[key]; has {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateAccessSet checks the internal accessListJSON lists. To search for
|
||||||
|
// duplicates, we cannot compare the new stringutil.Set and []string, because
|
||||||
|
// creating a set for a large array can be an unnecessary algorithmic complexity
|
||||||
|
func validateAccessSet(list accessListJSON) (err error) {
|
||||||
|
const (
|
||||||
|
errAllowedDup errors.Error = "duplicates in allowed clients"
|
||||||
|
errDisallowedDup errors.Error = "duplicates in disallowed clients"
|
||||||
|
errBlockedDup errors.Error = "duplicates in blocked hosts"
|
||||||
|
errIntersect errors.Error = "some items in allowed and " +
|
||||||
|
"disallowed lists at the same time"
|
||||||
|
)
|
||||||
|
|
||||||
|
ok, allowedClients := isUniq(list.AllowedClients)
|
||||||
|
if !ok {
|
||||||
|
return errAllowedDup
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, disallowedClients := isUniq(list.DisallowedClients)
|
||||||
|
if !ok {
|
||||||
|
return errDisallowedDup
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, _ = isUniq(list.BlockedHosts)
|
||||||
|
if !ok {
|
||||||
|
return errBlockedDup
|
||||||
|
}
|
||||||
|
|
||||||
|
if intersect(allowedClients, disallowedClients) {
|
||||||
|
return errIntersect
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
|
||||||
list := accessListJSON{}
|
list := accessListJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&list)
|
err := json.NewDecoder(r.Body).Decode(&list)
|
||||||
@@ -201,6 +256,13 @@ func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = validateAccessSet(list)
|
||||||
|
if err != nil {
|
||||||
|
httpError(r, w, http.StatusBadRequest, err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var a *accessCtx
|
var a *accessCtx
|
||||||
a, err = newAccessCtx(list.AllowedClients, list.DisallowedClients, list.BlockedHosts)
|
a, err = newAccessCtx(list.AllowedClients, list.DisallowedClients, list.BlockedHosts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtime"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
@@ -87,6 +88,9 @@ type FilteringConfig struct {
|
|||||||
BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only)
|
BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only)
|
||||||
AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled
|
AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled
|
||||||
FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm
|
FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm
|
||||||
|
// FastestTimeout replaces the default timeout for dialing IP addresses
|
||||||
|
// when FastestAddr is true.
|
||||||
|
FastestTimeout aghtime.Duration `yaml:"fastest_timeout"`
|
||||||
|
|
||||||
// Access settings
|
// Access settings
|
||||||
// --
|
// --
|
||||||
@@ -236,6 +240,7 @@ func (s *Server) createProxyConfig() (proxy.Config, error) {
|
|||||||
proxyConfig.UpstreamMode = proxy.UModeParallel
|
proxyConfig.UpstreamMode = proxy.UModeParallel
|
||||||
} else if s.conf.FastestAddr {
|
} else if s.conf.FastestAddr {
|
||||||
proxyConfig.UpstreamMode = proxy.UModeFastestAddr
|
proxyConfig.UpstreamMode = proxy.UModeFastestAddr
|
||||||
|
proxyConfig.FastestPingTimeout = s.conf.FastestTimeout.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.conf.BogusNXDomain) > 0 {
|
if len(s.conf.BogusNXDomain) > 0 {
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
|||||||
|
|
||||||
// Restrict an access to local addresses for external clients. We also
|
// Restrict an access to local addresses for external clients. We also
|
||||||
// assume that all the DHCP leases we give are locally-served or at
|
// assume that all the DHCP leases we give are locally-served or at
|
||||||
// least don't need to be unaccessable externally.
|
// least don't need to be inaccessible externally.
|
||||||
if s.subnetDetector.IsLocallyServedNetwork(ip) {
|
if s.subnetDetector.IsLocallyServedNetwork(ip) {
|
||||||
if !ctx.isLocalClient {
|
if !ctx.isLocalClient {
|
||||||
log.Debug("dns: %q requests for internal ip", d.Addr)
|
log.Debug("dns: %q requests for internal ip", d.Addr)
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ func (s *Server) startLocked() error {
|
|||||||
const defaultLocalTimeout = 1 * time.Second
|
const defaultLocalTimeout = 1 * time.Second
|
||||||
|
|
||||||
// collectDNSIPAddrs returns IP addresses the server is listening on without
|
// collectDNSIPAddrs returns IP addresses the server is listening on without
|
||||||
// port numbersю For internal use only.
|
// port numbers. For internal use only.
|
||||||
func (s *Server) collectDNSIPAddrs() (addrs []string, err error) {
|
func (s *Server) collectDNSIPAddrs() (addrs []string, err error) {
|
||||||
addrs = make([]string, len(s.conf.TCPListenAddrs)+len(s.conf.UDPListenAddrs))
|
addrs = make([]string, len(s.conf.TCPListenAddrs)+len(s.conf.UDPListenAddrs))
|
||||||
var i int
|
var i int
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ func TestServer(t *testing.T) {
|
|||||||
client := dns.Client{Net: tc.net}
|
client := dns.Client{Net: tc.net}
|
||||||
|
|
||||||
reply, _, err := client.Exchange(createGoogleATestMessage(), addr.String())
|
reply, _, err := client.Exchange(createGoogleATestMessage(), addr.String())
|
||||||
require.NoErrorf(t, err, "сouldn't talk to server %s: %s", addr, err)
|
require.NoErrorf(t, err, "couldn't talk to server %s: %s", addr, err)
|
||||||
|
|
||||||
assertGoogleAResponse(t, reply)
|
assertGoogleAResponse(t, reply)
|
||||||
})
|
})
|
||||||
@@ -330,7 +330,7 @@ func TestServerWithProtectionDisabled(t *testing.T) {
|
|||||||
client := &dns.Client{}
|
client := &dns.Client{}
|
||||||
|
|
||||||
reply, _, err := client.Exchange(req, addr.String())
|
reply, _, err := client.Exchange(req, addr.String())
|
||||||
require.NoErrorf(t, err, "сouldn't talk to server %s: %s", addr, err)
|
require.NoErrorf(t, err, "couldn't talk to server %s: %s", addr, err)
|
||||||
assertGoogleAResponse(t, reply)
|
assertGoogleAResponse(t, reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func (c *ipsetCtx) process(dctx *dnsContext) (rc resultCode) {
|
|||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("ipset: added %d new ips", n)
|
log.Debug("ipset: added %d new ipset entries", n)
|
||||||
|
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type fakeIpsetMgr struct {
|
|||||||
ip6s []net.IP
|
ip6s []net.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add implements the aghnet.IpsetManager inteface for *fakeIpsetMgr.
|
// Add implements the aghnet.IpsetManager interface for *fakeIpsetMgr.
|
||||||
func (m *fakeIpsetMgr) Add(host string, ip4s, ip6s []net.IP) (n int, err error) {
|
func (m *fakeIpsetMgr) Add(host string, ip4s, ip6s []net.IP) (n int, err error) {
|
||||||
m.ip4s = append(m.ip4s, ip4s...)
|
m.ip4s = append(m.ip4s, ip4s...)
|
||||||
m.ip6s = append(m.ip6s, ip6s...)
|
m.ip6s = append(m.ip6s, ip6s...)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ var serviceRulesArray = []svc{
|
|||||||
"||messenger.com^",
|
"||messenger.com^",
|
||||||
"||facebookcorewwwi.onion^",
|
"||facebookcorewwwi.onion^",
|
||||||
"||fbcdn.com^",
|
"||fbcdn.com^",
|
||||||
|
"||fb.watch^",
|
||||||
}},
|
}},
|
||||||
{"twitter", []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"}},
|
{"twitter", []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"}},
|
||||||
{"youtube", []string{
|
{"youtube", []string{
|
||||||
@@ -196,7 +197,7 @@ var serviceRulesArray = []svc{
|
|||||||
}},
|
}},
|
||||||
{"9gag", []string{
|
{"9gag", []string{
|
||||||
"||9cache.com^",
|
"||9cache.com^",
|
||||||
"||gag.com^",
|
"||9gag.com^",
|
||||||
}},
|
}},
|
||||||
{"telegram", []string{
|
{"telegram", []string{
|
||||||
"||t.me^",
|
"||t.me^",
|
||||||
@@ -206,6 +207,8 @@ var serviceRulesArray = []svc{
|
|||||||
{"disneyplus", []string{
|
{"disneyplus", []string{
|
||||||
"||disney-plus.net^",
|
"||disney-plus.net^",
|
||||||
"||disneyplus.com^",
|
"||disneyplus.com^",
|
||||||
|
"||disney.playback.edge.bamgrid.com^",
|
||||||
|
"||media.dssott.com^",
|
||||||
}},
|
}},
|
||||||
{"hulu", []string{
|
{"hulu", []string{
|
||||||
"||hulu.com^",
|
"||hulu.com^",
|
||||||
|
|||||||
@@ -255,10 +255,14 @@ func (d *DNSFilter) GetConfig() (s Settings) {
|
|||||||
// WriteDiskConfig - write configuration
|
// WriteDiskConfig - write configuration
|
||||||
func (d *DNSFilter) WriteDiskConfig(c *Config) {
|
func (d *DNSFilter) WriteDiskConfig(c *Config) {
|
||||||
d.confLock.Lock()
|
d.confLock.Lock()
|
||||||
|
defer d.confLock.Unlock()
|
||||||
|
|
||||||
*c = d.Config
|
*c = d.Config
|
||||||
c.Rewrites = rewriteArrayDup(d.Config.Rewrites)
|
c.Rewrites = cloneRewrites(c.Rewrites)
|
||||||
// BlockedServices
|
}
|
||||||
d.confLock.Unlock()
|
|
||||||
|
func cloneRewrites(entries []RewriteEntry) (clone []RewriteEntry) {
|
||||||
|
return append([]RewriteEntry(nil), entries...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFilters - set new filters (synchronously or asynchronously)
|
// SetFilters - set new filters (synchronously or asynchronously)
|
||||||
|
|||||||
@@ -27,8 +27,66 @@ type RewriteEntry struct {
|
|||||||
Type uint16 `yaml:"-"`
|
Type uint16 `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RewriteEntry) equals(b RewriteEntry) bool {
|
// equal returns true if the entry is considered equal to the other.
|
||||||
return r.Domain == b.Domain && r.Answer == b.Answer
|
func (e *RewriteEntry) equal(other RewriteEntry) (ok bool) {
|
||||||
|
return e.Domain == other.Domain && e.Answer == other.Answer
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchesQType returns true if the entry matched qtype.
|
||||||
|
func (e *RewriteEntry) matchesQType(qtype uint16) (ok bool) {
|
||||||
|
// Add CNAMEs, since they match for all types requests.
|
||||||
|
if e.Type == dns.TypeCNAME {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject types other than A and AAAA.
|
||||||
|
if qtype != dns.TypeA && qtype != dns.TypeAAAA {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the types match or the entry is set to allow only the other type,
|
||||||
|
// include them.
|
||||||
|
return e.Type == qtype || e.IP == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize makes sure that the a new or decoded entry is normalized with
|
||||||
|
// regards to domain name case, IP length, and so on.
|
||||||
|
func (e *RewriteEntry) normalize() {
|
||||||
|
// TODO(a.garipov): Write a case-agnostic version of strings.HasSuffix
|
||||||
|
// and use it in matchDomainWildcard instead of using strings.ToLower
|
||||||
|
// everywhere.
|
||||||
|
e.Domain = strings.ToLower(e.Domain)
|
||||||
|
|
||||||
|
switch e.Answer {
|
||||||
|
case "AAAA":
|
||||||
|
e.IP = nil
|
||||||
|
e.Type = dns.TypeAAAA
|
||||||
|
|
||||||
|
return
|
||||||
|
case "A":
|
||||||
|
e.IP = nil
|
||||||
|
e.Type = dns.TypeA
|
||||||
|
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// Go on.
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(e.Answer)
|
||||||
|
if ip == nil {
|
||||||
|
e.Type = dns.TypeCNAME
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ip4 := ip.To4()
|
||||||
|
if ip4 != nil {
|
||||||
|
e.IP = ip4
|
||||||
|
e.Type = dns.TypeA
|
||||||
|
} else {
|
||||||
|
e.IP = ip
|
||||||
|
e.Type = dns.TypeAAAA
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isWildcard(host string) bool {
|
func isWildcard(host string) bool {
|
||||||
@@ -78,48 +136,9 @@ func (a rewritesSorted) Less(i, j int) bool {
|
|||||||
return len(a[i].Domain) > len(a[j].Domain)
|
return len(a[i].Domain) > len(a[j].Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare prepares the a new or decoded entry.
|
|
||||||
func (r *RewriteEntry) prepare() {
|
|
||||||
// TODO(a.garipov): Write a case-agnostic version of strings.HasSuffix
|
|
||||||
// and use it in matchDomainWildcard instead of using strings.ToLower
|
|
||||||
// everywhere.
|
|
||||||
r.Domain = strings.ToLower(r.Domain)
|
|
||||||
|
|
||||||
switch r.Answer {
|
|
||||||
case "AAAA":
|
|
||||||
r.IP = nil
|
|
||||||
r.Type = dns.TypeAAAA
|
|
||||||
|
|
||||||
return
|
|
||||||
case "A":
|
|
||||||
r.IP = nil
|
|
||||||
r.Type = dns.TypeA
|
|
||||||
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// Go on.
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(r.Answer)
|
|
||||||
if ip == nil {
|
|
||||||
r.Type = dns.TypeCNAME
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ip4 := ip.To4()
|
|
||||||
if ip4 != nil {
|
|
||||||
r.IP = ip4
|
|
||||||
r.Type = dns.TypeA
|
|
||||||
} else {
|
|
||||||
r.IP = ip
|
|
||||||
r.Type = dns.TypeAAAA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DNSFilter) prepareRewrites() {
|
func (d *DNSFilter) prepareRewrites() {
|
||||||
for i := range d.Rewrites {
|
for i := range d.Rewrites {
|
||||||
d.Rewrites[i].prepare()
|
d.Rewrites[i].normalize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,18 +146,15 @@ func (d *DNSFilter) prepareRewrites() {
|
|||||||
// CNAME, then A and AAAA; exact, then wildcard. If the host is matched
|
// CNAME, then A and AAAA; exact, then wildcard. If the host is matched
|
||||||
// exactly, wildcard entries aren't returned. If the host matched by wildcards,
|
// exactly, wildcard entries aren't returned. If the host matched by wildcards,
|
||||||
// return the most specific for the question type.
|
// return the most specific for the question type.
|
||||||
func findRewrites(a []RewriteEntry, host string, qtype uint16) []RewriteEntry {
|
func findRewrites(entries []RewriteEntry, host string, qtype uint16) (matched []RewriteEntry) {
|
||||||
rr := rewritesSorted{}
|
rr := rewritesSorted{}
|
||||||
for _, r := range a {
|
for _, e := range entries {
|
||||||
if r.Domain != host && !matchDomainWildcard(host, r.Domain) {
|
if e.Domain != host && !matchDomainWildcard(host, e.Domain) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return CNAMEs for all types requests, but only the
|
if e.matchesQType(qtype) {
|
||||||
// appropriate ones for A and AAAA.
|
rr = append(rr, e)
|
||||||
if r.Type == dns.TypeCNAME ||
|
|
||||||
(r.Type == qtype && (qtype == dns.TypeA || qtype == dns.TypeAAAA)) {
|
|
||||||
rr = append(rr, r)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,12 +185,6 @@ func max(a, b int) int {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func rewriteArrayDup(a []RewriteEntry) []RewriteEntry {
|
|
||||||
a2 := make([]RewriteEntry, len(a))
|
|
||||||
copy(a2, a)
|
|
||||||
return a2
|
|
||||||
}
|
|
||||||
|
|
||||||
type rewriteEntryJSON struct {
|
type rewriteEntryJSON struct {
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Answer string `json:"answer"`
|
Answer string `json:"answer"`
|
||||||
@@ -213,7 +223,7 @@ func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
Domain: jsent.Domain,
|
Domain: jsent.Domain,
|
||||||
Answer: jsent.Answer,
|
Answer: jsent.Answer,
|
||||||
}
|
}
|
||||||
ent.prepare()
|
ent.normalize()
|
||||||
d.confLock.Lock()
|
d.confLock.Lock()
|
||||||
d.Config.Rewrites = append(d.Config.Rewrites, ent)
|
d.Config.Rewrites = append(d.Config.Rewrites, ent)
|
||||||
d.confLock.Unlock()
|
d.confLock.Unlock()
|
||||||
@@ -238,7 +248,7 @@ func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request)
|
|||||||
arr := []RewriteEntry{}
|
arr := []RewriteEntry{}
|
||||||
d.confLock.Lock()
|
d.confLock.Lock()
|
||||||
for _, ent := range d.Config.Rewrites {
|
for _, ent := range d.Config.Rewrites {
|
||||||
if ent.equals(entDel) {
|
if ent.equal(entDel) {
|
||||||
log.Debug("Rewrites: removed element: %s -> %s", ent.Domain, ent.Answer)
|
log.Debug("Rewrites: removed element: %s -> %s", ent.Domain, ent.Answer)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ func TestRewritesExceptionIP(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "match_AAAA_host3.com",
|
name: "match_AAAA_host3.com",
|
||||||
host: "host3.com",
|
host: "host3.com",
|
||||||
want: nil,
|
want: []net.IP{},
|
||||||
dtyp: dns.TypeAAAA,
|
dtyp: dns.TypeAAAA,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|||||||
@@ -149,19 +149,19 @@ func (clients *clientsContainer) Reload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type clientObject struct {
|
type clientObject struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Tags []string `yaml:"tags"`
|
|
||||||
IDs []string `yaml:"ids"`
|
|
||||||
UseGlobalSettings bool `yaml:"use_global_settings"`
|
|
||||||
FilteringEnabled bool `yaml:"filtering_enabled"`
|
|
||||||
ParentalEnabled bool `yaml:"parental_enabled"`
|
|
||||||
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
|
|
||||||
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
|
||||||
|
|
||||||
UseGlobalBlockedServices bool `yaml:"use_global_blocked_services"`
|
Tags []string `yaml:"tags"`
|
||||||
BlockedServices []string `yaml:"blocked_services"`
|
IDs []string `yaml:"ids"`
|
||||||
|
BlockedServices []string `yaml:"blocked_services"`
|
||||||
|
Upstreams []string `yaml:"upstreams"`
|
||||||
|
|
||||||
Upstreams []string `yaml:"upstreams"`
|
UseGlobalSettings bool `yaml:"use_global_settings"`
|
||||||
|
FilteringEnabled bool `yaml:"filtering_enabled"`
|
||||||
|
ParentalEnabled bool `yaml:"parental_enabled"`
|
||||||
|
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
|
||||||
|
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
||||||
|
UseGlobalBlockedServices bool `yaml:"use_global_blocked_services"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clients *clientsContainer) tagKnown(tag string) (ok bool) {
|
func (clients *clientsContainer) tagKnown(tag string) (ok bool) {
|
||||||
@@ -286,38 +286,67 @@ func toQueryLogWHOIS(wi *RuntimeClientWHOISInfo) (cw *querylog.ClientWHOIS) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// findMultiple is a wrapper around Find to make it a valid client finder for
|
// findMultiple is a wrapper around Find to make it a valid client finder for
|
||||||
// the query log. err is always nil.
|
// the query log. c is never nil; if no information about the client is found,
|
||||||
|
// it returns an artificial client record by only setting the blocking-related
|
||||||
|
// fields. err is always nil.
|
||||||
func (clients *clientsContainer) findMultiple(ids []string) (c *querylog.Client, err error) {
|
func (clients *clientsContainer) findMultiple(ids []string) (c *querylog.Client, err error) {
|
||||||
|
var artClient *querylog.Client
|
||||||
|
var art bool
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
var name string
|
c, art = clients.clientOrArtificial(net.ParseIP(id), id)
|
||||||
whois := &querylog.ClientWHOIS{}
|
if art {
|
||||||
ip := net.ParseIP(id)
|
artClient = c
|
||||||
|
|
||||||
c, ok := clients.Find(id)
|
continue
|
||||||
if ok {
|
|
||||||
name = c.Name
|
|
||||||
} else if ip != nil {
|
|
||||||
var rc *RuntimeClient
|
|
||||||
rc, ok = clients.FindRuntimeClient(ip)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
name = rc.Host
|
|
||||||
whois = toQueryLogWHOIS(rc.WHOISInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disallowed, disallowedRule := clients.dnsServer.IsBlockedClient(ip, id)
|
return c, nil
|
||||||
|
|
||||||
return &querylog.Client{
|
|
||||||
Name: name,
|
|
||||||
DisallowedRule: disallowedRule,
|
|
||||||
WHOIS: whois,
|
|
||||||
Disallowed: disallowed,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return artClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientOrArtificial returns information about one client. If art is true,
|
||||||
|
// this is an artificial client record, meaning that we currently don't have any
|
||||||
|
// records about this client besides maybe whether or not it is blocked. c is
|
||||||
|
// never nil.
|
||||||
|
func (clients *clientsContainer) clientOrArtificial(
|
||||||
|
ip net.IP,
|
||||||
|
id string,
|
||||||
|
) (c *querylog.Client, art bool) {
|
||||||
|
defer func() {
|
||||||
|
c.Disallowed, c.DisallowedRule = clients.dnsServer.IsBlockedClient(ip, id)
|
||||||
|
if c.WHOIS == nil {
|
||||||
|
c.WHOIS = &querylog.ClientWHOIS{}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
client, ok := clients.Find(id)
|
||||||
|
if ok {
|
||||||
|
return &querylog.Client{
|
||||||
|
Name: client.Name,
|
||||||
|
}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip == nil {
|
||||||
|
// Technically should never happen, but still.
|
||||||
|
return &querylog.Client{
|
||||||
|
Name: "",
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
var rc *RuntimeClient
|
||||||
|
rc, ok = clients.FindRuntimeClient(ip)
|
||||||
|
if ok {
|
||||||
|
return &querylog.Client{
|
||||||
|
Name: rc.Host,
|
||||||
|
WHOIS: toQueryLogWHOIS(rc.WHOISInfo),
|
||||||
|
}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return &querylog.Client{
|
||||||
|
Name: "",
|
||||||
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clients *clientsContainer) Find(id string) (c *Client, ok bool) {
|
func (clients *clientsContainer) Find(id string) (c *Client, ok bool) {
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtime"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
|
"github.com/AdguardTeam/dnsproxy/fastip"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/google/renameio/maybe"
|
"github.com/google/renameio/maybe"
|
||||||
@@ -106,9 +108,9 @@ type dnsConfig struct {
|
|||||||
QueryLogEnabled bool `yaml:"querylog_enabled"` // if true, query log is enabled
|
QueryLogEnabled bool `yaml:"querylog_enabled"` // if true, query log is enabled
|
||||||
QueryLogFileEnabled bool `yaml:"querylog_file_enabled"` // if true, query log will be written to a file
|
QueryLogFileEnabled bool `yaml:"querylog_file_enabled"` // if true, query log will be written to a file
|
||||||
// QueryLogInterval is the interval for query log's files rotation.
|
// QueryLogInterval is the interval for query log's files rotation.
|
||||||
QueryLogInterval Duration `yaml:"querylog_interval"`
|
QueryLogInterval aghtime.Duration `yaml:"querylog_interval"`
|
||||||
QueryLogMemSize uint32 `yaml:"querylog_size_memory"` // number of entries kept in memory before they are flushed to disk
|
QueryLogMemSize uint32 `yaml:"querylog_size_memory"` // number of entries kept in memory before they are flushed to disk
|
||||||
AnonymizeClientIP bool `yaml:"anonymize_client_ip"` // anonymize clients' IP addresses in logs and stats
|
AnonymizeClientIP bool `yaml:"anonymize_client_ip"` // anonymize clients' IP addresses in logs and stats
|
||||||
|
|
||||||
dnsforward.FilteringConfig `yaml:",inline"`
|
dnsforward.FilteringConfig `yaml:",inline"`
|
||||||
|
|
||||||
@@ -117,7 +119,7 @@ type dnsConfig struct {
|
|||||||
DnsfilterConf filtering.Config `yaml:",inline"`
|
DnsfilterConf filtering.Config `yaml:",inline"`
|
||||||
|
|
||||||
// UpstreamTimeout is the timeout for querying upstream servers.
|
// UpstreamTimeout is the timeout for querying upstream servers.
|
||||||
UpstreamTimeout Duration `yaml:"upstream_timeout"`
|
UpstreamTimeout aghtime.Duration `yaml:"upstream_timeout"`
|
||||||
|
|
||||||
// LocalDomainName is the domain name used for known internal hosts.
|
// LocalDomainName is the domain name used for known internal hosts.
|
||||||
// For example, a machine called "myhost" can be addressed as
|
// For example, a machine called "myhost" can be addressed as
|
||||||
@@ -160,8 +162,10 @@ type tlsConfigSettings struct {
|
|||||||
dnsforward.TLSConfig `yaml:",inline" json:",inline"`
|
dnsforward.TLSConfig `yaml:",inline" json:",inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize to default values, will be changed later when reading config or parsing command line
|
// config is the global configuration structure.
|
||||||
var config = configuration{
|
//
|
||||||
|
// TODO(a.garipov, e.burkov): This global is afwul and must be removed.
|
||||||
|
var config = &configuration{
|
||||||
BindPort: 3000,
|
BindPort: 3000,
|
||||||
BetaBindPort: 0,
|
BetaBindPort: 0,
|
||||||
BindHost: net.IP{0, 0, 0, 0},
|
BindHost: net.IP{0, 0, 0, 0},
|
||||||
@@ -169,7 +173,7 @@ var config = configuration{
|
|||||||
AuthBlockMin: 15,
|
AuthBlockMin: 15,
|
||||||
DNS: dnsConfig{
|
DNS: dnsConfig{
|
||||||
BindHosts: []net.IP{{0, 0, 0, 0}},
|
BindHosts: []net.IP{{0, 0, 0, 0}},
|
||||||
Port: 53,
|
Port: defaultPortDNS,
|
||||||
StatsInterval: 1,
|
StatsInterval: 1,
|
||||||
FilteringConfig: dnsforward.FilteringConfig{
|
FilteringConfig: dnsforward.FilteringConfig{
|
||||||
ProtectionEnabled: true, // whether or not use any of filtering features
|
ProtectionEnabled: true, // whether or not use any of filtering features
|
||||||
@@ -178,6 +182,9 @@ var config = configuration{
|
|||||||
Ratelimit: 20,
|
Ratelimit: 20,
|
||||||
RefuseAny: true,
|
RefuseAny: true,
|
||||||
AllServers: false,
|
AllServers: false,
|
||||||
|
FastestTimeout: aghtime.Duration{
|
||||||
|
Duration: fastip.DefaultPingWaitTimeout,
|
||||||
|
},
|
||||||
|
|
||||||
TrustedProxies: []string{"127.0.0.0/8", "::1/128"},
|
TrustedProxies: []string{"127.0.0.0/8", "::1/128"},
|
||||||
|
|
||||||
@@ -189,15 +196,15 @@ var config = configuration{
|
|||||||
},
|
},
|
||||||
FilteringEnabled: true, // whether or not use filter lists
|
FilteringEnabled: true, // whether or not use filter lists
|
||||||
FiltersUpdateIntervalHours: 24,
|
FiltersUpdateIntervalHours: 24,
|
||||||
UpstreamTimeout: Duration{Duration: dnsforward.DefaultTimeout},
|
UpstreamTimeout: aghtime.Duration{Duration: dnsforward.DefaultTimeout},
|
||||||
LocalDomainName: "lan",
|
LocalDomainName: "lan",
|
||||||
ResolveClients: true,
|
ResolveClients: true,
|
||||||
UsePrivateRDNS: true,
|
UsePrivateRDNS: true,
|
||||||
},
|
},
|
||||||
TLS: tlsConfigSettings{
|
TLS: tlsConfigSettings{
|
||||||
PortHTTPS: 443,
|
PortHTTPS: defaultPortHTTPS,
|
||||||
PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
|
PortDNSOverTLS: defaultPortTLS, // needs to be passed through to dnsproxy
|
||||||
PortDNSOverQUIC: 784,
|
PortDNSOverQUIC: defaultPortQUIC,
|
||||||
},
|
},
|
||||||
logSettings: logSettings{
|
logSettings: logSettings{
|
||||||
LogCompress: false,
|
LogCompress: false,
|
||||||
@@ -216,7 +223,7 @@ func initConfig() {
|
|||||||
|
|
||||||
config.DNS.QueryLogEnabled = true
|
config.DNS.QueryLogEnabled = true
|
||||||
config.DNS.QueryLogFileEnabled = true
|
config.DNS.QueryLogFileEnabled = true
|
||||||
config.DNS.QueryLogInterval = Duration{Duration: 90 * 24 * time.Hour}
|
config.DNS.QueryLogInterval = aghtime.Duration{Duration: 90 * 24 * time.Hour}
|
||||||
config.DNS.QueryLogMemSize = 1000
|
config.DNS.QueryLogMemSize = 1000
|
||||||
|
|
||||||
config.DNS.CacheSize = 4 * 1024 * 1024
|
config.DNS.CacheSize = 4 * 1024 * 1024
|
||||||
@@ -285,7 +292,7 @@ func parseConfig() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.DNS.UpstreamTimeout.Duration == 0 {
|
if config.DNS.UpstreamTimeout.Duration == 0 {
|
||||||
config.DNS.UpstreamTimeout = Duration{Duration: dnsforward.DefaultTimeout}
|
config.DNS.UpstreamTimeout = aghtime.Duration{Duration: dnsforward.DefaultTimeout}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -332,7 +339,7 @@ func (c *configuration) write() error {
|
|||||||
Context.queryLog.WriteDiskConfig(&dc)
|
Context.queryLog.WriteDiskConfig(&dc)
|
||||||
config.DNS.QueryLogEnabled = dc.Enabled
|
config.DNS.QueryLogEnabled = dc.Enabled
|
||||||
config.DNS.QueryLogFileEnabled = dc.FileEnabled
|
config.DNS.QueryLogFileEnabled = dc.FileEnabled
|
||||||
config.DNS.QueryLogInterval = Duration{Duration: dc.RotationIvl}
|
config.DNS.QueryLogInterval = aghtime.Duration{Duration: dc.RotationIvl}
|
||||||
config.DNS.QueryLogMemSize = dc.MemSize
|
config.DNS.QueryLogMemSize = dc.MemSize
|
||||||
config.DNS.AnonymizeClientIP = dc.AnonymizeClientIP
|
config.DNS.AnonymizeClientIP = dc.AnonymizeClientIP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func httpError(w http.ResponseWriter, code int, format string, args ...interface
|
|||||||
func appendDNSAddrs(dst []string, addrs ...net.IP) (res []string) {
|
func appendDNSAddrs(dst []string, addrs ...net.IP) (res []string) {
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
var hostport string
|
var hostport string
|
||||||
if config.DNS.Port != 53 {
|
if config.DNS.Port != defaultPortDNS {
|
||||||
hostport = netutil.JoinHostPort(addr.String(), config.DNS.Port)
|
hostport = netutil.JoinHostPort(addr.String(), config.DNS.Port)
|
||||||
} else {
|
} else {
|
||||||
hostport = addr.String()
|
hostport = addr.String()
|
||||||
@@ -285,8 +285,6 @@ func preInstallHandler(handler http.Handler) http.Handler {
|
|||||||
return &preInstallHandlerStruct{handler}
|
return &preInstallHandlerStruct{handler}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultHTTPSPort = 443
|
|
||||||
|
|
||||||
// handleHTTPSRedirect redirects the request to HTTPS, if needed. If ok is
|
// handleHTTPSRedirect redirects the request to HTTPS, if needed. If ok is
|
||||||
// true, the middleware must continue handling the request.
|
// true, the middleware must continue handling the request.
|
||||||
func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) {
|
func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) {
|
||||||
@@ -304,7 +302,7 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) {
|
|||||||
|
|
||||||
if r.TLS == nil && web.forceHTTPS {
|
if r.TLS == nil && web.forceHTTPS {
|
||||||
hostPort := host
|
hostPort := host
|
||||||
if port := web.conf.PortHTTPS; port != defaultHTTPSPort {
|
if port := web.conf.PortHTTPS; port != defaultPortHTTPS {
|
||||||
hostPort = netutil.JoinHostPort(host, port)
|
hostPort = netutil.JoinHostPort(host, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -254,13 +254,21 @@ func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.controlLock.Unlock()
|
|
||||||
flags := filterRefreshBlocklists
|
flags := filterRefreshBlocklists
|
||||||
if req.White {
|
if req.White {
|
||||||
flags = filterRefreshAllowlists
|
flags = filterRefreshAllowlists
|
||||||
}
|
}
|
||||||
resp.Updated, err = f.refreshFilters(flags|filterRefreshForce, false)
|
func() {
|
||||||
Context.controlLock.Lock()
|
// Temporarily unlock the Context.controlLock because the
|
||||||
|
// f.refreshFilters waits for it to be unlocked but it's
|
||||||
|
// actually locked in ensure wrapper.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Reconsider this messy syncing process.
|
||||||
|
Context.controlLock.Unlock()
|
||||||
|
defer Context.controlLock.Lock()
|
||||||
|
|
||||||
|
resp.Updated, err = f.refreshFilters(flags|filterRefreshForce, false)
|
||||||
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusInternalServerError, "%s", err)
|
httpError(w, http.StatusInternalServerError, "%s", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// getAddrsResponse is the response for /install/get_addresses endpoint.
|
// getAddrsResponse is the response for /install/get_addresses endpoint.
|
||||||
@@ -29,8 +29,8 @@ type getAddrsResponse struct {
|
|||||||
// handleInstallGetAddresses is the handler for /install/get_addresses endpoint.
|
// handleInstallGetAddresses is the handler for /install/get_addresses endpoint.
|
||||||
func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
||||||
data := getAddrsResponse{}
|
data := getAddrsResponse{}
|
||||||
data.WebPort = 80
|
data.WebPort = defaultPortHTTP
|
||||||
data.DNSPort = 53
|
data.DNSPort = defaultPortDNS
|
||||||
|
|
||||||
ifaces, err := aghnet.GetValidNetInterfacesForWeb()
|
ifaces, err := aghnet.GetValidNetInterfacesForWeb()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -257,7 +257,8 @@ type applyConfigReq struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy installation parameters between two configuration objects
|
// copyInstallSettings copies the installation parameters between two
|
||||||
|
// configuration structures.
|
||||||
func copyInstallSettings(dst, src *configuration) {
|
func copyInstallSettings(dst, src *configuration) {
|
||||||
dst.BindHost = src.BindHost
|
dst.BindHost = src.BindHost
|
||||||
dst.BindPort = src.BindPort
|
dst.BindPort = src.BindPort
|
||||||
@@ -286,55 +287,29 @@ func shutdownSrv(ctx context.Context, cancel context.CancelFunc, srv *http.Serve
|
|||||||
|
|
||||||
// Apply new configuration, start DNS server, restart Web server
|
// Apply new configuration, start DNS server, restart Web server
|
||||||
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
req := applyConfigReq{}
|
req, restartHTTP, err := decodeApplyConfigReq(r.Body)
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "Failed to parse 'configure' JSON: %s", err)
|
httpError(w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Web.Port == 0 || req.DNS.Port == 0 {
|
|
||||||
httpError(w, http.StatusBadRequest, "port value can't be 0")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
restartHTTP := true
|
|
||||||
if config.BindHost.Equal(req.Web.IP) && config.BindPort == req.Web.Port {
|
|
||||||
// no need to rebind
|
|
||||||
restartHTTP = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate that hosts and ports are bindable
|
|
||||||
if restartHTTP {
|
|
||||||
err = aghnet.CheckPortAvailable(req.Web.IP, req.Web.Port)
|
|
||||||
if err != nil {
|
|
||||||
httpError(
|
|
||||||
w,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
"can not listen on IP:port %s: %s",
|
|
||||||
netutil.JoinHostPort(req.Web.IP.String(), req.Web.Port),
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
err = aghnet.CheckPacketPortAvailable(req.DNS.IP, req.DNS.Port)
|
err = aghnet.CheckPacketPortAvailable(req.DNS.IP, req.DNS.Port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "%s", err)
|
httpError(w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = aghnet.CheckPortAvailable(req.DNS.IP, req.DNS.Port)
|
err = aghnet.CheckPortAvailable(req.DNS.IP, req.DNS.Port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "%s", err)
|
httpError(w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var curConfig configuration
|
curConfig := &configuration{}
|
||||||
copyInstallSettings(&curConfig, &config)
|
copyInstallSettings(curConfig, config)
|
||||||
|
|
||||||
Context.firstRun = false
|
Context.firstRun = false
|
||||||
config.BindHost = req.Web.IP
|
config.BindHost = req.Web.IP
|
||||||
@@ -349,8 +324,9 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
|||||||
err = StartMods()
|
err = StartMods()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Context.firstRun = true
|
Context.firstRun = true
|
||||||
copyInstallSettings(&config, &curConfig)
|
copyInstallSettings(config, curConfig)
|
||||||
httpError(w, http.StatusInternalServerError, "%s", err)
|
httpError(w, http.StatusInternalServerError, "%s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,8 +337,9 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
|||||||
err = config.write()
|
err = config.write()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Context.firstRun = true
|
Context.firstRun = true
|
||||||
copyInstallSettings(&config, &curConfig)
|
copyInstallSettings(config, curConfig)
|
||||||
httpError(w, http.StatusInternalServerError, "Couldn't write config: %s", err)
|
httpError(w, http.StatusInternalServerError, "Couldn't write config: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,6 +364,36 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decodeApplyConfigReq decodes the configuration, validates some parameters,
|
||||||
|
// and returns it along with the boolean indicating whether or not the HTTP
|
||||||
|
// server must be restarted.
|
||||||
|
func decodeApplyConfigReq(r io.Reader) (req *applyConfigReq, restartHTTP bool, err error) {
|
||||||
|
req = &applyConfigReq{}
|
||||||
|
err = json.NewDecoder(r).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("parsing request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Web.Port == 0 || req.DNS.Port == 0 {
|
||||||
|
return nil, false, errors.Error("ports cannot be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
restartHTTP = !config.BindHost.Equal(req.Web.IP) || config.BindPort != req.Web.Port
|
||||||
|
if restartHTTP {
|
||||||
|
err = aghnet.CheckPortAvailable(req.Web.IP, req.Web.Port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf(
|
||||||
|
"checking address %s:%d: %w",
|
||||||
|
req.Web.IP.String(),
|
||||||
|
req.Web.Port,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, restartHTTP, err
|
||||||
|
}
|
||||||
|
|
||||||
func (web *Web) registerInstallHandlers() {
|
func (web *Web) registerInstallHandlers() {
|
||||||
Context.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
|
Context.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
|
||||||
Context.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
|
Context.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
|
||||||
@@ -546,8 +553,8 @@ type getAddrsResponseBeta struct {
|
|||||||
// functionality will appear in default handleInstallGetAddresses.
|
// functionality will appear in default handleInstallGetAddresses.
|
||||||
func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Request) {
|
func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Request) {
|
||||||
data := getAddrsResponseBeta{}
|
data := getAddrsResponseBeta{}
|
||||||
data.WebPort = 80
|
data.WebPort = defaultPortHTTP
|
||||||
data.DNSPort = 53
|
data.DNSPort = defaultPortDNS
|
||||||
|
|
||||||
ifaces, err := aghnet.GetValidNetInterfacesForWeb()
|
ifaces, err := aghnet.GetValidNetInterfacesForWeb()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Default ports.
|
||||||
|
const (
|
||||||
|
defaultPortDNS = 53
|
||||||
|
defaultPortHTTP = 80
|
||||||
|
defaultPortHTTPS = 443
|
||||||
|
defaultPortQUIC = 784
|
||||||
|
defaultPortTLS = 853
|
||||||
|
)
|
||||||
|
|
||||||
// Called by other modules when configuration is changed
|
// Called by other modules when configuration is changed
|
||||||
func onConfigModified() {
|
func onConfigModified() {
|
||||||
_ = config.write()
|
_ = config.write()
|
||||||
@@ -253,7 +262,7 @@ func getDNSEncryption() (de dnsEncryption) {
|
|||||||
hostname := tlsConf.ServerName
|
hostname := tlsConf.ServerName
|
||||||
if tlsConf.PortHTTPS != 0 {
|
if tlsConf.PortHTTPS != 0 {
|
||||||
addr := hostname
|
addr := hostname
|
||||||
if tlsConf.PortHTTPS != 443 {
|
if tlsConf.PortHTTPS != defaultPortHTTPS {
|
||||||
addr = netutil.JoinHostPort(addr, tlsConf.PortHTTPS)
|
addr = netutil.JoinHostPort(addr, tlsConf.PortHTTPS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nextFilterID = time.Now().Unix() // semi-stable way to generate an unique ID
|
var nextFilterID = time.Now().Unix() // semi-stable way to generate an unique ID
|
||||||
@@ -535,26 +536,85 @@ func (f *Filtering) read(reader io.Reader, tmpFile *os.File, filter *filter) (in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateIntl returns true if filter update performed successfully.
|
// finalizeUpdate closes and gets rid of temporary file f with filter's content
|
||||||
func (f *Filtering) updateIntl(filter *filter) (updated bool, err error) {
|
// according to updated. It also saves new values of flt's name, rules number
|
||||||
updated = false
|
// and checksum if sucсeeded.
|
||||||
log.Tracef("Downloading update for filter %d from %s", filter.ID, filter.URL)
|
func finalizeUpdate(
|
||||||
|
f *os.File,
|
||||||
|
flt *filter,
|
||||||
|
updated bool,
|
||||||
|
name string,
|
||||||
|
rnum int,
|
||||||
|
cs uint32,
|
||||||
|
) (err error) {
|
||||||
|
tmpFileName := f.Name()
|
||||||
|
|
||||||
tmpFile, err := os.CreateTemp(filepath.Join(Context.getDataDir(), filterDir), "")
|
// Close the file before renaming it because it's required on Windows.
|
||||||
|
//
|
||||||
|
// See https://github.com/adguardTeam/adGuardHome/issues/1553.
|
||||||
|
if err = f.Close(); err != nil {
|
||||||
|
return fmt.Errorf("closing temporary file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated {
|
||||||
|
log.Tracef("filter #%d from %s has no changes, skip", flt.ID, flt.URL)
|
||||||
|
|
||||||
|
return os.Remove(tmpFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("saving filter %d contents to: %s", flt.ID, flt.Path())
|
||||||
|
|
||||||
|
if err = os.Rename(tmpFileName, flt.Path()); err != nil {
|
||||||
|
return errors.WithDeferred(err, os.Remove(tmpFileName))
|
||||||
|
}
|
||||||
|
|
||||||
|
flt.Name = stringutil.Coalesce(flt.Name, name)
|
||||||
|
flt.checksum = cs
|
||||||
|
flt.RulesCount = rnum
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processUpdate copies filter's content from src to dst and returns the name,
|
||||||
|
// rules number, and checksum for it. It also returns the number of bytes read
|
||||||
|
// from src.
|
||||||
|
func (f *Filtering) processUpdate(
|
||||||
|
src io.Reader,
|
||||||
|
dst *os.File,
|
||||||
|
flt *filter,
|
||||||
|
) (name string, rnum int, cs uint32, n int, err error) {
|
||||||
|
if n, err = f.read(src, dst, flt); err != nil {
|
||||||
|
return "", 0, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = dst.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return "", 0, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rnum, cs, name = f.parseFilterContents(dst)
|
||||||
|
|
||||||
|
return name, rnum, cs, n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateIntl updates the flt rewriting it's actual file. It returns true if
|
||||||
|
// the actual update has been performed.
|
||||||
|
func (f *Filtering) updateIntl(flt *filter) (ok bool, err error) {
|
||||||
|
log.Tracef("downloading update for filter %d from %s", flt.ID, flt.URL)
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var rnum, n int
|
||||||
|
var cs uint32
|
||||||
|
|
||||||
|
var tmpFile *os.File
|
||||||
|
tmpFile, err = os.CreateTemp(filepath.Join(Context.getDataDir(), filterDir), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return updated, err
|
return false, err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
var derr error
|
err = errors.WithDeferred(err, finalizeUpdate(tmpFile, flt, ok, name, rnum, cs))
|
||||||
if tmpFile != nil {
|
ok = ok && err == nil
|
||||||
if derr = tmpFile.Close(); derr != nil {
|
if ok {
|
||||||
log.Printf("Couldn't close temporary file: %s", derr)
|
log.Printf("updated filter %d: %d bytes, %d rules", flt.ID, n, rnum)
|
||||||
}
|
|
||||||
|
|
||||||
tmpFileName := tmpFile.Name()
|
|
||||||
if derr = os.Remove(tmpFileName); derr != nil {
|
|
||||||
log.Printf("Couldn't delete temporary file %s: %s", tmpFileName, derr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -562,72 +622,42 @@ func (f *Filtering) updateIntl(filter *filter) (updated bool, err error) {
|
|||||||
// end users.
|
// end users.
|
||||||
//
|
//
|
||||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/3198.
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/3198.
|
||||||
err = tmpFile.Chmod(0o644)
|
if err = tmpFile.Chmod(0o644); err != nil {
|
||||||
if err != nil {
|
return false, fmt.Errorf("changing file mode: %w", err)
|
||||||
return updated, fmt.Errorf("changing file mode: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var reader io.Reader
|
var r io.Reader
|
||||||
if filepath.IsAbs(filter.URL) {
|
if filepath.IsAbs(flt.URL) {
|
||||||
var f io.ReadCloser
|
var file io.ReadCloser
|
||||||
f, err = os.Open(filter.URL)
|
file, err = os.Open(flt.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return updated, fmt.Errorf("open file: %w", err)
|
return false, fmt.Errorf("open file: %w", err)
|
||||||
}
|
}
|
||||||
defer func() { err = errors.WithDeferred(err, f.Close()) }()
|
defer func() { err = errors.WithDeferred(err, file.Close()) }()
|
||||||
|
|
||||||
reader = f
|
r = file
|
||||||
} else {
|
} else {
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
resp, err = Context.client.Get(filter.URL)
|
resp, err = Context.client.Get(flt.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Couldn't request filter from URL %s, skipping: %s", filter.URL, err)
|
log.Printf("requesting filter from %s, skip: %s", flt.URL, err)
|
||||||
|
|
||||||
return updated, err
|
return false, err
|
||||||
}
|
}
|
||||||
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
log.Printf("Got status code %d from URL %s, skipping", resp.StatusCode, filter.URL)
|
log.Printf("got status code %d from %s, skip", resp.StatusCode, flt.URL)
|
||||||
return updated, fmt.Errorf("got status code != 200: %d", resp.StatusCode)
|
|
||||||
|
return false, fmt.Errorf("got status code != 200: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
reader = resp.Body
|
|
||||||
|
r = resp.Body
|
||||||
}
|
}
|
||||||
|
|
||||||
total, err := f.read(reader, tmpFile, filter)
|
name, rnum, cs, n, err = f.processUpdate(r, tmpFile, flt)
|
||||||
if err != nil {
|
|
||||||
return updated, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract filter name and count number of rules
|
return cs != flt.checksum, err
|
||||||
_, _ = tmpFile.Seek(0, io.SeekStart)
|
|
||||||
rulesCount, checksum, filterName := f.parseFilterContents(tmpFile)
|
|
||||||
// Check if the filter has been really changed
|
|
||||||
if filter.checksum == checksum {
|
|
||||||
log.Tracef("Filter #%d at URL %s hasn't changed, not updating it", filter.ID, filter.URL)
|
|
||||||
return updated, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Filter %d has been updated: %d bytes, %d rules",
|
|
||||||
filter.ID, total, rulesCount)
|
|
||||||
if len(filter.Name) == 0 {
|
|
||||||
filter.Name = filterName
|
|
||||||
}
|
|
||||||
filter.RulesCount = rulesCount
|
|
||||||
filter.checksum = checksum
|
|
||||||
filterFilePath := filter.Path()
|
|
||||||
log.Printf("Saving filter %d contents to: %s", filter.ID, filterFilePath)
|
|
||||||
|
|
||||||
// Closing the file before renaming it is necessary on Windows
|
|
||||||
_ = tmpFile.Close()
|
|
||||||
err = os.Rename(tmpFile.Name(), filterFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return updated, err
|
|
||||||
}
|
|
||||||
tmpFile = nil
|
|
||||||
updated = true
|
|
||||||
|
|
||||||
return updated, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loads filter contents from the file in dataDir
|
// loads filter contents from the file in dataDir
|
||||||
|
|||||||
@@ -1,76 +1,118 @@
|
|||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testStartFilterListener(t *testing.T) net.Listener {
|
const testFltsFileName = "1.txt"
|
||||||
|
|
||||||
|
func testStartFilterListener(t *testing.T, fltContent *[]byte) (l net.Listener) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
n, werr := w.Write(*fltContent)
|
||||||
|
require.NoError(t, werr)
|
||||||
|
require.Equal(t, len(*fltContent), n)
|
||||||
|
})
|
||||||
|
|
||||||
|
var err error
|
||||||
|
l, err = net.Listen("tcp", ":0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = http.Serve(l, h)
|
||||||
|
}()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, l.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilters(t *testing.T) {
|
||||||
const content = `||example.org^$third-party
|
const content = `||example.org^$third-party
|
||||||
# Inline comment example
|
# Inline comment example
|
||||||
||example.com^$third-party
|
||example.com^$third-party
|
||||||
0.0.0.0 example.com
|
0.0.0.0 example.com
|
||||||
`
|
`
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
fltContent := []byte(content)
|
||||||
mux.HandleFunc("/filters/1.txt", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, werr := w.Write([]byte(content))
|
|
||||||
assert.Nil(t, werr)
|
|
||||||
})
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", ":0")
|
l := testStartFilterListener(t, &fltContent)
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_ = http.Serve(listener, mux)
|
|
||||||
}()
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
assert.Nil(t, listener.Close())
|
|
||||||
})
|
|
||||||
|
|
||||||
return listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilters(t *testing.T) {
|
|
||||||
l := testStartFilterListener(t)
|
|
||||||
dir := t.TempDir()
|
|
||||||
|
|
||||||
Context = homeContext{
|
Context = homeContext{
|
||||||
workDir: dir,
|
workDir: t.TempDir(),
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Timeout: 5 * time.Second,
|
Timeout: 5 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Context.filters.Init()
|
Context.filters.Init()
|
||||||
|
|
||||||
f := filter{
|
f := &filter{
|
||||||
URL: fmt.Sprintf("http://127.0.0.1:%d/filters/1.txt", l.Addr().(*net.TCPAddr).Port),
|
URL: (&url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: (&netutil.IPPort{
|
||||||
|
IP: net.IP{127, 0, 0, 1},
|
||||||
|
Port: l.Addr().(*net.TCPAddr).Port,
|
||||||
|
}).String(),
|
||||||
|
Path: path.Join(filterDir, testFltsFileName),
|
||||||
|
}).String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download.
|
updateAndAssert := func(t *testing.T, want require.BoolAssertionFunc, wantRulesCount int) {
|
||||||
ok, err := Context.filters.update(&f)
|
ok, err := Context.filters.update(f)
|
||||||
require.Nil(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, ok)
|
want(t, ok)
|
||||||
assert.Equal(t, 3, f.RulesCount)
|
|
||||||
|
|
||||||
// Refresh.
|
assert.Equal(t, wantRulesCount, f.RulesCount)
|
||||||
ok, err = Context.filters.update(&f)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
err = Context.filters.load(&f)
|
var dir []fs.DirEntry
|
||||||
require.Nil(t, err)
|
dir, err = os.ReadDir(filepath.Join(Context.getDataDir(), filterDir))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
f.unload()
|
assert.Len(t, dir, 1)
|
||||||
require.Nil(t, os.Remove(f.Path()))
|
|
||||||
|
require.FileExists(t, f.Path())
|
||||||
|
|
||||||
|
err = Context.filters.load(f)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("download", func(t *testing.T) {
|
||||||
|
updateAndAssert(t, require.True, 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("refresh_idle", func(t *testing.T) {
|
||||||
|
updateAndAssert(t, require.False, 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("refresh_actually", func(t *testing.T) {
|
||||||
|
fltContent = []byte(`||example.com^`)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
fltContent = []byte(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
updateAndAssert(t, require.True, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("load_unload", func(t *testing.T) {
|
||||||
|
err := Context.filters.load(f)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
f.unload()
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, os.Remove(f.Path()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,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 inteface, and serve only first-run html
|
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
|
disableUpdate bool // If set, don't check for updates
|
||||||
controlLock sync.Mutex
|
controlLock sync.Mutex
|
||||||
@@ -116,13 +116,6 @@ func Main(clientBuildFS fs.FS) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if args.serviceControlAction != "" {
|
if args.serviceControlAction != "" {
|
||||||
// TODO(a.garipov): github.com/kardianos/service doesn't seem to
|
|
||||||
// support OpenBSD currently. Either patch it to do so or make
|
|
||||||
// our own implementation of the service.System interface.
|
|
||||||
if runtime.GOOS == "openbsd" {
|
|
||||||
log.Fatal("service actions are not supported on openbsd, see issue 3226")
|
|
||||||
}
|
|
||||||
|
|
||||||
handleServiceControlAction(args, clientBuildFS)
|
handleServiceControlAction(args, clientBuildFS)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -356,7 +349,7 @@ func run(args options, clientBuildFS fs.FS) {
|
|||||||
|
|
||||||
setupContext(args)
|
setupContext(args)
|
||||||
|
|
||||||
err = configureOS(&config)
|
err = configureOS(config)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
// clients package uses filtering package's static data (filtering.BlockedSvcKnown()),
|
// clients package uses filtering package's static data (filtering.BlockedSvcKnown()),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package home
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
@@ -14,31 +15,54 @@ import (
|
|||||||
"howett.net/plist"
|
"howett.net/plist"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// dnsSettings is the DNSSetting.DNSSettings mobileconfig profile.
|
||||||
|
//
|
||||||
|
// See https://developer.apple.com/documentation/devicemanagement/dnssettings/dnssettings.
|
||||||
type dnsSettings struct {
|
type dnsSettings struct {
|
||||||
|
// DNSProtocol is the required protocol to be used. The valid values
|
||||||
|
// are "HTTPS" and "TLS".
|
||||||
DNSProtocol string
|
DNSProtocol string
|
||||||
ServerURL string `plist:",omitempty"`
|
|
||||||
ServerName string `plist:",omitempty"`
|
// ServerURL is the URI template of the DoH server. It must be empty if
|
||||||
clientID string
|
// DNSProtocol is not "HTTPS".
|
||||||
|
ServerURL string `plist:",omitempty"`
|
||||||
|
|
||||||
|
// ServerName is the hostname of the DoT server. It must be empty if
|
||||||
|
// DNSProtocol is not "TLS".
|
||||||
|
ServerName string `plist:",omitempty"`
|
||||||
|
|
||||||
|
// ServerAddresses is a list IP addresses of the server.
|
||||||
|
ServerAddresses []net.IP `plist:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// payloadContent is a Device Management Profile payload.
|
||||||
|
//
|
||||||
|
// See https://developer.apple.com/documentation/devicemanagement/configuring_multiple_devices_using_profiles#3234127.
|
||||||
type payloadContent struct {
|
type payloadContent struct {
|
||||||
Name string
|
DNSSettings *dnsSettings
|
||||||
PayloadDescription string
|
|
||||||
PayloadDisplayName string
|
|
||||||
PayloadIdentifier string
|
|
||||||
PayloadType string
|
PayloadType string
|
||||||
|
PayloadIdentifier string
|
||||||
PayloadUUID string
|
PayloadUUID string
|
||||||
DNSSettings dnsSettings
|
PayloadDisplayName string
|
||||||
|
PayloadDescription string
|
||||||
PayloadVersion int
|
PayloadVersion int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dnsSettingsPayloadType is the payload type for a DNSSettings profile.
|
||||||
|
const dnsSettingsPayloadType = "com.apple.dnsSettings.managed"
|
||||||
|
|
||||||
|
// mobileConfig contains the TopLevel properties for configuring Device
|
||||||
|
// Management Profiles.
|
||||||
|
//
|
||||||
|
// See https://developer.apple.com/documentation/devicemanagement/toplevel.
|
||||||
type mobileConfig struct {
|
type mobileConfig struct {
|
||||||
PayloadDescription string
|
PayloadDescription string
|
||||||
PayloadDisplayName string
|
PayloadDisplayName string
|
||||||
PayloadIdentifier string
|
PayloadIdentifier string
|
||||||
PayloadType string
|
PayloadType string
|
||||||
PayloadUUID string
|
PayloadUUID string
|
||||||
PayloadContent []payloadContent
|
PayloadContent []*payloadContent
|
||||||
PayloadVersion int
|
PayloadVersion int
|
||||||
PayloadRemovalDisallowed bool
|
PayloadRemovalDisallowed bool
|
||||||
}
|
}
|
||||||
@@ -52,7 +76,7 @@ const (
|
|||||||
dnsProtoTLS = "TLS"
|
dnsProtoTLS = "TLS"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getMobileConfig(d dnsSettings) ([]byte, error) {
|
func encodeMobileConfig(d *dnsSettings, clientID string) ([]byte, error) {
|
||||||
var dspName string
|
var dspName string
|
||||||
switch proto := d.DNSProtocol; proto {
|
switch proto := d.DNSProtocol; proto {
|
||||||
case dnsProtoHTTPS:
|
case dnsProtoHTTPS:
|
||||||
@@ -60,41 +84,41 @@ func getMobileConfig(d dnsSettings) ([]byte, error) {
|
|||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: schemeHTTPS,
|
Scheme: schemeHTTPS,
|
||||||
Host: d.ServerName,
|
Host: d.ServerName,
|
||||||
Path: path.Join("/dns-query", d.clientID),
|
Path: path.Join("/dns-query", clientID),
|
||||||
}
|
}
|
||||||
d.ServerURL = u.String()
|
d.ServerURL = u.String()
|
||||||
|
|
||||||
// Empty the ServerName field since it is only must be presented
|
// Empty the ServerName field since it is only must be presented
|
||||||
// in DNS-over-TLS configuration.
|
// in DNS-over-TLS configuration.
|
||||||
//
|
|
||||||
// See https://developer.apple.com/documentation/devicemanagement/dnssettings/dnssettings.
|
|
||||||
d.ServerName = ""
|
d.ServerName = ""
|
||||||
case dnsProtoTLS:
|
case dnsProtoTLS:
|
||||||
dspName = fmt.Sprintf("%s DoT", d.ServerName)
|
dspName = fmt.Sprintf("%s DoT", d.ServerName)
|
||||||
if d.clientID != "" {
|
if clientID != "" {
|
||||||
d.ServerName = d.clientID + "." + d.ServerName
|
d.ServerName = clientID + "." + d.ServerName
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("bad dns protocol %q", proto)
|
return nil, fmt.Errorf("bad dns protocol %q", proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := mobileConfig{
|
payloadID := fmt.Sprintf("%s.%s", dnsSettingsPayloadType, genUUIDv4())
|
||||||
PayloadContent: []payloadContent{{
|
data := &mobileConfig{
|
||||||
Name: dspName,
|
PayloadDescription: "Adds AdGuard Home to macOS Big Sur " +
|
||||||
PayloadDescription: "Configures device to use AdGuard Home",
|
"and iOS 14 or newer systems",
|
||||||
PayloadDisplayName: dspName,
|
PayloadDisplayName: dspName,
|
||||||
PayloadIdentifier: fmt.Sprintf("com.apple.dnsSettings.managed.%s", genUUIDv4()),
|
PayloadIdentifier: genUUIDv4(),
|
||||||
PayloadType: "com.apple.dnsSettings.managed",
|
PayloadType: "Configuration",
|
||||||
|
PayloadUUID: genUUIDv4(),
|
||||||
|
PayloadContent: []*payloadContent{{
|
||||||
|
PayloadType: dnsSettingsPayloadType,
|
||||||
|
PayloadIdentifier: payloadID,
|
||||||
PayloadUUID: genUUIDv4(),
|
PayloadUUID: genUUIDv4(),
|
||||||
|
PayloadDisplayName: dspName,
|
||||||
|
PayloadDescription: "Configures device to use AdGuard Home",
|
||||||
PayloadVersion: 1,
|
PayloadVersion: 1,
|
||||||
DNSSettings: d,
|
DNSSettings: d,
|
||||||
}},
|
}},
|
||||||
PayloadDescription: "Adds AdGuard Home to Big Sur and iOS 14 or newer systems",
|
|
||||||
PayloadDisplayName: dspName,
|
|
||||||
PayloadIdentifier: genUUIDv4(),
|
|
||||||
PayloadRemovalDisallowed: false,
|
|
||||||
PayloadType: "Configuration",
|
|
||||||
PayloadUUID: genUUIDv4(),
|
|
||||||
PayloadVersion: 1,
|
PayloadVersion: 1,
|
||||||
|
PayloadRemovalDisallowed: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
return plist.MarshalIndent(data, plist.XMLFormat, "\t")
|
return plist.MarshalIndent(data, plist.XMLFormat, "\t")
|
||||||
@@ -133,13 +157,22 @@ func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d := dnsSettings{
|
dnsIPs, err := collectDNSIPs()
|
||||||
DNSProtocol: dnsp,
|
if err != nil {
|
||||||
ServerName: host,
|
// Don't add a lot of formatting, since the error is already
|
||||||
clientID: clientID,
|
// wrapped by collectDNSIPs.
|
||||||
|
respondJSONError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mobileconfig, err := getMobileConfig(d)
|
d := &dnsSettings{
|
||||||
|
DNSProtocol: dnsp,
|
||||||
|
ServerName: host,
|
||||||
|
ServerAddresses: dnsIPs,
|
||||||
|
}
|
||||||
|
|
||||||
|
mobileconfig, err := encodeMobileConfig(d, clientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondJSONError(w, http.StatusInternalServerError, err.Error())
|
respondJSONError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
|
||||||
@@ -170,3 +203,25 @@ func handleMobileConfigDoH(w http.ResponseWriter, r *http.Request) {
|
|||||||
func handleMobileConfigDoT(w http.ResponseWriter, r *http.Request) {
|
func handleMobileConfigDoT(w http.ResponseWriter, r *http.Request) {
|
||||||
handleMobileConfig(w, r, dnsProtoTLS)
|
handleMobileConfig(w, r, dnsProtoTLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collectDNSIPs returns a slice of IP addresses the server is listening
|
||||||
|
// on, including the addresses on all interfaces in cases of unspecified IPs but
|
||||||
|
// excluding loopback addresses.
|
||||||
|
func collectDNSIPs() (ips []net.IP, err error) {
|
||||||
|
// TODO(a.garipov): This really shouldn't be a function that parses
|
||||||
|
// a list of strings. Instead, we need a function that returns this
|
||||||
|
// data as []net.IP or []*netutil.IPPort. Maybe someday.
|
||||||
|
addrs, err := collectDNSAddresses()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ip := net.ParseIP(addr)
|
||||||
|
if ip != nil && !ip.IsLoopback() {
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,16 +3,42 @@ package home
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"howett.net/plist"
|
"howett.net/plist"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// setupDNSIPs is a helper that sets up the server IP address configuration for
|
||||||
|
// tests and also tears it down in a cleanup function.
|
||||||
|
func setupDNSIPs(t testing.TB) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
prevConfig := config
|
||||||
|
prevTLS := Context.tls
|
||||||
|
t.Cleanup(func() {
|
||||||
|
config = prevConfig
|
||||||
|
Context.tls = prevTLS
|
||||||
|
})
|
||||||
|
|
||||||
|
config = &configuration{
|
||||||
|
DNS: dnsConfig{
|
||||||
|
BindHosts: []net.IP{netutil.IPv4Zero()},
|
||||||
|
Port: defaultPortDNS,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Context.tls = &TLSMod{}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandleMobileConfigDoH(t *testing.T) {
|
func TestHandleMobileConfigDoH(t *testing.T) {
|
||||||
|
setupDNSIPs(t)
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig?host=example.org", nil)
|
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig?host=example.org", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -25,11 +51,16 @@ func TestHandleMobileConfigDoH(t *testing.T) {
|
|||||||
var mc mobileConfig
|
var mc mobileConfig
|
||||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, mc.PayloadContent, 1)
|
require.Len(t, mc.PayloadContent, 1)
|
||||||
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name)
|
|
||||||
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
|
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
|
||||||
assert.Equal(t, "https://example.org/dns-query", mc.PayloadContent[0].DNSSettings.ServerURL)
|
|
||||||
|
s := mc.PayloadContent[0].DNSSettings
|
||||||
|
require.NotNil(t, s)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, s.ServerAddresses)
|
||||||
|
assert.Empty(t, s.ServerName)
|
||||||
|
assert.Equal(t, "https://example.org/dns-query", s.ServerURL)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("error_no_host", func(t *testing.T) {
|
t.Run("error_no_host", func(t *testing.T) {
|
||||||
@@ -66,15 +97,22 @@ func TestHandleMobileConfigDoH(t *testing.T) {
|
|||||||
var mc mobileConfig
|
var mc mobileConfig
|
||||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, mc.PayloadContent, 1)
|
require.Len(t, mc.PayloadContent, 1)
|
||||||
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name)
|
|
||||||
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
|
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
|
||||||
assert.Equal(t, "https://example.org/dns-query/cli42", mc.PayloadContent[0].DNSSettings.ServerURL)
|
|
||||||
|
s := mc.PayloadContent[0].DNSSettings
|
||||||
|
require.NotNil(t, s)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, s.ServerAddresses)
|
||||||
|
assert.Empty(t, s.ServerName)
|
||||||
|
assert.Equal(t, "https://example.org/dns-query/cli42", s.ServerURL)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleMobileConfigDoT(t *testing.T) {
|
func TestHandleMobileConfigDoT(t *testing.T) {
|
||||||
|
setupDNSIPs(t)
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig?host=example.org", nil)
|
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig?host=example.org", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -87,11 +125,16 @@ func TestHandleMobileConfigDoT(t *testing.T) {
|
|||||||
var mc mobileConfig
|
var mc mobileConfig
|
||||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, mc.PayloadContent, 1)
|
require.Len(t, mc.PayloadContent, 1)
|
||||||
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name)
|
|
||||||
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName)
|
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName)
|
||||||
assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
|
||||||
|
s := mc.PayloadContent[0].DNSSettings
|
||||||
|
require.NotNil(t, s)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, s.ServerAddresses)
|
||||||
|
assert.Equal(t, "example.org", s.ServerName)
|
||||||
|
assert.Empty(t, s.ServerURL)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("error_no_host", func(t *testing.T) {
|
t.Run("error_no_host", func(t *testing.T) {
|
||||||
@@ -129,10 +172,15 @@ func TestHandleMobileConfigDoT(t *testing.T) {
|
|||||||
var mc mobileConfig
|
var mc mobileConfig
|
||||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, mc.PayloadContent, 1)
|
require.Len(t, mc.PayloadContent, 1)
|
||||||
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name)
|
|
||||||
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName)
|
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName)
|
||||||
assert.Equal(t, "cli42.example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
|
||||||
|
s := mc.PayloadContent[0].DNSSettings
|
||||||
|
require.NotNil(t, s)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, s.ServerAddresses)
|
||||||
|
assert.Equal(t, "cli42.example.org", s.ServerName)
|
||||||
|
assert.Empty(t, s.ServerURL)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(a.garipov): Move shell templates into actual files. Either during the
|
// TODO(a.garipov): Consider moving the shell templates into actual files and
|
||||||
// v0.106.0 cycle using packr or during the following cycle using go:embed.
|
// using go:embed instead of using large string constants.
|
||||||
|
|
||||||
const (
|
const (
|
||||||
launchdStdoutPath = "/var/log/AdGuardHome.stdout.log"
|
launchdStdoutPath = "/var/log/AdGuardHome.stdout.log"
|
||||||
@@ -26,33 +28,37 @@ const (
|
|||||||
serviceDescription = "AdGuard Home: Network-level blocker"
|
serviceDescription = "AdGuard Home: Network-level blocker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Represents the program that will be launched by a service or daemon
|
// program represents the program that will be launched by as a service or a
|
||||||
|
// daemon.
|
||||||
type program struct {
|
type program struct {
|
||||||
clientBuildFS fs.FS
|
clientBuildFS fs.FS
|
||||||
opts options
|
opts options
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start should quickly start the program
|
// Start implements service.Interface interface for *program.
|
||||||
func (p *program) Start(s service.Service) error {
|
func (p *program) Start(_ service.Service) (err error) {
|
||||||
// Start should not block. Do the actual work async.
|
// Start should not block. Do the actual work async.
|
||||||
args := p.opts
|
args := p.opts
|
||||||
args.runningAsService = true
|
args.runningAsService = true
|
||||||
|
|
||||||
go run(args, p.clientBuildFS)
|
go run(args, p.clientBuildFS)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the program
|
// Stop implements service.Interface interface for *program.
|
||||||
func (p *program) Stop(s service.Service) error {
|
func (p *program) Stop(_ service.Service) error {
|
||||||
// Stop should not block. Return with a few seconds.
|
// Stop should not block. Return with a few seconds.
|
||||||
if Context.appSignalChannel == nil {
|
if Context.appSignalChannel == nil {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.appSignalChannel <- syscall.SIGINT
|
Context.appSignalChannel <- syscall.SIGINT
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// svcStatus check the service's status.
|
// svcStatus returns the service's status.
|
||||||
//
|
//
|
||||||
// On OpenWrt, the service utility may not exist. We use our service script
|
// On OpenWrt, the service utility may not exist. We use our service script
|
||||||
// directly in this case.
|
// directly in this case.
|
||||||
@@ -87,8 +93,8 @@ func svcAction(s service.Service, action string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send SIGHUP to a process with ID taken from our pid-file
|
// Send SIGHUP to a process with PID taken from our .pid file. If it doesn't
|
||||||
// If pid-file doesn't exist, find our PID using 'ps' command
|
// exist, find our PID using 'ps' command.
|
||||||
func sendSigReload() {
|
func sendSigReload() {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
log.Error("not implemented on windows")
|
log.Error("not implemented on windows")
|
||||||
@@ -97,41 +103,48 @@ func sendSigReload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pidfile := fmt.Sprintf("/var/run/%s.pid", serviceName)
|
pidfile := fmt.Sprintf("/var/run/%s.pid", serviceName)
|
||||||
|
var pid int
|
||||||
data, err := os.ReadFile(pidfile)
|
data, err := os.ReadFile(pidfile)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
var code int
|
if pid, err = aghos.PIDByCommand(serviceName, os.Getpid()); err != nil {
|
||||||
var psdata string
|
log.Error("finding AdGuardHome process: %s", err)
|
||||||
code, psdata, err = aghos.RunCommand("ps", "-C", serviceName, "-o", "pid=")
|
|
||||||
if err != nil || code != 0 {
|
|
||||||
log.Error("finding AdGuardHome process: code: %d, error: %s", code, err)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data = []byte(psdata)
|
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.Error("reading pid file %s: %s", pidfile, err)
|
log.Error("reading pid file %s: %s", pidfile, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
} else {
|
||||||
|
parts := strings.SplitN(string(data), "\n", 2)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
log.Error("can't read pid file %s: bad value", pidfile)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pid, err = strconv.Atoi(strings.TrimSpace(parts[0])); err != nil {
|
||||||
|
log.Error("can't read pid file %s: %s", pidfile, err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(string(data), "\n", 2)
|
var proc *os.Process
|
||||||
if len(parts) == 0 {
|
if proc, err = os.FindProcess(pid); err != nil {
|
||||||
log.Error("Can't read PID file %s: bad value", pidfile)
|
log.Error("can't send signal to pid %d: %s", pid, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pid, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
if err = proc.Signal(syscall.SIGHUP); err != nil {
|
||||||
if err != nil {
|
log.Error("Can't send signal to pid %d: %s", pid, err)
|
||||||
log.Error("Can't read PID file %s: %s", pidfile, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = aghos.SendProcessSignal(pid, syscall.SIGHUP)
|
|
||||||
if err != nil {
|
log.Debug("sent signal to PID %d", pid)
|
||||||
log.Error("Can't send signal to PID %d: %s", pid, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debug("Sent signal to PID %d", pid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleServiceControlAction one of the possible control actions:
|
// handleServiceControlAction one of the possible control actions:
|
||||||
@@ -145,11 +158,16 @@ func sendSigReload() {
|
|||||||
// it is specified when we register a service, and it indicates to the app
|
// it is specified when we register a service, and it indicates to the app
|
||||||
// that it is being run as a service/daemon.
|
// that it is being run as a service/daemon.
|
||||||
func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
|
func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
|
||||||
|
// Call chooseSystem expicitly to introduce OpenBSD support for service
|
||||||
|
// package. It's a noop for other GOOS values.
|
||||||
|
chooseSystem()
|
||||||
|
|
||||||
action := opts.serviceControlAction
|
action := opts.serviceControlAction
|
||||||
log.Printf("Service control action: %s", action)
|
log.Printf("Service control action: %s", action)
|
||||||
|
|
||||||
if action == "reload" {
|
if action == "reload" {
|
||||||
sendSigReload()
|
sendSigReload()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +175,7 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to find the path to the current directory")
|
log.Fatal("Unable to find the path to the current directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
runOpts := opts
|
runOpts := opts
|
||||||
runOpts.serviceControlAction = "run"
|
runOpts.serviceControlAction = "run"
|
||||||
svcConfig := &service.Config{
|
svcConfig := &service.Config{
|
||||||
@@ -167,39 +186,39 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
|
|||||||
Arguments: serialize(runOpts),
|
Arguments: serialize(runOpts),
|
||||||
}
|
}
|
||||||
configureService(svcConfig)
|
configureService(svcConfig)
|
||||||
|
|
||||||
prg := &program{
|
prg := &program{
|
||||||
clientBuildFS: clientBuildFS,
|
clientBuildFS: clientBuildFS,
|
||||||
opts: runOpts,
|
opts: runOpts,
|
||||||
}
|
}
|
||||||
s, err := service.New(prg, svcConfig)
|
var s service.Service
|
||||||
if err != nil {
|
if s, err = service.New(prg, svcConfig); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == "status" {
|
switch action {
|
||||||
|
case "status":
|
||||||
handleServiceStatusCommand(s)
|
handleServiceStatusCommand(s)
|
||||||
} else if action == "run" {
|
case "run":
|
||||||
err = s.Run()
|
if err = s.Run(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to run service: %s", err)
|
log.Fatalf("Failed to run service: %s", err)
|
||||||
}
|
}
|
||||||
} else if action == "install" {
|
case "install":
|
||||||
initConfigFilename(opts)
|
initConfigFilename(opts)
|
||||||
initWorkingDir(opts)
|
initWorkingDir(opts)
|
||||||
handleServiceInstallCommand(s)
|
handleServiceInstallCommand(s)
|
||||||
} else if action == "uninstall" {
|
case "uninstall":
|
||||||
handleServiceUninstallCommand(s)
|
handleServiceUninstallCommand(s)
|
||||||
} else {
|
default:
|
||||||
err = svcAction(s, action)
|
if err = svcAction(s, action); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Action %s has been done successfully on %s", action, service.ChosenSystem().String())
|
log.Printf("action %s has been done successfully on %s", action, service.ChosenSystem())
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleServiceStatusCommand handles service "status" command
|
// handleServiceStatusCommand handles service "status" command.
|
||||||
func handleServiceStatusCommand(s service.Service) {
|
func handleServiceStatusCommand(s service.Service) {
|
||||||
status, errSt := svcStatus(s)
|
status, errSt := svcStatus(s)
|
||||||
if errSt != nil {
|
if errSt != nil {
|
||||||
@@ -224,15 +243,16 @@ func handleServiceInstallCommand(s service.Service) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if aghos.IsOpenWrt() {
|
if aghos.IsOpenWrt() {
|
||||||
// On OpenWrt it is important to run enable after the service installation
|
// On OpenWrt it is important to run enable after the service
|
||||||
// Otherwise, the service won't start on the system startup
|
// installation Otherwise, the service won't start on the system
|
||||||
|
// startup.
|
||||||
_, err = runInitdCommand("enable")
|
_, err = runInitdCommand("enable")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start automatically after install
|
// Start automatically after install.
|
||||||
err = svcAction(s, "start")
|
err = svcAction(s, "start")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to start the service: %s", err)
|
log.Fatalf("Failed to start the service: %s", err)
|
||||||
@@ -260,14 +280,13 @@ func handleServiceUninstallCommand(s service.Service) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := svcAction(s, "uninstall")
|
if err := svcAction(s, "uninstall"); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
// Remove log files on cleanup and log errors.
|
// Remove log files on cleanup and log errors.
|
||||||
err = os.Remove(launchdStdoutPath)
|
err := os.Remove(launchdStdoutPath)
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
log.Printf("removing stdout file: %s", err)
|
log.Printf("removing stdout file: %s", err)
|
||||||
}
|
}
|
||||||
@@ -291,21 +310,32 @@ func configureService(c *service.Config) {
|
|||||||
// This key is used to start the job as soon as it has been loaded. For daemons this means execution at boot time, for agents execution at login.
|
// This key is used to start the job as soon as it has been loaded. For daemons this means execution at boot time, for agents execution at login.
|
||||||
c.Option["RunAtLoad"] = true
|
c.Option["RunAtLoad"] = true
|
||||||
|
|
||||||
// POSIX
|
// POSIX / systemd
|
||||||
|
|
||||||
// Redirect StdErr & StdOut to files.
|
// Redirect stderr and stdout to files. Make sure we always restart.
|
||||||
c.Option["LogOutput"] = true
|
c.Option["LogOutput"] = true
|
||||||
|
c.Option["Restart"] = "always"
|
||||||
|
|
||||||
// Use modified service file templates.
|
// Start only once network is up on Linux/systemd.
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
c.Dependencies = []string{
|
||||||
|
"After=syslog.target network-online.target",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the modified service file templates.
|
||||||
c.Option["SystemdScript"] = systemdScript
|
c.Option["SystemdScript"] = systemdScript
|
||||||
c.Option["SysvScript"] = sysvScript
|
c.Option["SysvScript"] = sysvScript
|
||||||
|
|
||||||
// On OpenWrt we're using a different type of sysvScript.
|
// Use different scripts on OpenWrt and FreeBSD.
|
||||||
if aghos.IsOpenWrt() {
|
if aghos.IsOpenWrt() {
|
||||||
c.Option["SysvScript"] = openWrtScript
|
c.Option["SysvScript"] = openWrtScript
|
||||||
} else if runtime.GOOS == "freebsd" {
|
} else if runtime.GOOS == "freebsd" {
|
||||||
c.Option["SysvScript"] = freeBSDScript
|
c.Option["SysvScript"] = freeBSDScript
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.Option["RunComScript"] = openBSDScript
|
||||||
|
c.Option["SvcInfo"] = fmt.Sprintf("%s %s", version.Full(), time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
// runInitdCommand runs init.d service command
|
// runInitdCommand runs init.d service command
|
||||||
@@ -346,17 +376,26 @@ var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
|
|||||||
</plist>
|
</plist>
|
||||||
`
|
`
|
||||||
|
|
||||||
// Note: we should keep it in sync with the template from service_systemd_linux.go file
|
// systemdScript is an improved version of the systemd script originally from
|
||||||
// Add "After=" setting for systemd service file, because we must be started only after network is online
|
// the systemdScript constant in file service_systemd_linux.go in module
|
||||||
// Set "RestartSec" to 10
|
// github.com/kardianos/service. The following changes have been made:
|
||||||
|
//
|
||||||
|
// 1. The RestartSec setting is set to a lower value of 10 to make sure we
|
||||||
|
// always restart quickly.
|
||||||
|
//
|
||||||
|
// 2. The ExecStartPre setting is added to make sure that the log directory is
|
||||||
|
// always created to prevent the 209/STDOUT errors.
|
||||||
|
//
|
||||||
const systemdScript = `[Unit]
|
const systemdScript = `[Unit]
|
||||||
Description={{.Description}}
|
Description={{.Description}}
|
||||||
ConditionFileIsExecutable={{.Path|cmdEscape}}
|
ConditionFileIsExecutable={{.Path|cmdEscape}}
|
||||||
After=syslog.target network-online.target
|
{{range $i, $dep := .Dependencies}}
|
||||||
|
{{$dep}} {{end}}
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
StartLimitInterval=5
|
StartLimitInterval=5
|
||||||
StartLimitBurst=10
|
StartLimitBurst=10
|
||||||
|
ExecStartPre=/bin/mkdir -p /var/log/
|
||||||
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
|
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
|
||||||
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
|
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
|
||||||
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
|
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
|
||||||
@@ -367,7 +406,9 @@ ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
|
|||||||
StandardOutput=file:/var/log/{{.Name}}.out
|
StandardOutput=file:/var/log/{{.Name}}.out
|
||||||
StandardError=file:/var/log/{{.Name}}.err
|
StandardError=file:/var/log/{{.Name}}.err
|
||||||
{{- end}}
|
{{- end}}
|
||||||
Restart=always
|
{{if gt .LimitNOFILE -1 }}LimitNOFILE={{.LimitNOFILE}}{{end}}
|
||||||
|
{{if .Restart}}Restart={{.Restart}}{{end}}
|
||||||
|
{{if .SuccessExitStatus}}SuccessExitStatus={{.SuccessExitStatus}}{{end}}
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
EnvironmentFile=-/etc/sysconfig/{{.Name}}
|
EnvironmentFile=-/etc/sysconfig/{{.Name}}
|
||||||
|
|
||||||
@@ -541,6 +582,20 @@ name="{{.Name}}"
|
|||||||
{{.Name}}_user="root"
|
{{.Name}}_user="root"
|
||||||
pidfile="/var/run/${name}.pid"
|
pidfile="/var/run/${name}.pid"
|
||||||
command="/usr/sbin/daemon"
|
command="/usr/sbin/daemon"
|
||||||
command_args="-P ${pidfile} -f -r {{.WorkingDirectory}}/{{.Name}}"
|
command_args="-p ${pidfile} -f -r {{.WorkingDirectory}}/{{.Name}}"
|
||||||
run_rc_command "$1"
|
run_rc_command "$1"
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const openBSDScript = `#!/bin/sh
|
||||||
|
#
|
||||||
|
# $OpenBSD: {{ .SvcInfo }}
|
||||||
|
|
||||||
|
daemon="{{.Path}}"
|
||||||
|
daemon_flags={{ .Arguments | args }}
|
||||||
|
|
||||||
|
. /etc/rc.d/rc.subr
|
||||||
|
|
||||||
|
rc_bg=YES
|
||||||
|
|
||||||
|
rc_cmd $1
|
||||||
|
`
|
||||||
|
|||||||
432
internal/home/service_openbsd.go
Normal file
432
internal/home/service_openbsd.go
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
//go:build openbsd
|
||||||
|
// +build openbsd
|
||||||
|
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
|
"github.com/kardianos/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenBSD Service Implementation
|
||||||
|
//
|
||||||
|
// The file contains OpenBSD implementations for service.System and
|
||||||
|
// service.Service interfaces. It uses the default approach for RunCom-based
|
||||||
|
// services systems, e.g. rc.d script. It's written as if it was in a separate
|
||||||
|
// package and has only one internal dependency.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Perhaps, file a PR to github.com/kardianos/service.
|
||||||
|
|
||||||
|
// sysVersion is the version of local service.System interface
|
||||||
|
// implementation.
|
||||||
|
const sysVersion = "openbsd-runcom"
|
||||||
|
|
||||||
|
func chooseSystem() {
|
||||||
|
service.ChooseSystem(openbsdSystem{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// openbsdSystem is the service.System to be used on the OpenBSD.
|
||||||
|
type openbsdSystem struct{}
|
||||||
|
|
||||||
|
// String implements service.System interface for openbsdSystem.
|
||||||
|
func (openbsdSystem) String() string {
|
||||||
|
return sysVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect implements service.System interface for openbsdSystem.
|
||||||
|
func (openbsdSystem) Detect() (ok bool) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interactive implements service.System interface for openbsdSystem.
|
||||||
|
func (openbsdSystem) Interactive() (ok bool) {
|
||||||
|
return os.Getppid() != 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// New implements service.System interface for openbsdSystem.
|
||||||
|
func (openbsdSystem) New(i service.Interface, c *service.Config) (s service.Service, err error) {
|
||||||
|
return &openbsdRunComService{
|
||||||
|
i: i,
|
||||||
|
cfg: c,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openbsdRunComService is the RunCom-based service.Service to be used on the
|
||||||
|
// OpenBSD.
|
||||||
|
type openbsdRunComService struct {
|
||||||
|
i service.Interface
|
||||||
|
cfg *service.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (*openbsdRunComService) Platform() (p string) {
|
||||||
|
return "openbsd"
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) String() string {
|
||||||
|
return stringutil.Coalesce(s.cfg.DisplayName, s.cfg.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBool returns the value of the given name from kv, assuming the value is a
|
||||||
|
// boolean. If the value isn't found or is not of the type, the defaultValue is
|
||||||
|
// returned.
|
||||||
|
func getBool(kv service.KeyValue, name string, defaultValue bool) (val bool) {
|
||||||
|
var ok bool
|
||||||
|
if val, ok = kv[name].(bool); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// getString returns the value of the given name from kv, assuming the value is
|
||||||
|
// a string. If the value isn't found or is not of the type, the defaultValue
|
||||||
|
// is returned.
|
||||||
|
func getString(kv service.KeyValue, name, defaultValue string) (val string) {
|
||||||
|
var ok bool
|
||||||
|
if val, ok = kv[name].(string); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFuncNiladic returns the value of the given name from kv, assuming the
|
||||||
|
// value is a func(). If the value isn't found or is not of the type, the
|
||||||
|
// defaultValue is returned.
|
||||||
|
func getFuncNiladic(kv service.KeyValue, name string, defaultValue func()) (val func()) {
|
||||||
|
var ok bool
|
||||||
|
if val, ok = kv[name].(func()); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// optionUserService is the UserService option name.
|
||||||
|
optionUserService = "UserService"
|
||||||
|
|
||||||
|
// optionUserServiceDefault is the UserService option default value.
|
||||||
|
optionUserServiceDefault = false
|
||||||
|
|
||||||
|
// errNoUserServiceRunCom is returned when the service uses some custom
|
||||||
|
// path to script.
|
||||||
|
errNoUserServiceRunCom errors.Error = "user services are not supported on " + sysVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
// scriptPath returns the absolute path to the script. It's commonly used to
|
||||||
|
// send commands to the service.
|
||||||
|
func (s *openbsdRunComService) scriptPath() (cp string, err error) {
|
||||||
|
if getBool(s.cfg.Option, optionUserService, optionUserServiceDefault) {
|
||||||
|
return "", errNoUserServiceRunCom
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptPathPref = "/etc/rc.d"
|
||||||
|
|
||||||
|
return filepath.Join(scriptPathPref, s.cfg.Name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// optionRunComScript is the RunCom script option name.
|
||||||
|
optionRunComScript = "RunComScript"
|
||||||
|
|
||||||
|
// runComScript is the default RunCom script.
|
||||||
|
runComScript = `#!/bin/sh
|
||||||
|
#
|
||||||
|
# $OpenBSD: {{ .SvcInfo }}
|
||||||
|
|
||||||
|
daemon="{{.Path}}"
|
||||||
|
daemon_flags={{ .Arguments | args }}
|
||||||
|
|
||||||
|
. /etc/rc.d/rc.subr
|
||||||
|
|
||||||
|
rc_bg=YES
|
||||||
|
|
||||||
|
rc_cmd $1
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
// template returns the script template to put into rc.d.
|
||||||
|
func (s *openbsdRunComService) template() (t *template.Template) {
|
||||||
|
tf := map[string]interface{}{
|
||||||
|
"args": func(sl []string) string {
|
||||||
|
return `"` + strings.Join(sl, " ") + `"`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.Must(template.New("").Funcs(tf).Parse(getString(
|
||||||
|
s.cfg.Option,
|
||||||
|
optionRunComScript,
|
||||||
|
runComScript,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// execPath returns the absolute path to the excutable to be run as a service.
|
||||||
|
func (s *openbsdRunComService) execPath() (path string, err error) {
|
||||||
|
if c := s.cfg; c != nil && len(c.Executable) != 0 {
|
||||||
|
return filepath.Abs(c.Executable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if path, err = os.Executable(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Abs(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// annotate wraps errors.Annotate applying a common error format.
|
||||||
|
func (s *openbsdRunComService) annotate(action string, err error) (annotated error) {
|
||||||
|
return errors.Annotate(err, "%s %s %s service: %w", action, sysVersion, s.cfg.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Install() (err error) {
|
||||||
|
defer func() { err = s.annotate("installing", err) }()
|
||||||
|
|
||||||
|
if err = s.writeScript(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.configureSysStartup(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureSysStartup adds s into the group of packages started with system.
|
||||||
|
func (s *openbsdRunComService) configureSysStartup(enable bool) (err error) {
|
||||||
|
cmd := "enable"
|
||||||
|
if !enable {
|
||||||
|
cmd = "disable"
|
||||||
|
}
|
||||||
|
|
||||||
|
var code int
|
||||||
|
code, _, err = aghos.RunCommand("rcctl", cmd, s.cfg.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if code != 0 {
|
||||||
|
return fmt.Errorf("rcctl finished with code %d", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeScript tries to write the script for the service.
|
||||||
|
func (s *openbsdRunComService) writeScript() (err error) {
|
||||||
|
var scriptPath string
|
||||||
|
if scriptPath, err = s.scriptPath(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Stat(scriptPath); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return fmt.Errorf("script already exists at %s", scriptPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var execPath string
|
||||||
|
if execPath, err = s.execPath(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := s.template()
|
||||||
|
f, err := os.Create(scriptPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating rc.d script file: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
err = t.Execute(f, &struct {
|
||||||
|
*service.Config
|
||||||
|
Path string
|
||||||
|
SvcInfo string
|
||||||
|
}{
|
||||||
|
Config: s.cfg,
|
||||||
|
Path: execPath,
|
||||||
|
SvcInfo: getString(s.cfg.Option, "SvcInfo", s.String()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Annotate(
|
||||||
|
os.Chmod(scriptPath, 0o755),
|
||||||
|
"changing rc.d script file permissions: %w",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uninstall implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Uninstall() (err error) {
|
||||||
|
defer func() { err = s.annotate("uninstalling", err) }()
|
||||||
|
|
||||||
|
if err = s.configureSysStartup(false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var scriptPath string
|
||||||
|
if scriptPath, err = s.scriptPath(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.Remove(scriptPath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
return service.ErrNotInstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Annotate(err, "removing rc.d script: %w")
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionRunWait is the name of the option associated with function which waits
|
||||||
|
// for the service to be stopped.
|
||||||
|
const optionRunWait = "RunWait"
|
||||||
|
|
||||||
|
// runWait is the default function to wait for service to be stopped.
|
||||||
|
func runWait() {
|
||||||
|
sigChan := make(chan os.Signal, 3)
|
||||||
|
signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
|
||||||
|
<-sigChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Run() (err error) {
|
||||||
|
if err = s.i.Start(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
getFuncNiladic(s.cfg.Option, optionRunWait, runWait)()
|
||||||
|
|
||||||
|
return s.i.Stop(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCom calls the script with the specified cmd.
|
||||||
|
func (s *openbsdRunComService) runCom(cmd string) (out string, err error) {
|
||||||
|
var scriptPath string
|
||||||
|
if scriptPath, err = s.scriptPath(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(e.burkov): It's possible that os.ErrNotExist is caused by
|
||||||
|
// something different than the service script's non-existence. Keep it
|
||||||
|
// in mind, when replace the aghos.RunCommand.
|
||||||
|
_, out, err = aghos.RunCommand(scriptPath, cmd)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return "", service.ErrNotInstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Status() (status service.Status, err error) {
|
||||||
|
defer func() { err = s.annotate("getting status of", err) }()
|
||||||
|
|
||||||
|
var out string
|
||||||
|
if out, err = s.runCom("check"); err != nil {
|
||||||
|
return service.StatusUnknown, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := s.cfg.Name
|
||||||
|
switch out {
|
||||||
|
case fmt.Sprintf("%s(ok)\n", name):
|
||||||
|
return service.StatusRunning, nil
|
||||||
|
case fmt.Sprintf("%s(failed)\n", name):
|
||||||
|
return service.StatusStopped, nil
|
||||||
|
default:
|
||||||
|
return service.StatusUnknown, service.ErrNotInstalled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Start() (err error) {
|
||||||
|
_, err = s.runCom("start")
|
||||||
|
|
||||||
|
return s.annotate("starting", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Stop() (err error) {
|
||||||
|
_, err = s.runCom("stop")
|
||||||
|
|
||||||
|
return s.annotate("stopping", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Restart() (err error) {
|
||||||
|
if err = s.Stop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Logger(errs chan<- error) (l service.Logger, err error) {
|
||||||
|
if service.ChosenSystem().Interactive() {
|
||||||
|
return service.ConsoleLogger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.SystemLogger(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemLogger implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) SystemLogger(errs chan<- error) (l service.Logger, err error) {
|
||||||
|
return newSysLogger(s.cfg.Name, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSysLogger returns a stub service.Logger implementation.
|
||||||
|
func newSysLogger(_ string, _ chan<- error) (service.Logger, error) {
|
||||||
|
return sysLogger{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysLogger wraps calls of the logging functions understandable for service
|
||||||
|
// interfaces.
|
||||||
|
type sysLogger struct{}
|
||||||
|
|
||||||
|
// Error implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Error(v ...interface{}) error {
|
||||||
|
log.Error(fmt.Sprint(v...))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Warning(v ...interface{}) error {
|
||||||
|
log.Info("warning: %s", fmt.Sprint(v...))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Info(v ...interface{}) error {
|
||||||
|
log.Info(fmt.Sprint(v...))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Errorf(format string, a ...interface{}) error {
|
||||||
|
log.Error(format, a...)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Warningf(format string, a ...interface{}) error {
|
||||||
|
log.Info("warning: %s", fmt.Sprintf(format, a...))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Infof(format string, a ...interface{}) error {
|
||||||
|
log.Info(format, a...)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user