Compare commits

...

476 Commits

Author SHA1 Message Date
Frank Denis 8dadd61730
Merge pull request #2630 from DNSCrypt/dependabot/github_actions/softprops/action-gh-release-2.0.5
Bump softprops/action-gh-release from 2.0.2 to 2.0.5
2024-05-08 10:31:44 +02:00
dependabot[bot] f7da81cf29
Bump softprops/action-gh-release from 2.0.2 to 2.0.5
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.2 to 2.0.5.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](d99959edae...69320dbe05)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-08 03:55:52 +00:00
Frank Denis 0efce55895 Try another ODoH relay 2024-05-07 22:44:33 +02:00
Frank Denis 5a1d94d506 Update the test ODoH relay 2024-05-07 22:35:09 +02:00
Frank Denis 271943c158 Update quic-go 2024-05-07 17:39:56 +02:00
Frank Denis 34a1f2ebf5 Update quic-go 2024-04-27 22:33:56 +02:00
Frank Denis f8ce22d9b9 Update deps 2024-04-25 12:45:52 +02:00
Frank Denis 249dba391d Support gzip compression to fetch source files 2024-04-25 12:43:29 +02:00
Frank Denis 987ae216e3 Add fritz.box to the set of undelegated zones 2024-04-21 20:14:15 +02:00
Frank Denis 7fba32651b Make it more visible that DNS64 has been enabled 2024-04-19 18:27:39 +02:00
Frank Denis 6ae388e646 DNS64 plugin: don't return SYNTH data, alter the response directly
Fixes #2619

However, cached responses now appear with the "PASS" status rather
than "CLOAK".
2024-04-19 18:19:16 +02:00
Frank Denis 0af88bc875 Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  chore: fix some typos in comments
2024-04-15 12:36:30 +02:00
Frank Denis d36edeb612 Update deps 2024-04-15 12:36:21 +02:00
Frank Denis 041a6c7d7f
Merge pull request #2615 from cuibuwei/master
chore: fix some typos in comments
2024-04-13 20:02:49 +02:00
cuibuwei 2c6416d5ae chore: fix some typos in comments
Signed-off-by: cuibuwei <cuibuwei@gmail.com>
2024-04-13 19:56:31 +08:00
Frank Denis 4d1cd67d4d Nits 2024-04-03 16:49:37 +02:00
Frank Denis 363d44919f Properly check for the sticky bit 2024-04-03 16:47:13 +02:00
Frank Denis a88076d06f
Merge pull request #2605 from edmonds/forward-root-subdomain-matches
Forwarding plugin: Support forwarding subdomains of the root domain
2024-03-26 21:32:06 +01:00
Frank Denis 119bc0b660 Update deps 2024-03-26 19:56:06 +01:00
Robert Edmonds 49000cd4f4 Forwarding plugin: Support forwarding subdomains of the root domain
This commit updates the forwarding plugin to support matching subdomains
of the root domain ("."). It looks like the forwarding plugin already
performs subdomain matches against the domains specified in the
forwarding rules files, but matches against the root domain weren't
working because of the way matches are performed by comparing the
normalized presentation format QNAME (which omits the trailing dot for
all QNAMEs except the root domain name).

Without this commit, only queries where the QNAME is exactly "."
would match a forwarding rule for the "." domain, like this (with
`offline_mode = true` and a single forwarding rule for the "." domain):

```
[2024-03-25 21:13:31]	100.100.100.100	.	NS	FORWARD	0ms	127.0.0.1:53
[2024-03-25 21:13:36]	100.100.100.100	com	NS	NOT_READY	0ms	-
```

With this commit I get the expected result:

```
[2024-03-25 21:40:07]	100.100.100.100	.	NS	FORWARD	0ms	127.0.0.1:53
[2024-03-25 21:40:09]	100.100.100.100	com	NS	FORWARD	0ms	127.0.0.1:53
```
2024-03-25 21:30:09 -04:00
Frank Denis ec46e09360
Merge pull request #2597 from DNSCrypt/dependabot/github_actions/softprops/action-gh-release-d99959edae48b5ffffd7b00da66dcdb0a33a52ee
Bump softprops/action-gh-release from 975c1b265e11dd76618af1c374e7981f9a6ff44a to d99959edae48b5ffffd7b00da66dcdb0a33a52ee
2024-03-11 09:23:02 +01:00
dependabot[bot] ea5808e024
Bump softprops/action-gh-release
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 975c1b265e11dd76618af1c374e7981f9a6ff44a to d99959edae48b5ffffd7b00da66dcdb0a33a52ee.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](975c1b265e...d99959edae)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 03:24:39 +00:00
Frank Denis 79a1aa8325
Merge pull request #2591 from alisonatwork/fix-build 2024-02-28 10:11:48 +01:00
Alison Winters 4c442f5dbb use go mod version for codeql 2024-02-28 10:07:03 +08:00
Alison Winters f7e13502c0 fix go version format 2024-02-28 09:47:34 +08:00
YX Hao 8d43ce9b56 make expression be more self-explanatory 2024-02-27 22:05:40 +08:00
YX Hao ac5087315c Listen `0.0.0.0` only on IPv4 2024-02-27 19:04:09 +08:00
Frank Denis ad80d81d43
Merge pull request #2587 from DNSCrypt/dependabot/github_actions/softprops/action-gh-release-975c1b265e11dd76618af1c374e7981f9a6ff44a
Bump softprops/action-gh-release from 4634c16e79c963813287e889244c50009e7f0981 to 975c1b265e11dd76618af1c374e7981f9a6ff44a
2024-02-26 08:47:39 +01:00
dependabot[bot] a7fb13ba4e
Bump softprops/action-gh-release
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 4634c16e79c963813287e889244c50009e7f0981 to 975c1b265e11dd76618af1c374e7981f9a6ff44a.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](4634c16e79...975c1b265e)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 03:37:41 +00:00
Frank Denis 006619837f Update deps 2024-02-20 08:01:38 +01:00
Frank Denis 093936f7ab Check for dumb file permissions on startup
There's nothing special about "-service install".

On any system, executables shouldn't be modifiable by other system
users, no matter what the executable is and how it's run.
2024-02-20 02:39:39 +01:00
Frank Denis 7462961980 Warn if the executable of the service being installed could be overwritten by other system users
Fixes #2579

Until this is handled by `kardianos/service`
2024-02-20 02:23:49 +01:00
Frank Denis 0b559bb54f Warn if the main config file could be written by other system users 2024-02-20 02:11:03 +01:00
Frank Denis 658835b4ff
Merge pull request #2573 from DNSCrypt/dependabot/github_actions/softprops/action-gh-release-4634c16e79c963813287e889244c50009e7f0981
Bump softprops/action-gh-release from c9b46fe7aad9f02afd89b12450b780f52dacfb2d to 4634c16e79c963813287e889244c50009e7f0981
2024-02-06 13:23:03 +01:00
Frank Denis 90c3017793
Merge pull request #2544 from DNSCrypt/dependabot/github_actions/actions/setup-go-5
Bump actions/setup-go from 4 to 5
2024-02-06 13:22:24 +01:00
dependabot[bot] e371138b86
Bump softprops/action-gh-release
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from c9b46fe7aad9f02afd89b12450b780f52dacfb2d to 4634c16e79c963813287e889244c50009e7f0981.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](c9b46fe7aa...4634c16e79)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-06 03:22:12 +00:00
Frank Denis bcbf2db4ff
Merge pull request #2568 from lifenjoiner/ci 2024-01-19 14:30:34 +01:00
YX Hao 64fa90839c CI tests the unregistered domain for solid NS answer
And formats.
2024-01-19 19:11:03 +08:00
Frank Denis f2484f5bd5 Cache plugin: replace ARC cache with SIEVE 2024-01-19 00:05:33 +01:00
Frank Denis 63f8d9b30d Update deps 2024-01-18 23:47:00 +01:00
Xiaotong Liu 49e3570c2c
Support server refresh concurrency (#2537)
* simultaneously refresh all servers

* Add `cert_refresh_concurrency`

---------

Co-authored-by: YX Hao <lifenjoiner@163.com>
2023-12-18 19:25:54 +08:00
lifenjoiner 3be53642fe
Merge pull request #2549 from lifenjoiner/wg
Optimize CaptivePortalHandler for clean code
2023-12-17 18:59:05 +08:00
YX Hao 13e7077200 Optimize CaptivePortalHandler for clean code 2023-12-14 19:23:42 +08:00
Frank Denis f5912d7ca9
Merge pull request #2548 from DNSCrypt/dependabot/github_actions/github/codeql-action-3
Bump github/codeql-action from 2 to 3
2023-12-14 07:47:05 +01:00
dependabot[bot] 0196d7d2ab
Bump github/codeql-action from 2 to 3
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 03:22:09 +00:00
Frank Denis 898ded9c52 Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  add timeout for udp and tcp dialer
  Use blocking channel instead of looping sleep for less CPU usage
2023-12-13 08:35:55 +01:00
Frank Denis e782207911 Update deps 2023-12-13 08:35:41 +01:00
Frank Denis 0f1f635ec1
Merge pull request #2545 from keatonLiu/dial-timeout
add timeout for udp and tcp dialer
2023-12-11 14:45:20 +01:00
keatonLiu 956a14ee21 add timeout for udp and tcp dialer 2023-12-10 23:56:13 +08:00
dependabot[bot] 22731786a2
Bump actions/setup-go from 4 to 5
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-07 03:49:26 +00:00
Frank Denis 42b6ae9052
Merge pull request #2541 from lifenjoiner/chan
Use blocking channel instead of looping sleep for less CPU usage
2023-12-03 17:24:10 +01:00
YX Hao 0d5e52bb16 Use blocking channel instead of looping sleep for less CPU usage 2023-12-03 23:00:10 +08:00
Frank Denis 0ba728b6ce Update deps 2023-11-15 15:51:48 -08:00
Frank Denis cb80bf33e8 shellcheck 2023-11-09 22:47:41 +01:00
Frank Denis 88207560a7 shellcheck fixes 2023-11-09 22:45:11 +01:00
Jeffrey Damick 4a361dbb05 Added support to package dnscrypt for windows into an msi 2023-11-09 13:14:29 -08:00
Frank Denis b37a5c991a Update deps 2023-10-12 15:53:53 +02:00
Frank Denis 0232870870 -list: only copy nofilter flag for ODoH relays 2023-09-23 22:52:43 +02:00
Frank Denis 1a9bf8a286 Omit DNSSEC flag for relays 2023-09-23 18:46:11 +02:00
Frank Denis 7fb58720fb Add -include-relays option to include relays in -list and -list-all 2023-09-23 18:37:52 +02:00
Frank Denis f85b3e81ec Update deps 2023-09-19 20:59:57 +02:00
Frank Denis 79779cf744 Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  Bump actions/checkout from 3 to 4
2023-09-05 22:37:54 +02:00
Frank Denis 8bea679e7b Unofficially support DoH/ODoH over HTTP 2023-09-05 22:37:11 +02:00
Frank Denis 96f21f1bff
Merge pull request #2479 from DNSCrypt/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2023-09-05 08:13:08 +02:00
dependabot[bot] 21097686c1
Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-05 03:29:06 +00:00
Frank Denis 87571d4a7f Add an IPv6 forwarding example
Fixes #2474
2023-08-30 21:32:22 +02:00
Frank Denis f531c8fffb plugin_forward: parse more conventions for IPv6 addresses 2023-08-30 21:29:09 +02:00
Frank Denis 5ae83c1592 Remove minor go version for codeql 2023-08-26 18:28:55 +02:00
Frank Denis c86e9a90cc Update quic-go 2023-08-25 16:15:45 +02:00
Frank Denis d48c811ea9 Don't use absolute paths in the example file 2023-08-17 14:44:47 +02:00
Frank Denis f2b1edcec2 Add dnscry.pt servers 2023-08-17 14:43:33 +02:00
Frank Denis 1b65fe62b0 Bump 2023-08-11 18:56:31 +02:00
Frank Denis 194752e829 Update ChangeLog 2023-08-11 18:47:44 +02:00
Frank Denis 808f2dfa0e Update deps 2023-08-11 17:06:18 +02:00
Frank Denis 7dd79d5f96 Add a little bit more delay when spinning
But we really shouldn't do it that way, if only because there's a race
between the last write to the channel and the close() call
2023-08-11 15:24:14 +02:00
Frank Denis 5088d8fae1 Use the latest version of Go in CI 2023-08-11 14:59:34 +02:00
Frank Denis aff09648bb Add support for extended error codes 2023-08-11 14:59:10 +02:00
Frank Denis 7bca9a6c0a
Merge pull request #2457 from Neo2308/feature/master/check-latest-go
Configured Github CI to always use latest go version
2023-08-11 14:07:59 +02:00
Frank Denis 98d0938815 Make RefreshDelay match the documentation 2023-08-11 14:06:03 +02:00
Frank Denis 50780421a8 Remove ipv6.download.dnscrypt.info
IPv6 address has been added to download.dnscrypt.info
2023-08-11 14:04:20 +02:00
RadhaKrishna be7d5d1277 Configured Github CI to always use latest go
version

* Check if there is a newer version of go before using the cached version.
2023-08-11 17:15:09 +05:30
Frank Denis c3dd761b81 Make error more explicit 2023-08-11 12:07:13 +02:00
Frank Denis d8aec47a72 Revert "Make RefreshDelay match the documentation"
This reverts commit cfd6ced134.
2023-08-11 11:48:30 +02:00
Frank Denis cfd6ced134 Make RefreshDelay match the documentation 2023-08-11 11:42:12 +02:00
Frank Denis bdf27330c9 Make fetchWithCache() more readable 2023-08-11 11:24:54 +02:00
Frank Denis a108d048d8 A useless Chtimes() call is still required for the tests :/ 2023-08-11 11:16:44 +02:00
Frank Denis afcfd566c9 Make updateCache() more readable 2023-08-11 11:11:16 +02:00
Frank Denis ce55d1c5bb Get rid of named return parameters 2023-08-11 11:01:55 +02:00
Frank Denis 2481fbebd7 Revert b898e07066 2023-08-11 01:39:15 +02:00
Frank Denis 32aad7bb34 Format fix 2023-08-11 01:15:34 +02:00
Frank Denis 7033f242c0 Restore the cache update code from version 2.1.4 for now 2023-08-11 00:51:34 +02:00
Frank Denis 2675d73b13 Port changes from #2334
I'm not sure I follow, but I trust @lifenjoiner

Fixes #2334
2023-08-11 00:17:46 +02:00
Frank Denis 5085a22903 Update quic-go again 2023-08-10 23:50:43 +02:00
Frank Denis 7cc5a051c7 Update golang-lru 2023-08-08 14:21:12 +02:00
Frank Denis 894d20191f Update deps 2023-08-07 17:32:25 +02:00
Frank Denis 0a98be94a7 Update quic-go to fix two regressions 2023-08-01 00:06:22 +02:00
Frank Denis 1792c06bc7
Merge pull request #2442 from Expertcoderz/patch-1
Add note regarding block_unqualified setting
2023-07-25 14:55:42 +02:00
Expertcoderz 63e414021b
Add note regarding block_unqualified 2023-07-25 12:36:07 +00:00
Frank Denis d659a801c2 Big update to Update quic-go 2023-07-22 00:41:27 +02:00
Frank Denis a4eda39563
Merge pull request #2438 from Expertcoderz/patch-1
Add .mail & .home.arpa undelegated names
2023-07-15 20:07:05 +02:00
Expertcoderz 4114f032c3
Add .mail & .home.arpa undelegated names
Both names have been recognized for internal use in private networks.
2023-07-15 13:12:40 +00:00
Frank Denis a352a3035c Update deps 2023-07-08 14:56:13 +02:00
Frank Denis 60684f8ee4
Merge pull request #2429 from lifenjoiner/quic-go
Upgrade quic-go to v0.36.1
2023-07-08 14:54:39 +02:00
YX Hao be369a1f7a Shorten a line 2023-07-06 21:01:41 +08:00
YX Hao 89ccc59f0e Upgrade quic-go to v0.36.1
quic-go has made breaking changes since v0.35.0, includes implementing
`CloseIdleConnections`.
Now, the local listener UDPConn are reused, and don't pile up. But,
1 instance (IPv4/IPv6) persists for each connected server.
2023-07-05 21:19:54 +08:00
Frank Denis 16b2c84147 Tone down some errors 2023-06-24 22:38:59 +02:00
Carlo Teubner b46775ae0c
Add some missing error checks (#2420)
I found these with the 'errcheck' tool (via 'golangci-lint').

I aimed to apply reasonable judgement when deciding which errors
actually need handling, and how to handle them.
2023-06-24 22:23:12 +02:00
Frank Denis cef4b041d7 Don't call "bin" what is actually text 2023-06-24 22:11:47 +02:00
Carlo Teubner d8b1f4e7cd
Fix miscellaneous style issues (#2421)
Found by running: golangci-lint run --enable-all

I have only addressed the reported issues that seemed relevant to me.
2023-06-24 21:56:03 +02:00
Frank Denis 23a6cd7504 Revert "Update quic-go"
This reverts commit f9f68cf0a3.

quic-go >= 1.0.35 panics

We may not be using the new API correctly.
2023-06-22 11:06:37 +02:00
Frank Denis f42b7dad17 Update deps 2023-06-22 10:36:33 +02:00
Frank Denis 4f3ce0cbae Update deps 2023-06-18 04:19:52 +02:00
Frank Denis 0f1e3b4ba8 error check all the rand.Read() calls 2023-06-06 09:16:44 +02:00
Frank Denis 62ef5c9d02 Update quic-go to fix a regression 2023-06-01 11:32:10 +02:00
Frank Denis f9f68cf0a3 Update quic-go 2023-05-30 18:17:27 +02:00
Frank Denis 0c26d1637a Add suport for TLS key logging 2023-05-24 09:21:49 +02:00
Frank Denis 9f86ffdd1e Update deps 2023-05-13 11:39:11 +02:00
lifenjoiner 9b2c674744
Base on clientProto value explicitly to dereference clientAddr (#2393)
There are variants local_doh and trampoline for internal flow.
2023-05-13 11:22:52 +02:00
Frank Denis d381af5510 Update deps 2023-05-01 16:05:02 +02:00
Frank Denis c66023c7d7 Clarify that TLS cipher suites are for TLS 1.2
Fixes #2377
2023-04-18 13:15:59 -06:00
Frank Denis 5b8e7d4114 Use the same command as on the wiki to create a local DoH cert 2023-04-14 23:08:10 +02:00
KOLANICH f4007f709d
Add DOH certificate generation commands into the example config. (#2367) 2023-04-14 21:34:29 +02:00
lifenjoiner dd1c066724
CI: Don't downloading already vendored dependencies (#2370) 2023-04-14 21:21:22 +02:00
lifenjoiner 5d551e54ce
CI: Fix actions/setup-go@v4 warning (#2371)
Warning: Restore cache failed: Dependencies file is not found ...
https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs
2023-04-14 21:20:37 +02:00
Thad Guidry fbc7817366
fix grammar in example file (#2372) 2023-04-14 21:19:55 +02:00
Frank Denis 9b61b73852 Update deps 2023-04-07 16:42:57 +02:00
Frank Denis af6340df09 Comment 2023-04-07 16:20:26 +02:00
Frank Denis 9c73ab3070 Simplify updateCache() 2023-04-07 16:18:50 +02:00
Frank Denis ea3625bcfd Try to simplify updateCache() to understand what it does 2023-04-07 16:09:51 +02:00
Frank Denis f567f57150 up 2023-04-07 15:58:34 +02:00
Frank Denis c03f1a31eb Go named return parameters are utterly confusing 2023-04-07 15:37:09 +02:00
Frank Denis c3c51bb435 Partially re-merge 92ed5b95e0 2023-04-07 15:21:00 +02:00
Frank Denis 0f30b3b028 Revert "Try to understand how cache files are updated"
This reverts commit 92ed5b95e0.
2023-04-07 15:16:15 +02:00
lifenjoiner 6d826afac5
Reduce a local variable (#2363) 2023-04-06 14:22:21 +02:00
Frank Denis b341c21dbd Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  Bump softprops/action-gh-release (#2357)
  Bump actions/setup-go from 3 to 4 (#2354)
  Update deps
  Format
  Better description for ignore_system_dns
  Move booleans together for alignment, avoid unneeded format string
  Try dnscrypt-proxy to resolve configured hosts when ignore_system_dns (#2204)
  Downgrade to TLS 1.2 if an 1.3-incompatible cipher suite is set
2023-04-06 14:21:15 +02:00
Frank Denis 92ed5b95e0 Try to understand how cache files are updated
Having to keep a copy of all the files in memory is weird.

We shouldn't have to do that.
2023-04-06 14:19:25 +02:00
Frank Denis b898e07066 A source URL may have an IP address that doesn't exist any more 2023-04-06 14:18:38 +02:00
dependabot[bot] 92063aa76d
Bump softprops/action-gh-release (#2357)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from d4e8205d7e959a9107da6396278b2f1f07af0f9b to c9b46fe7aad9f02afd89b12450b780f52dacfb2d.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](d4e8205d7e...c9b46fe7aa)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-05 21:43:53 +02:00
dependabot[bot] 4be5264529
Bump actions/setup-go from 3 to 4 (#2354)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-05 21:43:43 +02:00
Frank Denis 13d78c042b Update deps 2023-04-05 21:36:29 +02:00
Frank Denis 36c17eb59a Format 2023-04-05 21:33:21 +02:00
Frank Denis b9f8f78c6e Better description for ignore_system_dns 2023-04-05 21:31:07 +02:00
Frank Denis fc16e3c31c Move booleans together for alignment, avoid unneeded format string 2023-04-05 21:20:42 +02:00
lifenjoiner b3318a94b7
Try dnscrypt-proxy to resolve configured hosts when ignore_system_dns (#2204) 2023-04-05 21:17:51 +02:00
Frank Denis ca0f353087 Downgrade to TLS 1.2 if an 1.3-incompatible cipher suite is set
Fixes #2359
2023-04-05 20:53:27 +02:00
Frank Denis cf7d60a704 Update miekg/dns 2023-03-29 22:00:24 +09:00
Frank Denis a47f7fe750 Update deps 2023-03-23 12:54:45 +01:00
Frank Denis beb002335f Add an example forwarding rule with Tor 2023-03-23 12:53:08 +01:00
Frank Denis 15c87a68a1 Update quic-go 2023-02-25 23:45:38 +01:00
Frank Denis 47e6a56b16 Logger: pre-create log files before lumberjack does
Clunky workaround for https://github.com/natefinch/lumberjack/issues/164
2023-02-25 23:42:38 +01:00
Frank Denis 03c6f92a5f Use crypto_rand() everywhere 2023-02-24 16:20:39 +01:00
lifenjoiner 24a301b1af
Fix DoH3 connections piling up (#2337)
DoH3 creates a new connection for each request without closing.

* `Conn` should be self maintained if it's created by customized `Dial` of `http3.RoundTripper`.
https://pkg.go.dev/github.com/quic-go/quic-go#DialAddrEarlyContext

* http3 doesn't have a `CloseIdleConnections`.
https://pkg.go.dev/net/http#Client.CloseIdleConnections
2023-02-24 16:14:43 +01:00
lifenjoiner a8d1c2fd24
`dlog.SetLogLevel(dlog.SeverityDebug)` if `go test -v` (#2331) 2023-02-21 16:24:11 +01:00
Frank Denis 96ffb21228 Update deps 2023-02-15 18:42:36 +01:00
Frank Denis acc25fcefb Format with gofumpt 2023-02-11 14:27:12 +01:00
Frank Denis 07b4ec33c5 Minor update of x/net and x/crypto 2023-02-09 17:23:48 +01:00
Frank Denis 9f3ef735f2 Bump 2023-02-07 11:03:09 +01:00
Frank Denis d568e43937 Update deps 2023-02-07 11:03:09 +01:00
Frank Denis 68f3ab249c Unbreak cloaking plugin
In version 2.1.3, when the cloaking pluging was enabled, a blocked
response was returned for records that were not A/AAAA/PTR, even
with names that were not in the cloaked list.
2023-02-07 11:03:05 +01:00
dependabot[bot] 2edfdc48b8
Bump roots/issue-closer from 1.1 to 1.2 (#2307)
Bumps [roots/issue-closer](https://github.com/roots/issue-closer) from 1.1 to 1.2.
- [Release notes](https://github.com/roots/issue-closer/releases)
- [Commits](https://github.com/roots/issue-closer/compare/v1.1...v1.2)

---
updated-dependencies:
- dependency-name: roots/issue-closer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-06 10:46:14 +01:00
Frank Denis 3f31c4d3e2 Update issue-close-message 2023-02-04 22:15:18 +01:00
Frank Denis 84184bbad8 Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  GitHub Actions: Deprecating save-state and set-output commands (#2295)
  Nits (#2293)
  Make CodeQL happy (#2294)
2023-02-04 22:11:14 +01:00
Frank Denis f630094e8d Add autocloser 2023-02-04 22:11:05 +01:00
lifenjoiner 3517dec376
GitHub Actions: Deprecating save-state and set-output commands (#2295) 2023-02-03 16:24:32 +01:00
lifenjoiner 683aad75da
Nits (#2293) 2023-02-03 16:23:57 +01:00
lifenjoiner e1c7ea1770
Make CodeQL happy (#2294) 2023-02-03 16:22:32 +01:00
Frank Denis 470460f069 GO386=387 -> GO386=softfloat
Fixes #2296
2023-02-03 16:21:51 +01:00
Frank Denis 8694753866 Bump action-gh-release up 2023-02-02 20:28:37 +01:00
Frank Denis b4b58366cc Update ChangeLog 2023-02-02 20:27:16 +01:00
Frank Denis f7df72eafa Bump to 2.1.3 2023-02-02 20:10:54 +01:00
Frank Denis fb15535282 Format 2023-02-02 20:10:49 +01:00
Frank Denis c32aad3dfd Update GitHub status badge 2023-02-02 20:09:09 +01:00
Frank Denis 9e208e6090 Cloak plugin: reject uncloaked records, except NS & SOA
Fixes #2220
2023-02-02 19:59:47 +01:00
Frank Denis 5f88a9146c Get rid of the latest ioutil bits 2023-02-02 19:44:51 +01:00
Frank Denis 3f23ff5c08 Mostly get rid of ioutil 2023-02-02 19:38:24 +01:00
Frank Denis 33c8027e0a Use a custom dialer for HTTP/3 2023-02-02 19:32:17 +01:00
Frank Denis 11e824bd13 Update go-acl 2023-02-02 12:44:12 +01:00
Deltadroid c3fd855831
Update quic-go dependency to support go 1.20 (#2292) 2023-02-02 12:42:11 +01:00
Frank Denis 5438eed2f4 Update go/x/crypto 2023-01-08 21:37:50 +01:00
Frank Denis a868e2b306 2023 2023-01-05 14:06:00 +01:00
Frank Denis f21eca0764 Add time.google.com IP addresses to the captive portals example 2022-12-30 13:50:31 +01:00
Frank Denis c883949a97 Document cert_ignore_timestamp 2022-12-29 22:39:12 +01:00
Frank Denis e13b4842e8 Updater deps 2022-12-17 14:50:10 +01:00
dependabot[bot] 5705b60936
Bump softprops/action-gh-release (#2247)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from cd28b0f5ee8571b76cfdaa62a30d51d752317477 to 1. This release includes the previously tagged commit.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](cd28b0f5ee...de2c0eb89a)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 11:42:01 +01:00
Frank Denis 5f4dfc5d6e Update quic-go 2022-11-19 23:01:21 +01:00
Frank Denis a89d96144f Update deps 2022-11-16 21:38:52 +01:00
Frank Denis 4aa415de6e Update deps 2022-10-24 10:20:25 +02:00
Frank Denis f4389559ef Pin softprops/action-gh-release 2022-10-24 10:10:58 +02:00
Frank Denis 361455cd58 ServiceManagerReadyNotify is not just for systemd 2022-10-20 15:33:43 +02:00
cobratbq 77059ce450
systemd: report Ready earlier as dnscrypt-proxy can itself manage retries for updates/refreshes (#2225) 2022-10-20 15:32:26 +02:00
Frank Denis 09a6918226 Use os.Geteuid()
Fixes #2224
2022-10-18 14:56:39 +02:00
Frank Denis c748630691 Update deps 2022-10-15 10:37:07 +02:00
Frank Denis 94cba8cf78 Update quic-go 2022-09-26 15:12:20 +02:00
Maurizio Pasquinelli ca253923eb
Rename macOS binary (#2191) 2022-08-31 12:05:35 +02:00
Frank Denis 08d44241b9 Update deps 2022-08-30 20:45:06 +02:00
lifenjoiner 4881186dcf
Optimize adopted relay name to show (#2188)
* Optimize adopted relay name to show

DNSCrypt relay requires ServerAddrStr;
ODoH relay requires ProviderName, port 443 can be either present or not;
raw stamp can be both.

Displaying specified stamp makes it easier to debug.

* Fix pasto
2022-08-25 19:28:04 +02:00
Frank Denis 41f192a907 Mention HTTP/3 2022-08-24 17:35:34 +02:00
Frank Denis 937c1e63e2 Revert "xtransport layer to netip and immediate dependencies (#2159)"
This reverts commit baee50f1dc.
2022-08-10 22:24:36 +02:00
Frank Denis e124623ffc Mention HTTP/3 support 2022-08-03 15:31:57 +02:00
lifenjoiner 55fc4c207b
Log to console when in command mode (#2167)
Quick results.
Avoid overwriting the log file in use, by the same config most of the time.
2022-08-03 14:52:08 +02:00
Ian Bashford baee50f1dc
xtransport layer to netip and immediate dependencies (#2159) 2022-08-01 22:31:12 +02:00
Frank Denis 6e1bc06477 Update quic-go 2022-08-01 17:59:01 +02:00
Frank Denis 8523a92437 Update example to include http3 configuration 2022-07-24 16:16:21 +02:00
Frank Denis 442f2e15cb Make HTTP/3 support configurable 2022-07-24 16:13:14 +02:00
Frank Denis 0c88e2a1a0 ChangeLog update 2022-07-24 15:43:03 +02:00
Frank Denis 35063a1eec Quit dep update before the release 2022-07-24 15:06:14 +02:00
Frank Denis 07266e4d4f Update toml dep 2022-07-21 19:02:28 +02:00
Frank Denis 5977de660b Add suport for DoH over HTTP/3 2022-07-21 18:50:10 +02:00
lifenjoiner 91388b148c
Optimize stopping CaptivePortalHandler - 2 (#2155)
1. Fix early return that triggers port rebinding error by 8e46f447.
2. Reduce waiting time while there are multiple listen_addresses.
2022-07-19 12:35:52 +02:00
lifenjoiner 8e46f44799
Optimize stopping CaptivePortalHandler (#2151)
* Optimize stopping CaptivePortalHandler

* Still use unbuffered channel as we close it instead of sending a signal
2022-07-14 21:53:13 +02:00
Frank Denis 3d641b758a Bump 2022-07-13 18:49:50 +02:00
Frank Denis 49ea894ce8 Update dependencies 2022-07-13 18:48:17 +02:00
lifenjoiner 568f54fabb
Reduce comparisons (#2148) 2022-07-08 14:11:51 +02:00
pc-v2 dc2fff05be
Adding flush dns to clean up previous dns cache (#2141) 2022-07-03 14:13:12 +02:00
Frank Denis 38e87f9a7b Add a constant for the maximum number of attempts 2022-06-28 18:30:15 +02:00
lifenjoiner 0e2bb13254
Fix goroutines memory leak by unbuffered channel blocking (#2136)
* Use buffered channel to avoid goroutine hanging on

A send on an unbuffered channel can proceed if a receiver is ready.

* Balance captivePortalHandler.cancelChannels for Stop
2022-06-28 18:28:57 +02:00
Frank Denis 59ce17e0ab No need to warn if this is then going to be an error 2022-06-24 15:41:05 +02:00
Frank Denis ee5c9d67a4 Update miekg/dns 2022-06-24 15:18:33 +02:00
Frank Denis 8c43118b03 Stop mentioning "SERVFAIL" in info messages 2022-06-19 20:38:49 +02:00
ignoramous 7177a0ec74
dns64: preserve cnames in translated response (#2129)
* dns64: preserve cnames in translated response

* dns64: rename synthAAAAs to synth64
2022-06-16 00:53:50 +02:00
lifenjoiner 72a602577a
Raise error for invalid relay (#2128)
* Raise error for invalid relay

* Keep error messages the same

* Distinguish this from validation failed
2022-06-15 13:16:06 +02:00
lifenjoiner 0a0b69d93d
RUnlock for early exit (#2127) 2022-06-14 14:25:52 +02:00
lifenjoiner 6916c047e1
Use registeredServers slice copy during ServerInfo refreshing period (#2125)
goroutines:
proxy.updateRegisteredServers() versus proxy.serversInfo.refresh(proxy)
2022-06-13 17:51:33 +02:00
ignoramous 8d737a69f5
PluginDNS64: Use read and write mutexes as approp (#2124) 2022-06-12 11:27:55 +02:00
Frank Denis 866954fbad PreferServerCipherSuites has been deprecated 2022-06-11 19:26:26 +02:00
Frank Denis e477d0e126 We may not have a configured IP address 2022-06-11 19:23:58 +02:00
Frank Denis e24fdd2235 Nits 2022-06-07 21:33:50 +02:00
livingentity 74fb5dabb9
fix negative rtt / shorten lines (#2118)
* fix negative rtt / shorten lines

* Update serversInfo.go
2022-05-18 17:57:57 +02:00
Frank Denis 1afd573b0d Add builds for linux/riscv64 2022-05-18 13:31:46 +02:00
Frank Denis c367a82ac0 Bump miekg/dns 2022-05-11 19:38:45 +02:00
dependabot[bot] 9c8c327703
Bump github/codeql-action from 1 to 2 (#2102)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-26 08:20:41 -07:00
livingentity 207d44323d
Update serversInfo.go (#2092) 2022-04-16 21:26:38 +02:00
Frank Denis 3eac156789 Update burntSushi/toml 2022-04-05 14:05:53 +02:00
Frank Denis 5fca7ea49e Back to VividCortex/ewma 2022-04-05 14:04:26 +02:00
Frank Denis 77dc3b1e85 Update miekg/dns 2022-04-03 23:08:29 +02:00
Frank Denis 66f019d886 Revert "regression: fix ewma warmup again (#2079)"
This reverts commit f67e9cab32.
2022-04-03 23:01:03 +02:00
livingentity f67e9cab32
regression: fix ewma warmup again (#2079)
* Update estimators.go

* Update go.mod

* Update modules.txt

* Update go.sum

* Update serversInfo.go

* Update estimators.go

* Update serversInfo.go
2022-04-02 17:41:36 +02:00
Frank Denis 5d023d2a7c Revert "New feature: sleep mode"
This reverts commit e931b234b7.
2022-04-02 09:33:49 +02:00
Frank Denis e931b234b7 New feature: sleep mode 2022-03-31 20:51:34 +02:00
quindecim ed2c880648
Add sources in [domains-blocklist.conf] (#2039)
* Remove duplicate in [domains-blocklist.conf]

__NextDNS CNAME cloaking list__ is already contained in [your big source](https://github.com/notracking/hosts-blocklists/blob/master/SOURCES.md?plain=1#L47). No reasons to merge it two times.

* Remove more duplicates in [domains-blocklist.conf]

* Revert previous commit

* Add anudeepND and lightswitch05 blocklists
2022-03-29 14:46:46 +02:00
Frank Denis c467e20311 Update deps 2022-03-28 12:35:01 +02:00
Frank Denis df3fb0c9f8 Keep lines short
$ golines -w -m 120 --shorten-comments .
2022-03-23 17:48:48 +01:00
Frank Denis c0435772d4 -resolve: report ECS support
Note that we can't randomize the source network, as Google and
possible others refuse networks that don't get BGP announcements.
2022-03-14 17:04:54 +01:00
Frank Denis 49c17f8e98 -resolve: use TXT records to get resolver information 2022-03-14 16:11:10 +01:00
Frank Denis 0465cd35ef miekg/dns update 2022-03-14 16:02:51 +01:00
dependabot[bot] 08420917a5
Bump actions/setup-go from 2.2.0 to 3 (#2054)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2.2.0 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2.2.0...v3)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-07 17:01:40 +01:00
livingentity 87d9653ec2
Remove unused functions (#2057)
They aren't used anywhere.
2022-03-07 17:01:18 +01:00
BigDargon d30c44a6a8
Change bootstrap resolver Quad9 (with ECS) (#2056) 2022-03-02 13:18:20 +01:00
dependabot[bot] 911108149b
Bump actions/checkout from 2 to 3 (#2055)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-02 10:15:29 +01:00
dependabot[bot] a8aa4cb8e6
Bump actions/setup-go from 2.1.5 to 2.2.0 (#2035)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2.1.5 to 2.2.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2.1.5...v2.2.0)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-21 14:17:33 +01:00
Frank Denis ca076ce133 Size estimator: provide the slide size 2022-02-21 14:16:13 +01:00
Frank Denis 034d3bd424 Switch to lifenjoiner's ewma variant 2022-02-21 09:14:24 +01:00
Frank Denis c08852feb1 Update deps; sync vendor 2022-02-21 09:00:21 +01:00
Frank Denis 9373cc7162 Use SimpleEWMA for the question size estimator 2022-02-20 23:40:32 +01:00
Frank Denis cb140673fa Set the number of warmup samples to 1 for the RTT estimator 2022-02-20 23:38:42 +01:00
Frank Denis 7956ba5b10 Switch to an ewma fork that allows setting the warmup samples # 2022-02-20 23:38:06 +01:00
livingentity 9ec8a35468
restore old logic/constants (#2045)
* fix indices

* Update serversInfo.go

For safety go back to former logic, just generalized for lbStrategy, until someone comes up with an actual improvement.

* restore old logic/constants
2022-02-19 17:55:36 +01:00
livingentity ac6abfb985
LBStrategy-aware estimator (#2043)
* fix estimator

* LBStrategy-aware estimator

* typo

* cosmetics
2022-02-15 20:17:48 +01:00
quindecim a20d1685b2
Another minor cosmetic fix to [example-dnscrypt-proxy.toml] (#2036) 2022-02-10 15:27:53 +01:00
livingentity 62092726ec
Minor cosmetic toml changes (#2034)
* Minor cosmetic toml changes

* Minor cosmetic toml changes
2022-02-10 08:49:04 +01:00
Frank Denis f38a5463b0 Indent comments 2022-02-09 12:57:02 +01:00
quindecim 7a54406415
Use the same format logic throughout the document (#2029)
* Use the same spacing logic throughout the document

* Fix previous commit

* Fix previous commit, again

* Use the same logic in comments too
2022-02-09 12:49:22 +01:00
quindecim bce0405c0a
Add more sources in [domains-blocklist.conf] file (#2031)
[OISD nsfw] in [Block pornography] section

[Developer Dan's Hosts: Dating Services] in [Block dating websites] section
[Developer Dan's Hosts: Facebook] in [Block social media sites] section
2022-02-09 12:47:39 +01:00
quindecim 29a3442306
[FIX] Start to use wildcards urls for OISD lists (#2027) 2022-02-08 13:31:32 +01:00
Frank Denis 8ed98cacae Bump miekg/dns up 2022-02-08 09:26:55 +01:00
Peter Dave Hello ef1c70e87d
Remove README.md "Contribute" link as the target doesn't exist (#2015) 2022-02-03 01:30:10 +01:00
Frank Denis 4c67e790f6 -list command: print ODoH targets addresses 2022-02-01 08:19:46 +01:00
Frank Denis 4eeed5816f Fix funky indentation for CloakedPTR 2022-02-01 08:18:45 +01:00
Frank Denis c10e6e0635 Local DoH: add support for request using the GET method
Fixes #2012
2022-01-31 14:56:46 +01:00
Frank Denis e6089449b6 Update domains-blocklist.conf examples:
- Change KADhosts file to the base one
- Remove CHEF-KOCH and GameIndustry lists that don't exist any more

Reported by @remyabel2 - Thanks!
2022-01-30 21:20:19 +01:00
mibere 706c1ab286
Download mirror dnscrypt.net removed (#2003) 2022-01-24 01:36:30 +01:00
Frank Denis f7e3381650 generate-domains-blocklist: parse names prefixed with `*.` 2022-01-23 00:55:26 +01:00
cobratbq 7a8bd35009
systemd: use constants and update status on ready (#1993)
Systemd-notify signaling indicates the status of dnscrypt-proxy when
starting as 'Type=notify' systemd service. However, the status is not
updated when initialization completes, instead it always shows
"Starting". Now fixed.
2022-01-19 20:30:15 +01:00
Frank Denis 06733f57ed If a relay has multiple names, print the one matching the protocol
Fixes #1992
2022-01-17 19:43:12 +01:00
Frank Denis 4fd26029c7 Update BurntSushi/toml 2022-01-13 09:22:22 +01:00
Frank Denis 351bced7c5 New kardianos/service with support for OpenRC 2022-01-11 16:05:08 +01:00
Frank Denis 916e84e798 2022 2021-12-31 23:56:13 +01:00
ValdikSS 53f3a0e63d
Fix broken odoh link in readme (#1976)
* Fix broken odoh link in readme
2021-12-27 12:30:41 +01:00
Frank Denis 4e9f0382ee miekg/dns update 2021-12-24 09:16:59 +01:00
dependabot[bot] 9121f4f359
Bump actions/setup-go from 2.1.4 to 2.1.5 (#1968)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2.1.4 to 2.1.5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2.1.4...v2.1.5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-22 17:59:04 +01:00
Frank Denis e73459f558 Update deps 2021-12-22 14:00:37 +01:00
Frank Denis fbfc2d57a7 omit comparison to bool constant, can be simplified to !cloakedName.isIP
Reported by GitHub's code scanning
2021-12-16 10:43:40 +01:00
Frank Denis b9d6b22ce1 Align 2021-12-13 14:01:13 +01:00
Ian Bashford 1b6caba307
allow ptr queries for cloaked domains (#1958)
* allow ptr queries for cloaked domains

* multi ips per PTR returned + cleanup

* some string tidy up

* enable config file switch

* add cloaked ptr test

* enable cloak ptrs in test scenario

* fix reverse ipv6 ptr lookup

* added ipv6 cloaked ptr test
2021-12-13 14:00:13 +01:00
CNMan 27e93a53cf
minor typo fix (#1951) 2021-11-30 18:26:34 +01:00
Frank Denis 9e7221c31c Update deps 2021-11-05 13:29:16 +01:00
Frank Denis f6f63743ce Update go-minisign 2021-10-28 19:54:01 +02:00
Frank Denis 561e849889 Add a forwarding example for local reverse entries 2021-10-17 15:53:54 +02:00
a1346054 766e149699
Fix typo and alignment in example-dnscrypt-proxy.toml (#1915) 2021-10-10 19:19:45 +02:00
Frank Denis e2ada45598 Update deps 2021-10-09 13:35:18 +02:00
Frank Denis fc0ff3b26a Update ChangeLog 2021-09-27 17:59:16 +02:00
Frank Denis 88b174e5eb Sync deps 2021-09-27 17:49:02 +02:00
Frank Denis e1f3f58eed Bump 2021-09-27 15:51:48 +02:00
Frank Denis efcd392279 StaleResponseTtl -> StaleResponseTTL 2021-09-27 15:47:19 +02:00
Frank Denis 8da1b698ad Revert "Pasto, thanks to @lifenjoiner"
This reverts commit 14ef11447e.
2021-09-27 15:42:54 +02:00
Frank Denis 180e75bfdb Update deps 2021-09-27 12:35:04 +02:00
Frank Denis 77b27d9293 Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  Make return value explicit
  Repair stale respones for DoH
  Define a constant for the TTL of stale responses
  Update plugin_cache.go (#1900)
2021-09-27 12:31:43 +02:00
Frank Denis 4c29840040 Revert "Print an error if a block/allow rule contains more than a pattern"
This reverts commit 6e8628f796.
2021-09-27 12:29:41 +02:00
Frank Denis b7704a05c5 Make return value explicit 2021-09-25 20:09:29 +02:00
Frank Denis d82021b545 Repair stale respones for DoH 2021-09-25 19:53:43 +02:00
Frank Denis e5608e08cf Define a constant for the TTL of stale responses 2021-09-25 19:53:25 +02:00
livingentity 2a3e59c4bf
Update plugin_cache.go (#1900) 2021-09-25 19:04:17 +02:00
Frank Denis 69019b7a80 generate-domains-blocklist: fix the rx_timed regex
Fixes #1896
2021-09-25 03:10:00 +02:00
Frank Denis 8e913d8bf9 Udate deps 2021-09-24 09:30:49 +02:00
Frank Denis 3bae61dbe1 Properly round the TTL 2021-09-24 09:26:31 +02:00
Frank Denis 5fedbe4c6e // +build -> //go:build 2021-09-23 19:16:26 +02:00
Frank Denis b2f26192e1 gofmt 2021-09-23 19:16:12 +02:00
Frank Denis a4684d3bf5 Round TTLs 2021-09-23 19:10:40 +02:00
Frank Denis 34f0caaa34 Merge branch 'master' of github.com:jedisct1/dnscrypt-proxy
* 'master' of github.com:jedisct1/dnscrypt-proxy:
  Enable HTTP/2 pings
  Remove leftovers from the daemonize option
  DoH/ODoH: strip optional port number when caching a hostname
  Bump actions/setup-go from 2.1.3 to 2.1.4 (#1843)
  fix minor typo in example config (#1847)
2021-09-23 18:38:50 +02:00
Frank Denis 75e917ae49 plugin_cache: update the response TTL, not the cached data
Fixes #1895
2021-09-23 18:37:40 +02:00
Frank Denis 8fc0ffc35f Enable HTTP/2 pings 2021-09-21 12:57:42 +02:00
Frank Denis 97a983c6b3 Remove leftovers from the daemonize option 2021-09-09 11:26:17 +02:00
Frank Denis 0f00cd27f9 DoH/ODoH: strip optional port number when caching a hostname
Fixes #1861
2021-09-06 12:02:56 +02:00
dependabot[bot] 8178f3419f
Bump actions/setup-go from 2.1.3 to 2.1.4 (#1843)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2.1.3 to 2.1.4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2.1.3...v2.1.4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-30 16:34:03 +02:00
Alison Winters d8358b795f
fix minor typo in example config (#1847) 2021-08-29 11:11:52 +02:00
Aaron b8c5790716
Add captive portal entry for dual stack setups (#1835)
For users with a dual stack set up at home (IPv4+native IPv6), Windows 21H1 will report that there is no Internet connection if this entry is missing from their captive portals file.

Signed-off-by: Aaron <admin@datahoarder.dev>
2021-08-20 17:32:32 +02:00
a1346054 f6a2d2ea44
Minor cleanup (#1822)
Make github recognize the licence properly
2021-08-18 11:08:18 +02:00
Frank Denis 68b0d87522 Remove comment decorators around license text
Apparently, this is confusing some license checking tool
2021-08-18 11:03:06 +02:00
Frank Denis bb5dc3b1fc Remove trailing blanks 2021-08-18 11:01:38 +02:00
Frank Denis 59b6b8359c Format as single lines 2021-08-17 20:46:01 +02:00
Frank Denis 785f86f50f powerman update 2021-08-17 16:56:09 +02:00
Frank Denis d8c4a90501 The party is over :) 2021-08-15 03:07:19 +02:00
Frank Denis 9cb89ae410 odoh.md has been deprecated 2021-08-14 13:01:12 +02:00
Frank Denis e83cb28ef5 Split ODoH servers and relays 2021-08-14 12:33:10 +02:00
Frank Denis 73dc3dd1d8 Update/merge ChangeLog 2021-08-13 19:27:11 +02:00
Frank Denis 35c82e3dcf Next will be 2.1.0 2021-08-13 19:20:27 +02:00
Frank Denis 9f0b409375 Update deps 2021-08-13 19:19:23 +02:00
Frank Denis 49fdb461d8 ECHO -> ECH 2021-08-12 19:44:33 +02:00
Frank Denis c9d5d81e6a Update toml dependency 2021-08-08 10:21:25 +02:00
Frank Denis 1052fa6323 serve-stale on overflow 2021-08-04 14:30:32 +02:00
Frank Denis c8a61abb79 Update comment 2021-08-04 14:27:58 +02:00
Frank Denis e64425b5e7 On overflow, only respond to cached/synthesized queries 2021-08-04 14:27:24 +02:00
Frank Denis da69583bd2 When we run out of connections, handle an extra one synchronously 2021-08-04 13:35:33 +02:00
Frank Denis d996e3424d No need to get the time if the connection is refused 2021-08-04 13:23:21 +02:00
Frank Denis b4a073f54f Typo 2021-08-03 11:24:16 +02:00
Frank Denis 0ca90dd8cc xtransport: set a default error status code 2021-07-31 13:21:45 +02:00
Frank Denis 026c42424f Workaround for ODoH relays not properly forwarding the status code
Some ODoH relays return a 200 status code even when the upstream
server returns something different. This is an issue after a key
update, where a 401 code is expected.

Handle empty responses with a 200 status code as a response with
a 401 code as a workaround until these relays are fixed.
2021-07-31 12:54:23 +02:00
Frank Denis cedd4f3b54 xtransport: properly forward the status code on error 2021-07-31 12:38:10 +02:00
dependabot[bot] d3b1976208
Bump actions/setup-go from 1 to 2.1.3 (#1773)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 1 to 2.1.3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v1...v2.1.3)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-30 20:39:48 +02:00
Frank Denis bc529f0076 Update deps 2021-07-30 20:39:23 +02:00
Frank Denis 796a7f6d31 Add an example for blocking private relay 2021-07-17 14:22:10 +02:00
Frank Denis 03e1916527 🥂 2021-07-16 18:51:03 +02:00
Frank Denis d35c1c3cb2 Lower reject_ttl even more 2021-07-16 16:46:50 +02:00
Frank Denis 8b3b7d38ac Set ttl to reject_ttl for HINFO refused responses
Also lower the example TTL
2021-07-16 16:40:21 +02:00
Frank Denis 9acf257fe5 Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  Update dependabot.yml (#1770)
2021-07-03 10:57:19 +02:00
Frank Denis 5a091c6da4 Update deps 2021-07-03 10:56:53 +02:00
Jauder Ho 59690da355
Update dependabot.yml (#1770) 2021-07-03 09:42:19 +02:00
Frank Denis f033bb3034 miekg/dns update 2021-06-23 09:45:43 +02:00
Frank Denis 4caa7b6d64 Verbose 2021-06-12 14:48:02 +02:00
Frank Denis 6117a1111c CI: use odoh-crypto-sx which is the only ODoH server yet 2021-06-12 14:21:22 +02:00
Frank Denis 9bea0e8f20 Nits 2021-06-12 14:16:20 +02:00
Frank Denis da9c95ec5c up 2021-06-12 14:08:22 +02:00
Frank Denis b472fb3b21 Bump 2021-06-12 14:03:26 +02:00
Frank Denis 5fb2901dbc Fuzz the stamps parser 2021-06-11 22:20:54 +02:00
Frank Denis ccddb18424 Time to start fuzzing 2021-06-11 22:13:58 +02:00
Frank Denis 1b03ac817e ODoH: supoprt config version 0x0001 in addition to 0xff06 2021-06-11 21:18:05 +02:00
Frank Denis a34258c3aa Nits 2021-06-08 12:07:35 +02:00
Frank Denis 95c9fa75f8 Bump 2021-06-08 11:00:01 +02:00
Frank Denis d6be6f97ea Update ChangeLog 2021-06-08 10:59:26 +02:00
Frank Denis a85a003d2b Filter relays by compatible type before selecting them
Fixes #1739
2021-06-08 10:52:06 +02:00
Frank Denis 5a9a6467df Correctly check for empty/wrong relays 2021-06-08 10:27:03 +02:00
Frank Denis ec581597a2 Require ODoH relays to be present
ODoH target stamps don't include certificate hashes; they are not
meant to be used directly.
2021-06-08 10:19:02 +02:00
Frank Denis 33ed882efe Warn if fallback_resolvers is still in use 2021-06-08 09:53:53 +02:00
Frank Denis b39232e996 this -> that 2021-06-08 01:14:11 +02:00
Frank Denis 9ebb90b22e fallback -> bootstrap 2021-06-08 00:44:06 +02:00
Frank Denis b1dd11be72 Dash 2021-06-07 19:05:15 +02:00
Frank Denis 6076e2dd03 www.msftncsi.com IPs update 2021-06-07 18:47:31 +02:00
Frank Denis a0ecfd6a77 Thanks Chris! 2021-06-07 18:36:37 +02:00
Frank Denis 6c3cec2753 Accept warning for the direct ODoH test 2021-06-07 18:32:20 +02:00
Frank Denis d0e27a1366 Update ChangeLog 2021-06-07 18:25:52 +02:00
Frank Denis d5e9ed3aa9 Don't hardcode the HPKE cipher 2021-06-07 18:16:15 +02:00
Frank Denis 72a354caf9 Update go-hpke-compact 2021-06-07 18:11:58 +02:00
Frank Denis 45d3afc8f9 Call ObliviousDoHQuery() on the initial test 2021-06-07 17:32:34 +02:00
Frank Denis e7f017c592 ODoH: try POST first, even without a relay 2021-06-07 17:19:56 +02:00
Frank Denis 4eab70b770 Indentation matters 2021-06-07 16:44:55 +02:00
Frank Denis 9263d1a01c CI: odoh relays are not servers 2021-06-07 16:15:29 +02:00
Frank Denis f8144f8db6 CI stamp update 2021-06-07 16:13:27 +02:00
Frank Denis 98ab4aee23 Update deps 2021-06-07 15:54:56 +02:00
Frank Denis a2ebe0c4a4 dnscrypt-proxy/odoh.go -> dnscrypt-proxy/oblivious_doh.go 2021-06-07 15:53:11 +02:00
Frank Denis e11b8b0c04 + ODoH 2021-06-07 15:52:52 +02:00
Frank Denis 527c38ebc4 Update CI 2021-06-07 14:27:22 +02:00
Frank Denis 083fa0ad3c Add an extra retry since ODoH servers are currently unstable 2021-06-07 13:49:37 +02:00
Frank Denis f5a69c3bdc Reduce delay 2021-06-07 13:46:44 +02:00
Frank Denis 9e96bbc20b Continue, don't return 2021-06-07 13:44:08 +02:00
Frank Denis a181a23263 Send a dummy initial query before RTT measurement in ODoH 2021-06-07 13:42:33 +02:00
Frank Denis fad415f05a Update example documentation 2021-06-07 13:37:08 +02:00
Frank Denis 29613096da ODoH servers should not require a static configuration 2021-06-07 13:21:58 +02:00
Frank Denis 7980af6f46 Error propagation 2021-06-07 12:38:36 +02:00
Frank Denis 94151f9f96 Use ODoH relays in probes 2021-06-07 12:23:26 +02:00
Frank Denis a11da2d4fb ODoH: check certificate hashes 2021-06-07 12:09:27 +02:00
Frank Denis e0483bbb27 Pretend not to always use the first ODoH config 2021-06-07 12:06:36 +02:00
Frank Denis b35e27bd51 Shuffle ODoH target configs and use different NX queries 2021-06-07 12:05:42 +02:00
Frank Denis 4a4f69edb7 ODoH: only store working configurations
Actually, we only store the first one right now.

We should at least randomize them.
2021-06-07 12:02:21 +02:00
Frank Denis 96b05e57ca Preliminary propoer ODoH initialization 2021-06-07 11:47:11 +02:00
Frank Denis 56f2e9adcc server_name is ignored for x509 certs 2021-06-07 11:27:33 +02:00
Frank Denis dc99f1bc2c If you need this, implement it 2021-06-07 11:26:37 +02:00
Frank Denis 0d81fa2796 Remove doh_client_x509_auth stuf from fetchServerInfo
It doesn't belong there, and that feature doesn't do what it's
documented to do. It sets client certificates globally instead of
doing it per server.
2021-06-07 11:23:48 +02:00
Frank Denis 402860e2a6 ODoH broke DNSCrypt relays with wildcards - repair 2021-06-07 11:06:41 +02:00
Frank Denis cd45f64c18 ODoH: until relay auto selection is implemented, pick random ones 2021-06-07 11:00:21 +02:00
Frank Denis 27a82c54c8 ODoH: handle relay IP addresses 2021-06-07 10:46:01 +02:00
Frank Denis dce4db4c86 Construct net.URL directly 2021-06-07 10:08:55 +02:00
Frank Denis 525927e797 Don't use net/http 2021-06-07 10:05:20 +02:00
Frank Denis 3159bc3191 CI: use odoh-crypto-sx and odohrelay-fastly for testing ODoH 2021-06-06 22:42:33 +02:00
Frank Denis e57d5173e9 Support GET in ODoH targets 2021-06-06 01:22:48 +02:00
Frank Denis f542edacaa ODoH: until detection is in place, without a relay, prefer GET 2021-06-06 01:15:28 +02:00
Frank Denis 92792f0e8b Prevent remotely triggerable crash in ODoH config parser 2021-06-06 01:05:14 +02:00
Frank Denis 1cdb71cd7c Avoid double slashes in ODoH relay URLs 2021-06-06 01:01:39 +02:00
Frank Denis d2947cad75 Unbreak compilation 2021-06-06 00:14:56 +02:00
Frank Denis 3cf5c1ab8e Limit the number of ODoH target configs 2021-06-05 18:35:45 +02:00
Frank Denis 06135b6141 Reduce MaxHTTPBodyLength 2021-06-05 18:29:13 +02:00
Frank Denis 44f3db31ee Just a safeguard 2021-06-05 17:57:48 +02:00
Frank Denis 0a1d3b725c Rename ODoHTarget to ODoHTargetConfig for clarity 2021-06-05 17:49:19 +02:00
Frank Denis 2cf29f9fab CI: check the tests after running them
The ODoH tests don't seem to pass.
2021-06-05 17:04:35 +02:00
Frank Denis e27419f73d x509.SystemCertPool() may fail 2021-06-03 20:59:05 +02:00
Frank Denis ddcc40c954 Hardcode Let's Encrypt ISRG X1 cert
Some operating systems don't include it yet.

Thanks to @rs for the heads up
2021-06-03 12:48:33 +02:00
Frank Denis 14ef11447e Pasto, thanks to @lifenjoiner 2021-05-13 10:30:57 +02:00
Frank Denis 6e8628f796 Print an error if a block/allow rule contains more than a pattern
... and it is not a time range.
2021-05-12 17:43:13 +02:00
Frank Denis 31f4d7aa03 Do not ignore ODoH encryption errors 2021-05-09 16:16:38 +02:00
Frank Denis f9cecd1215 Update miekg/dns 2021-05-07 20:28:25 +02:00
Frank Denis 30779a40a6 Remove sysctl list, which is now updated any more
Fixes #1694
2021-05-01 01:16:35 +02:00
Frank Denis 367b5062ec Add another IP block list 2021-04-30 20:51:22 +02:00
Frank Denis d751781996 Update deps 2021-04-27 14:28:39 +02:00
Frank Denis 58e1410e66 Nits 2021-04-17 16:42:18 +02:00
Frank Denis e2e32406fb Improve ODoH log messages 2021-04-17 16:41:10 +02:00
Christopher Wood 23588733ae
Synchronously update the target configuration upon failure. (#1671)
* Synchronously update the target configuration upon failure.

* Notice a serverInfo failure when key updates fail.

* Add server name to debug logs.
2021-04-17 16:35:55 +02:00
Frank Denis 9759dd90a2 Limit the number of dependabot pull requests 2021-04-14 18:26:38 +02:00
milgradesec 754c2bdb93
Create dependabot.yml (#1670) 2021-04-14 18:25:51 +02:00
Alison Winters eda8dd5181
replace TrimFunc(s, IsSpace) with TrimSpace for ASCII optimization (#1663) 2021-04-05 11:46:57 +02:00
Christopher Wood 03413eae2f
Add ODoH test files. (#1656) 2021-03-30 15:11:09 +02:00
Frank Denis 81692a3a80 Update xsecretbox again 2021-03-30 13:38:50 +02:00
Frank Denis 8213a96cd5 Revert "Remove the need for two chacha20 implementations"
This reverts commit 8e8a4bd024.
2021-03-30 12:29:07 +02:00
Frank Denis cee31b646e Update Go version in CI 2021-03-30 11:57:10 +02:00
Frank Denis 8e8a4bd024 Remove the need for two chacha20 implementations 2021-03-30 11:54:04 +02:00
Frank Denis 3efbacc0d4 Rename 2021-03-30 11:53:59 +02:00
Christopher Wood c748f93752 Add ODoH support. (#1653) 2021-03-30 11:53:51 +02:00
Frank Denis f7219b2bfa Recommend discussions 2021-03-30 10:56:01 +02:00
Frank Denis 09e5812a23 Send info messages to stdout if an output file has been specified
Fixes #1651
2021-03-27 18:39:24 +01:00
Frank Denis 873522c7f7 Add discussions link 2021-03-27 01:57:20 +01:00
Frank Denis 789e5bf162 ... 2021-03-27 01:51:44 +01:00
Frank Denis 795c56f518 Shorten 2021-03-27 01:31:24 +01:00
Frank Denis 4201e7f373 Rename 2021-03-27 01:28:53 +01:00
Frank Denis 1e3f03ab0c ... 2021-03-27 01:19:01 +01:00
Frank Denis 6430ae942c Nits 2021-03-26 09:49:25 +01:00
Frank Denis 76392285e2 Nits 2021-03-26 00:02:54 +01:00
Frank Denis 1ca67270b8 pluralize 2021-03-26 00:01:34 +01:00
Frank Denis a6ea636117 Rename questions to protocol 2021-03-25 23:50:34 +01:00
Frank Denis 0f7208f7ef up 2021-03-25 23:47:55 +01:00
Frank Denis 7d10a2c84d Update issue template 2021-03-25 23:42:10 +01:00
Frank Denis 10ded3d9f2 Update deps 2021-03-22 22:57:07 +01:00
Frank Denis 54d85d7298 Filters don't apply to static entries 2021-03-12 20:05:58 +01:00
Frank Denis 84ea8dc9b0 Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  Fix domain blocklist config example (#1632)
2021-03-08 15:36:53 +01:00
Frank Denis b1e96b69fd Save 1.4 MiB of memory 2021-03-08 15:36:42 +01:00
synthtech 6bdeaaa70c
Fix domain blocklist config example (#1632) 2021-03-02 01:04:48 +01:00
Frank Denis 09866acdb5 New miekg/dns to fix a resource leak (not affecting us) 2021-02-26 10:38:52 +01:00
Frank Denis 1ae2281588 Update miekg/dns 2021-02-24 15:38:04 +01:00
Frank Denis 34909babfb Typo 2021-02-20 19:11:06 +01:00
Frank Denis bbf4094508 Deps update 2021-02-20 18:55:58 +01:00
Frank Denis 1a82786e07 Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  Update codeql-analysis.yml (#1612)
2021-02-20 18:51:20 +01:00
Frank Denis c500287498 Rename fallback_resolvers to bootstrap_resolvers
Clarify what they are used for.

Remove the legacy `fallback_resolver`.
2021-02-20 18:50:42 +01:00
miracle091 1795c7be3a Update codeql-analysis.yml (#1612)
using a custom name for the job and removed a step not more needed

Signed-off-by: Frank Denis <github@pureftpd.org>
2021-02-15 01:17:51 +01:00
miracle091 1b4045a098
Update codeql-analysis.yml (#1612)
using a custom name for the job and removed a step not more needed
2021-02-08 17:17:23 +01:00
Frank Denis b131708cd6 Update miekg/dns 2021-02-02 09:48:11 +01:00
Frank Denis 472ab609e4 Update miekg/dns to v1.1.37 2021-01-30 19:29:12 +01:00
Frank Denis 6517bae0c3 Disable full hosts.oisd.nl, replace with the shorter version 2021-01-29 16:20:32 +01:00
Frank Denis 5d6b35213c Add the file name ;) 2021-01-22 18:39:04 +01:00
Frank Denis 96ba551836 Revert "The source tests are completely brok4n :("
This reverts commit a76ffb0143.
2021-01-22 17:50:01 +01:00
Frank Denis d8ff82cf45 IP addresses of queries over local DoH are now logged 2021-01-22 16:13:56 +01:00
lifenjoiner a9cf16b33e
Fix: Randomize source URLs (#1593) 2021-01-22 15:06:49 +01:00
Frank Denis 0ab9e30fa9 Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
* 'master' of github.com:DNSCrypt/dnscrypt-proxy:
  The source tests are completely brok4n :(
  Explain how to listen to all IP addresses
  In the query log, consider everything that's not UDP as TCP
2021-01-22 09:16:56 +01:00
Frank Denis d0f981156b Add the base inherited fd to the application logging fd
Fixes #1585
2021-01-22 09:15:40 +01:00
Frank Denis a76ffb0143 The source tests are completely brok4n :(
Fix at least the fact that URLs are now randomized
2021-01-21 14:59:34 +01:00
Frank Denis 53c8e25352 Explain how to listen to all IP addresses
Fixes #1588
2021-01-21 14:38:36 +01:00
Frank Denis ac0b9cdfe8 In the query log, consider everything that's not UDP as TCP
Fixes #1589
2021-01-21 14:35:06 +01:00
Frank Denis 213695f651 Update deps 2021-01-19 23:05:16 +01:00
Ian Bashford fcd9225121
Threadsafe update (#1579)
* threadsafe update for relays

* locks around registeredRelays
2021-01-09 22:44:32 +01:00
Frank Denis 85d268f2b9 Randomize source URLs
Fixes #1577
2021-01-04 16:41:39 +01:00
1533 changed files with 218898 additions and 53380 deletions

View File

@ -23,7 +23,7 @@ ln ../windows/* win64/
zip -9 -r dnscrypt-proxy-win64-${PACKAGE_VERSION:-dev}.zip win64
go clean
env GO386=387 GOOS=openbsd GOARCH=386 go build -mod vendor -ldflags="-s -w"
env GO386=softfloat GOOS=openbsd GOARCH=386 go build -mod vendor -ldflags="-s -w"
mkdir openbsd-i386
ln dnscrypt-proxy openbsd-i386/
ln ../LICENSE example-dnscrypt-proxy.toml localhost.pem example-*.txt openbsd-i386/
@ -141,6 +141,13 @@ ln dnscrypt-proxy linux-mips64le/
ln ../LICENSE example-dnscrypt-proxy.toml localhost.pem example-*.txt linux-mips64le/
tar czpvf dnscrypt-proxy-linux_mips64le-${PACKAGE_VERSION:-dev}.tar.gz linux-mips64le
go clean
env CGO_ENABLED=0 GOOS=linux GOARCH=riscv64 go build -mod vendor -ldflags="-s -w"
mkdir linux-riscv64
ln dnscrypt-proxy linux-riscv64/
ln ../LICENSE example-dnscrypt-proxy.toml localhost.pem example-*.txt linux-riscv64/
tar czpvf dnscrypt-proxy-linux_riscv64-${PACKAGE_VERSION:-dev}.tar.gz linux-riscv64
go clean
env GOOS=darwin GOARCH=amd64 go build -mod vendor -ldflags="-s -w"
mkdir macos-x86_64

58
.ci/ci-package.sh Executable file
View File

@ -0,0 +1,58 @@
#! /bin/sh
PACKAGE_VERSION="$1"
cd dnscrypt-proxy || exit 1
# setup the environment
sudo apt-get update -y
sudo apt-get install -y wget wine dotnet-sdk-6.0
sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install -y wine32
sudo apt-get install -y unzip
export WINEPREFIX="$HOME"/.wine32
export WINEARCH=win32
export WINEDEBUG=-all
wget https://dl.winehq.org/wine/wine-mono/8.1.0/wine-mono-8.1.0-x86.msi
WINEPREFIX="$HOME/.wine32" WINEARCH=win32 wineboot --init
WINEPREFIX="$HOME/.wine32" WINEARCH=win32 wine msiexec /i wine-mono-8.1.0-x86.msi
mkdir "$HOME"/.wine32/drive_c/temp
mkdir -p "$HOME"/.wine/drive_c/temp
wget https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip -nv -O wix.zip
unzip wix.zip -d "$HOME"/wix
rm -f wix.zip
builddir=$(pwd)
srcdir=$(
cd ..
pwd
)
version=$PACKAGE_VERSION
cd "$HOME"/wix || exit
ln -s "$builddir" "$HOME"/wix/build
ln -s "$srcdir"/contrib/msi "$HOME"/wix/wixproj
echo "builddir: $builddir"
# build the msi's
#################
for arch in x64 x86; do
binpath="win32"
if [ "$arch" = "x64" ]; then
binpath="win64"
fi
echo $arch
wine candle.exe -dVersion="$version" -dPlatform=$arch -dPath=build\\$binpath -arch $arch wixproj\\dnscrypt.wxs -out build\\dnscrypt-$arch.wixobj
wine light.exe -out build\\dnscrypt-proxy-$arch-"$version".msi build\\dnscrypt-$arch.wixobj -sval
done
cd "$builddir" || exit

View File

@ -66,10 +66,15 @@ t || dig -p${DNS_PORT} +dnssec darpa.mil @127.0.0.1 2>&1 | grep -Fvq 'RRSIG' ||
t || dig -p${DNS_PORT} +dnssec www.darpa.mil @127.0.0.1 2>&1 | grep -Fvq 'RRSIG' || fail
section
t || dig -p${DNS_PORT} +short cloaked.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1' || fail
t || dig -p${DNS_PORT} +short www.cloaked2.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1' || fail
t || dig -p${DNS_PORT} +short cloakedunregistered.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1' || fail
t || dig -p${DNS_PORT} +short MX cloakedunregistered.com @127.0.0.1 | grep -Fq 'locally blocked' || fail
t || dig -p${DNS_PORT} +short MX example.com @127.0.0.1 | grep -Fvq 'locally blocked' || fail
t || dig -p${DNS_PORT} NS cloakedunregistered.com @127.0.0.1 | grep -Fiq 'gtld-servers.net' || fail
t || dig -p${DNS_PORT} +short www.cloakedunregistered2.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1' || fail
t || dig -p${DNS_PORT} +short www.dnscrypt-test @127.0.0.1 | grep -Fq '192.168.100.100' || fail
t || dig -p${DNS_PORT} a.www.dnscrypt-test @127.0.0.1 | grep -Fq 'NXDOMAIN' || fail
t || dig -p${DNS_PORT} +short ptr 101.100.168.192.in-addr.arpa. @127.0.0.1 | grep -Eq 'www.dnscrypt-test.com' || fail
t || dig -p${DNS_PORT} +short ptr 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.d.f.ip6.arpa. @127.0.0.1 | grep -Eq 'ipv6.dnscrypt-test.com' || fail
section
t || dig -p${DNS_PORT} telemetry.example @127.0.0.1 | grep -Fq 'locally blocked' || fail
@ -117,25 +122,20 @@ t || grep -Eq 'invalid.*SYNTH' query.log || fail
t || grep -Eq '168.192.in-addr.arpa.*SYNTH' query.log || fail
t || grep -Eq 'darpa.mil.*FORWARD' query.log || fail
t || grep -Eq 'www.darpa.mil.*FORWARD' query.log || fail
t || grep -Eq 'cloaked.com.*CLOAK' query.log || fail
t || grep -Eq 'www.cloaked2.com.*CLOAK' query.log || fail
t || grep -Eq 'cloakedunregistered.com.*CLOAK' query.log || fail
t || grep -Eq 'www.cloakedunregistered2.com.*CLOAK' query.log || fail
t || grep -Eq 'www.dnscrypt-test.*CLOAK' query.log || fail
t || grep -Eq 'a.www.dnscrypt-test.*NXDOMAIN' query.log || fail
t || grep -Eq 'telemetry.example.*REJECT' query.log || fail
t || grep -Eq 'dns.google.*REJECT' query.log || fail
t || grep -Eq 'tracker.xdebian.org.*REJECT' query.log || fail
t || grep -Eq 'tracker.debian.org.*PASS' query.log || fail
t || cut -f2 query.log | grep -Eq -- '-' || fail
t || grep -Eq '[.].*NS.*PASS' query.log || fail
section
t || grep -Fq 'tracker.debian.org' allowed-names.log || fail
t || grep -Fq '*.tracker.debian' allowed-names.log || fail
if [ -s error.log ]; then
cat *.log
exit 1
fi
section
../dnscrypt-proxy/dnscrypt-proxy -loglevel 3 -config test3-dnscrypt-proxy.toml -pidfile /tmp/dnscrypt-proxy.pidfile &
sleep 5
@ -145,4 +145,20 @@ t || dig -p${DNS_PORT} A microsoft.com @127.0.0.1 | grep -Fq "NOERROR" || fail
t || dig -p${DNS_PORT} A MICROSOFT.COM @127.0.0.1 | grep -Fq "NOERROR" || fail
kill $(cat /tmp/dnscrypt-proxy.pidfile)
sleep 5
sleep 5
section
../dnscrypt-proxy/dnscrypt-proxy -loglevel 3 -config test-odoh-proxied.toml -pidfile /tmp/odoh-proxied.pidfile &
sleep 5
section
t || dig -p${DNS_PORT} A microsoft.com @127.0.0.1 | grep -Fq "NOERROR" || fail
t || dig -p${DNS_PORT} A cloudflare.com @127.0.0.1 | grep -Fq "NOERROR" || fail
kill $(cat /tmp/odoh-proxied.pidfile)
sleep 5
if [ -s error.log ]; then
cat *.log
exit 1
fi

View File

@ -1,3 +1,5 @@
cloaked.* one.one.one.one
*.cloaked2.* one.one.one.one # inline comment
=www.dnscrypt-test 192.168.100.100
cloakedunregistered.* one.one.one.one
*.cloakedunregistered2.* one.one.one.one # inline comment
=www.dnscrypt-test 192.168.100.100
=www.dnscrypt-test.com 192.168.100.101
=ipv6.dnscrypt-test.com fd02::1

View File

@ -0,0 +1,17 @@
server_names = ['odohtarget']
listen_addresses = ['127.0.0.1:5300']
[query_log]
file = 'query.log'
[static]
[static.'odohtarget']
stamp = 'sdns://BQcAAAAAAAAADm9kb2guY3J5cHRvLnN4Ci9kbnMtcXVlcnk'
[static.'odohrelay']
stamp = 'sdns://hQcAAAAAAAAADDg5LjM4LjEzMS4zOAAYb2RvaC1ubC5hbGVrYmVyZy5uZXQ6NDQzBi9wcm94eQ'
[anonymized_dns]
routes = [
{ server_name='odohtarget', via=['odohrelay'] }
]

View File

@ -10,6 +10,7 @@ block_unqualified = true
block_undelegated = true
forwarding_rules = 'forwarding-rules.txt'
cloaking_rules = 'cloaking-rules.txt'
cloak_ptr = true
cache = true
[local_doh]

View File

@ -13,9 +13,7 @@ cache = true
[query_log]
file = 'query.log'
[static]
[static.'myserver']
stamp = 'sdns://AQcAAAAAAAAADjIxMi40Ny4yMjguMTM2IOgBuE6mBr-wusDOQ0RbsV66ZLAvo8SqMa4QY2oHkDJNHzIuZG5zY3J5cHQtY2VydC5mci5kbnNjcnlwdC5vcmc'
stamp = 'sdns://AQcAAAAAAAAADjIxMi40Ny4yMjguMTM2IOgBuE6mBr-wusDOQ0RbsV66ZLAvo8SqMa4QY2oHkDJNHzIuZG5zY3J5cHQtY2VydC5mci5kbnNjcnlwdC5vcmc'

View File

@ -1,38 +0,0 @@
---
name: "\U0001F41E Bug report"
about: Create a report about a reproducible bug
title: ''
labels: ''
assignees: ''
---
<!-- Thanks for the bug report! -->
## *Who* is the bug affecting?
## *What* is affected by this bug?
## *When* does this occur?
## *Where* does it happen?
## *How* do we replicate the issue?
<!-- Please list all the steps required to reliably replicate it, starting from a newly installed operating system -->
## Expected behavior (i.e. solution)
## Other Comments

65
.github/ISSUE_TEMPLATE/bugs.md vendored Normal file
View File

@ -0,0 +1,65 @@
---
name: "\U0001F41E Issues"
about: Bug reports
title: ''
labels: ''
assignees: ''
---
THE TRACKER IS DEDICATED TO KEEPING TRACK OF *BUGS*,
preferably after they have been already discussed and confirmed to be reproducible.
FOR ASSISTANCE, PLEASE CLOSE THIS FORM AND USE THE DISCUSSIONS SECTION INSTEAD:
https://github.com/DNSCrypt/dnscrypt-proxy/discussions/categories/q-a
~~~
Reported bugs must reproducible in the context described in the "Context" section.
Installation and configuration issues are not bugs, but individual assistance request.
Context: the LATEST version of `dnscrypt-proxy` (precompiled binaries downloaded from this repository) is correctly installed and configured on your system, but something doesn't seem to produce the expected result.
If the bug is not trivial to reproduce on any platform, please include ALL the steps required to reliably duplicate it, on a vanilla, generic install of macOS, Windows, OpenBSD or Ubuntu Linux system, in their most current version.
If you don't have any clear understanding of the issue or can't enumerate the steps to reproduce it, open a discussion instead:
https://github.com/DNSCrypt/dnscrypt-proxy/discussions
## Output of the following commands:
./dnscrypt-proxy -version
./dnscrypt-proxy -check
./dnscrypt-proxy -resolve example.com
- [ ] Initially raised as discussion #...
## *What* is affected by this bug?
## *When* does this occur?
## *Where* does it happen?
## *How* do we replicate the issue?
<!-- Please list all the steps required to reliably replicate it, starting from a newly installed operating system -->
## Expected behavior (i.e. solution)
## Other Comments

View File

@ -1,42 +0,0 @@
---
name: "\U0001F389 Feature request"
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
## Is your feature request related to a problem?
<!-- A clear and concise description of what the problem is. -->
## Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->
## Alternatives you've considered
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
## Who will that feature be useful to?
<!-- Is it solving a common problem, or is it an improvement that all current users will benefit from? -->
## What have you done already?
<!-- If you already built a prototype or a complete implementation of that feature, where is it possible to see it? -->
## What are you going to contribute?
<!-- Besides testing volunteering work made by other people, what are you going to contribute in order to help have that feature implemented, tested and documented? -->
## Additional context
<!-- Add any other context about the feature request here. -->

View File

@ -1,24 +0,0 @@
---
name: "❓ Question"
about: Ask a question about dnscrypt-proxy or the DNSCrypt protocol
title: ''
labels: ''
assignees: ''
---
## Subject
<!-- Select one: -->
* [ ] I'd like to contribute to the project (code, documentation, website, advocacy, integration, ...)
* [ ] I want to run my own DNSCrypt and/or DoH server
* [ ] I'd like to have my existing server added to a public list of encrypted DNS servers
* [ ] I'd like to announce a new application/website/article related to the project
* [ ] I have questions about the DNSCrypt or Anonymized DNSCrypt protocols
* [ ] I am a MacOS developer and I'd like to contribute a GUI for dnscrypt-proxy
* [ ] I am an Android developer and I'd like to contribute an Android app
* [ ] I am a technical writer and I'd like to help write an IETF Internet Draft for the DNSCrypt protocol
* [ ] Other
## Description

22
.github/ISSUE_TEMPLATE/suggestions.md vendored Normal file
View File

@ -0,0 +1,22 @@
---
name: "🙋🏽 Planned changes"
about: List of planned changes
title: ''
labels: ''
assignees: ''
---
The starting point should be a discussion.
https://github.com/DNSCrypt/dnscrypt-proxy/discussions/
Suggestions may be raised as an "Ideas" discussion.
We can then determine if the discussion needs to be escalated into a "planned change" or not.
This will help us ensure that the issue tracker properly reflects ongoing or needed work on the project.
---
- [ ] Initially raised as discussion #...

15
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,15 @@
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
# Maintain dependencies for Go
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10

12
.github/workflows/autocloser.yml vendored Normal file
View File

@ -0,0 +1,12 @@
name: Autocloser
on: [issues]
jobs:
autoclose:
runs-on: ubuntu-latest
steps:
- name: Autoclose issues that did not follow issue template
uses: roots/issue-closer@v1.2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-close-message: "This issue was automatically closed because it did not follow the issue template. We use the issue tracker exclusively for bug reports and feature additions that have been previously discussed. However, this issue appears to be a support request. Please use the discussion forums for support requests."
issue-pattern: ".*(do we replicate the issue|Expected behavior|raised as discussion|# Impact).*"

View File

@ -7,24 +7,26 @@ on:
- cron: '0 14 * * 6'
jobs:
CodeQL-Build:
analyze:
name: Analyze
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
uses: actions/checkout@v4
with:
fetch-depth: 2
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v3

View File

@ -25,17 +25,18 @@ jobs:
steps:
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
- name: Set up Go 1.16beta1
uses: actions/setup-go@v1
- name: Check out code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.16beta1
go-version: 1
check-latest: true
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Test suite
run: |
go version
@ -48,6 +49,11 @@ jobs:
run: |
.ci/ci-build.sh "${{ steps.get_version.outputs.VERSION }}"
- name: Package
if: startsWith(github.ref, 'refs/tags/')
run: |
.ci/ci-package.sh "${{ steps.get_version.outputs.VERSION }}"
- name: Install minisign and sign
if: startsWith(github.ref, 'refs/tags/')
run: |
@ -77,7 +83,7 @@ jobs:
prerelease: false
- name: Upload release assets
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -86,3 +92,4 @@ jobs:
dnscrypt-proxy/*.zip
dnscrypt-proxy/*.tar.gz
dnscrypt-proxy/*.minisig
dnscrypt-proxy/*.msi

View File

@ -6,7 +6,7 @@ jobs:
Scan-Build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: Perform ShiftLeft Scan
uses: ShiftLeftSecurity/scan-action@master
@ -18,6 +18,6 @@ jobs:
output: reports
- name: Upload report
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: reports

3
.gitignore vendored
View File

@ -14,3 +14,6 @@ dnscrypt-proxy/dnscrypt-proxy
.ci/*.md
.ci/*.md.minisig
.ci/test-dnscrypt-proxy.toml
contrib/msi/*.msi
contrib/msi/*.wixpdb
contrib/msi/*.wixobj

View File

@ -1,3 +1,84 @@
# Version 2.1.5
- dnscrypt-proxy can be compiled with Go 1.21.0+
- Responses to blocked queries now include extended error codes
- Reliability of connections using HTTP/3 has been improved
- New configuration directive: `tls_key_log_file`. When defined, this
is the path to a file where TLS secret keys will be written to, so
that DoH traffic can be locally inspected.
# Version 2.1.4
- Fixes a regression from version 2.1.3: when cloaking was enabled,
blocked responses were returned for records that were not A/AAAA/PTR
even for names that were not in the cloaked list.
# Version 2.1.3
- DNS-over-HTTP/3 (QUIC) should be more reliable. In particular,
version 2.1.2 required another (non-QUIC) resolver to be present for
bootstrapping, or the resolver's IP address to be present in the
stamp. This is not the case any more.
- dnscrypt-proxy is now compatible with Go 1.20+
- Commands (-check, -show-certs, -list, -list-all) now ignore log
files and directly output the result to the standard output.
- The `cert_ignore_timestamp` configuration switch is now documented.
It allows ignoring timestamps for DNSCrypt certificate verification,
until a first server is available. This should only be used on devices
that don't have any ways to set the clock before DNS service is up.
However, a safer alternative remains to use an NTP server with a fixed
IP address (such as time.google.com), configured in the captive portals
file.
- Cloaking: when a name is cloaked, unsupported record types now
return a blocked response rather than the actual records.
- systemd: report Ready earlier as dnscrypt-proxy can itself manage
retries for updates/refreshes.
# Version 2.1.2
- Support for DoH over HTTP/3 (DoH3, HTTP over QUIC) has been added.
Compatible servers will automatically use it. Note that QUIC uses UDP
(usually over port 443, like DNSCrypt) instead of TCP.
- In previous versions, memory usage kept growing due to channels not
being properly closed, causing goroutines to pile up. This was fixed,
resulting in an important reduction of memory usage. Thanks to
@lifenjoiner for investigating and fixing this!
- DNS64: `CNAME` records are now translated like other responses.
Thanks to @ignoramous for this!
- A relay whose name has been configured, but doesn't exist in the
list of available relays is now a hard error. Thanks to @lifenjoiner!
- Mutexes/locking: bug fixes and improvements, by @ignoramous
- Official packages now include linux/riscv64 builds.
- `dnscrypt-proxy -resolve` now reports if ECS (EDNS-clientsubnet) is
supported by the server.
- `dnscrypt-proxy -list` now includes ODoH (Oblivious DoH) servers.
- Local DoH: queries made using the `GET` method are now handled.
- The service can now be installed on OpenRC-based systems.
- `PTR` queries are now supported for cloaked domains. Contributed by
Ian Bashford, thanks!
# Version 2.1.1
This is a bugfix only release, addressing regressions introduced in
version 2.1.0:
- When using DoH, cached responses were not served any more when
experiencing connectivity issues. This has been fixed.
- Time attributes in allow/block lists were ignored. This has been
fixed.
- The TTL as served to clients is now rounded and starts decreasing
before the first query is received.
- Time-based rules are properly handled again in
generate-domains-blocklist.
- DoH/ODoH: entries with an IP address and using a non-standard port
used to require help from a bootstrap resolver. This is not the case
any more.
# Version 2.1.0
- `dnscrypt-proxy` now includes support for Oblivious DoH.
- If the proxy is overloaded, cached and synthetic queries now keep being
served, while non-cached queries are delayed.
- A deprecation warning was added for `fallback_resolvers`.
- Source URLs are now randomized.
- On some platforms, redirecting the application log to a file was not
compatible with user switching; this has been fixed.
- `fallback_resolvers` was renamed to `bootstrap_resolvers` for
clarity. Please update your configuration file accordingly.
# Version 2.0.45
- Configuration changes (to be required in versions 2.1.x):
* `[blacklist]` has been renamed to `[blocked_names]`

33
LICENSE
View File

@ -1,18 +1,15 @@
/*
* ISC License
*
* Copyright (c) 2018-2021
* Frank Denis <j at pureftpd dot org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
ISC License
Copyright (c) 2018-2023, Frank Denis <j at pureftpd dot org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View File

@ -2,17 +2,18 @@
[![Financial Contributors on Open Collective](https://opencollective.com/dnscrypt/all/badge.svg?label=financial+contributors)](https://opencollective.com/dnscrypt)
[![DNSCrypt-Proxy Release](https://img.shields.io/github/release/dnscrypt/dnscrypt-proxy.svg?label=Latest%20Release&style=popout)](https://github.com/dnscrypt/dnscrypt-proxy/releases/latest)
[![Build Status](https://github.com/DNSCrypt/dnscrypt-proxy/workflows/CI%20and%20optionally%20publish/badge.svg)](https://github.com/DNSCrypt/dnscrypt-proxy/actions)
[![Build Status](https://github.com/DNSCrypt/dnscrypt-proxy/actions/workflows/releases.yml/badge.svg)](https://github.com/DNSCrypt/dnscrypt-proxy/actions/workflows/releases.yml)
![CodeQL scan](https://github.com/DNSCrypt/dnscrypt-proxy/workflows/CodeQL%20scan/badge.svg)
![ShiftLeft Scan](https://github.com/DNSCrypt/dnscrypt-proxy/workflows/ShiftLeft%20Scan/badge.svg)
[![#dnscrypt-proxy:matrix.org](https://img.shields.io/matrix/dnscrypt-proxy:matrix.org.svg?label=DNSCrypt-Proxy%20Matrix%20Chat&server_fqdn=matrix.org&style=popout)](https://matrix.to/#/#dnscrypt-proxy:matrix.org)
## Overview
A flexible DNS proxy, with support for modern encrypted DNS protocols such as [DNSCrypt v2](https://dnscrypt.info/protocol), [DNS-over-HTTPS](https://www.rfc-editor.org/rfc/rfc8484.txt) and [Anonymized DNSCrypt](https://github.com/DNSCrypt/dnscrypt-protocol/blob/master/ANONYMIZED-DNSCRYPT.txt).
A flexible DNS proxy, with support for modern encrypted DNS protocols such as [DNSCrypt v2](https://dnscrypt.info/protocol), [DNS-over-HTTPS](https://www.rfc-editor.org/rfc/rfc8484.txt), [Anonymized DNSCrypt](https://github.com/DNSCrypt/dnscrypt-protocol/blob/master/ANONYMIZED-DNSCRYPT.txt) and [ODoH (Oblivious DoH)](https://github.com/DNSCrypt/dnscrypt-resolvers/blob/master/v3/odoh-servers.md).
* **[dnscrypt-proxy documentation](https://dnscrypt.info/doc) ← Start here**
* [DNSCrypt project home page](https://dnscrypt.info/)
* [Discussions](https://github.com/DNSCrypt/dnscrypt-proxy/discussions)
* [DNS-over-HTTPS and DNSCrypt resolvers](https://dnscrypt.info/public-servers)
* [Server and client implementations](https://dnscrypt.info/implementations)
* [DNS stamps](https://dnscrypt.info/stamps)
@ -24,7 +25,7 @@ Available as source code and pre-built binaries for most operating systems and a
## Features
* DNS traffic encryption and authentication. Supports DNS-over-HTTPS (DoH) using TLS 1.3, DNSCrypt and Anonymized DNS
* DNS traffic encryption and authentication. Supports DNS-over-HTTPS (DoH) using TLS 1.3 and QUIC, DNSCrypt, Anonymized DNS and ODoH
* Client IP addresses can be hidden using Tor, SOCKS proxies or Anonymized DNS relays
* DNS query monitoring, with separate log files for regular and suspicious queries
* Filtering: block ads, malware, and other unwanted content. Compatible with all DNS services
@ -37,7 +38,7 @@ Available as source code and pre-built binaries for most operating systems and a
* Automatic background updates of resolvers lists
* Can force outgoing connections to use TCP
* Compatible with DNSSEC
* Includes a local DoH server in order to support ECHO (ESNI)
* Includes a local DoH server in order to support ECH (ESNI)
## Pre-built binaries
@ -59,7 +60,8 @@ Up-to-date, pre-built binaries are available for:
* Linux/mips64le
* Linux/x86
* Linux/x86_64
* MacOS X
* macOS/arm64
* macOS/x86_64
* NetBSD/x86
* NetBSD/x86_64
* OpenBSD/x86
@ -73,7 +75,7 @@ How to use these files, as well as how to verify their signatures, are documente
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
This project exists thanks to all the people who contribute.
<a href="https://github.com/dnscrypt/dnscrypt-proxy/graphs/contributors"><img src="https://opencollective.com/dnscrypt/contributors.svg?width=890&button=false" /></a>
### Financial Contributors

21
contrib/msi/Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM ubuntu:latest
MAINTAINER dnscrypt-authors
RUN apt-get update && \
apt-get install -y wget wine dotnet-sdk-6.0 && \
dpkg --add-architecture i386 && apt-get update && apt-get install -y wine32
ENV WINEPREFIX=/root/.wine32 WINEARCH=win32 WINEDEBUG=-all
RUN wget https://dl.winehq.org/wine/wine-mono/8.1.0/wine-mono-8.1.0-x86.msi && \
WINEPREFIX="$HOME/.wine32" WINEARCH=win32 wineboot --init && \
WINEPREFIX="$HOME/.wine32" WINEARCH=win32 wine msiexec /i wine-mono-8.1.0-x86.msi && \
mkdir $WINEPREFIX/drive_c/temp && \
apt-get install -y unzip && \
wget https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip -nv -O wix.zip && \
unzip wix.zip -d /wix && \
rm -f wix.zip
WORKDIR /wix

13
contrib/msi/README.md Normal file
View File

@ -0,0 +1,13 @@
# Scripts and utilities related to building an .msi (Microsoft Standard Installer) file.
## Docker test image for building an MSI locally
```sh
docker build . -f Dockerfile -t ubuntu:dnscrypt-msi
```
## Test building msi files for intel win32 & win64
```sh
./build.sh
```

30
contrib/msi/build.sh Executable file
View File

@ -0,0 +1,30 @@
#! /bin/sh
version=0.0.0
gitver=$(git describe --tags --always --match="[0-9]*.[0-9]*.[0-9]*" --exclude='*[^0-9.]*')
if [ "$gitver" != "" ]; then
version=$gitver
fi
# build the image by running: docker build . -f Dockerfile -t ubuntu:dnscrypt-msi
if [ "$(docker image list -q ubuntu:dnscrypt-msi)" = "" ]; then
docker build . -f Dockerfile -t ubuntu:dnscrypt-msi
fi
image=ubuntu:dnscrypt-msi
for arch in x64 x86; do
binpath="win32"
if [ "$arch" = "x64" ]; then
binpath="win64"
fi
src=$(
cd ../../dnscrypt-proxy/$binpath || exit
pwd
)
echo "$src"
docker run --rm -v "$(pwd)":/wixproj -v "$src":/src $image wine candle.exe -dVersion="$version" -dPlatform=$arch -dPath=\\src -arch $arch \\wixproj\\dnscrypt.wxs -out \\wixproj\\dnscrypt-$arch.wixobj
docker run --rm -v "$(pwd)":/wixproj -v "$src":/src $image wine light.exe -out \\wixproj\\dnscrypt-proxy-$arch-"$version".msi \\wixproj\\dnscrypt-$arch.wixobj -sval
done

60
contrib/msi/dnscrypt.wxs Normal file
View File

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<?if $(var.Platform)="x64" ?>
<?define Program_Files="ProgramFiles64Folder"?>
<?else ?>
<?define Program_Files="ProgramFilesFolder"?>
<?endif ?>
<?ifndef var.Version?>
<?error Undefined Version variable?>
<?endif ?>
<?ifndef var.Path?>
<?error Undefined Path variable?>
<?endif ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*"
UpgradeCode="fbf99dd8-c21e-4f9b-a632-de53bb64c45e"
Name="dnscrypt-proxy"
Version="$(var.Version)"
Manufacturer="DNSCrypt"
Language="1033">
<Package InstallerVersion="200" Compressed="yes" Comments="Windows Installer Package" InstallScope="perMachine" />
<Media Id="1" Cabinet="product.cab" EmbedCab="yes" />
<MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed. Setup will now exit." />
<Upgrade Id="fbf99dd8-c21e-4f9b-a632-de53bb64c45e">
<UpgradeVersion Minimum="$(var.Version)" OnlyDetect="yes" Property="NEWERVERSIONDETECTED" />
<UpgradeVersion Minimum="2.1.0" Maximum="$(var.Version)" IncludeMinimum="yes" IncludeMaximum="no" Property="OLDERVERSIONBEINGUPGRADED" />
</Upgrade>
<Condition Message="A newer version of this software is already installed.">NOT NEWERVERSIONDETECTED</Condition>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.Program_Files)">
<Directory Id="INSTALLDIR" Name="DNSCrypt">
<Component Id="ApplicationFiles" Guid="7d693c0b-71d8-436a-9c84-60a11dc74092">
<File Id="dnscryptproxy.exe" KeyPath="yes" Source="$(var.Path)\dnscrypt-proxy.exe" DiskId="1"/>
<File Source="$(var.Path)\LICENSE"></File>
<File Source="$(var.Path)\service-install.bat"></File>
<File Source="$(var.Path)\service-restart.bat"></File>
<File Source="$(var.Path)\service-uninstall.bat"></File>
<File Source="$(var.Path)\example-dnscrypt-proxy.toml"></File>
</Component>
<Component Id="ConfigInstall" Guid="db7b691e-f7c7-4c9a-92e1-c6f21ce6430f" KeyPath="yes">
<Condition><![CDATA[CONFIGFILE]]></Condition>
<CopyFile Id="dnscryptproxytoml" DestinationDirectory="INSTALLDIR" DestinationName="dnscrypt-proxy.toml" SourceProperty="CONFIGFILE">
</CopyFile>
<RemoveFile Id="RemoveConfig" Directory="INSTALLDIR" Name="dnscrypt-proxy.toml" On="uninstall" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id="Complete" Level="1">
<ComponentRef Id="ApplicationFiles" />
<ComponentRef Id="ConfigInstall" />
</Feature>
</Product>
</Wix>

View File

@ -4,8 +4,8 @@ import (
"fmt"
"net"
"strings"
"sync"
"time"
"unicode"
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
@ -16,14 +16,13 @@ type CaptivePortalEntryIPs []net.IP
type CaptivePortalMap map[string]CaptivePortalEntryIPs
type CaptivePortalHandler struct {
cancelChannels []chan struct{}
wg sync.WaitGroup
cancelChannel chan struct{}
}
func (captivePortalHandler *CaptivePortalHandler) Stop() {
for _, cancelChannel := range captivePortalHandler.cancelChannels {
cancelChannel <- struct{}{}
<-cancelChannel
}
close(captivePortalHandler.cancelChannel)
captivePortalHandler.wg.Wait()
}
func (ipsMap *CaptivePortalMap) GetEntry(msg *dns.Msg) (*dns.Question, *CaptivePortalEntryIPs) {
@ -117,20 +116,30 @@ func handleColdStartClient(clientPc *net.UDPConn, cancelChannel chan struct{}, i
return false
}
func addColdStartListener(proxy *Proxy, ipsMap *CaptivePortalMap, listenAddrStr string, cancelChannel chan struct{}) error {
listenUDPAddr, err := net.ResolveUDPAddr("udp", listenAddrStr)
func addColdStartListener(
ipsMap *CaptivePortalMap,
listenAddrStr string,
captivePortalHandler *CaptivePortalHandler,
) error {
network := "udp"
isIPv4 := isDigit(listenAddrStr[0])
if isIPv4 {
network = "udp4"
}
listenUDPAddr, err := net.ResolveUDPAddr(network, listenAddrStr)
if err != nil {
return err
}
clientPc, err := net.ListenUDP("udp", listenUDPAddr)
clientPc, err := net.ListenUDP(network, listenUDPAddr)
if err != nil {
return err
}
captivePortalHandler.wg.Add(1)
go func() {
for !handleColdStartClient(clientPc, cancelChannel, ipsMap) {
for !handleColdStartClient(clientPc, captivePortalHandler.cancelChannel, ipsMap) {
}
clientPc.Close()
cancelChannel <- struct{}{}
captivePortalHandler.wg.Done()
}()
return nil
}
@ -139,13 +148,13 @@ func ColdStart(proxy *Proxy) (*CaptivePortalHandler, error) {
if len(proxy.captivePortalMapFile) == 0 {
return nil, nil
}
bin, err := ReadTextFile(proxy.captivePortalMapFile)
lines, err := ReadTextFile(proxy.captivePortalMapFile)
if err != nil {
dlog.Warn(err)
return nil, err
}
ipsMap := make(CaptivePortalMap)
for lineNo, line := range strings.Split(string(bin), "\n") {
for lineNo, line := range strings.Split(lines, "\n") {
line = TrimAndStripInlineComments(line)
if len(line) == 0 {
continue
@ -163,7 +172,7 @@ func ColdStart(proxy *Proxy) (*CaptivePortalHandler, error) {
}
var ips []net.IP
for _, ip := range strings.Split(ipsStr, ",") {
ipStr := strings.TrimFunc(ip, unicode.IsSpace)
ipStr := strings.TrimSpace(ip)
if ip := net.ParseIP(ipStr); ip != nil {
ips = append(ips, ip)
} else {
@ -176,16 +185,19 @@ func ColdStart(proxy *Proxy) (*CaptivePortalHandler, error) {
ipsMap[name] = ips
}
listenAddrStrs := proxy.listenAddresses
cancelChannels := make([]chan struct{}, 0)
captivePortalHandler := CaptivePortalHandler{
cancelChannel: make(chan struct{}),
}
ok := false
for _, listenAddrStr := range listenAddrStrs {
cancelChannel := make(chan struct{})
if err := addColdStartListener(proxy, &ipsMap, listenAddrStr, cancelChannel); err == nil {
cancelChannels = append(cancelChannels, cancelChannel)
err = addColdStartListener(&ipsMap, listenAddrStr, &captivePortalHandler)
if err == nil {
ok = true
}
}
captivePortalHandler := CaptivePortalHandler{
cancelChannels: cancelChannels,
if ok {
err = nil
}
proxy.captivePortalMap = &ipsMap
return &captivePortalHandler, nil
return &captivePortalHandler, err
}

View File

@ -4,12 +4,14 @@ import (
"bytes"
"encoding/binary"
"errors"
"io/ioutil"
"net"
"os"
"path"
"strconv"
"strings"
"unicode"
"github.com/jedisct1/dlog"
)
type CryptoConstruction uint16
@ -25,7 +27,7 @@ const (
)
const (
MaxHTTPBodyLength = 4000000
MaxHTTPBodyLength = 1000000
)
var (
@ -96,20 +98,6 @@ func Max(a, b int) int {
return b
}
func MinF(a, b float64) float64 {
if a < b {
return a
}
return b
}
func MaxF(a, b float64) float64 {
if a > b {
return a
}
return b
}
func StringReverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
@ -126,7 +114,7 @@ func StringTwoFields(str string) (string, string, bool) {
if pos == -1 {
return "", "", false
}
a, b := strings.TrimFunc(str[:pos], unicode.IsSpace), strings.TrimFunc(str[pos+1:], unicode.IsSpace)
a, b := strings.TrimSpace(str[:pos]), strings.TrimSpace(str[pos+1:])
if len(a) == 0 || len(b) == 0 {
return a, b, false
}
@ -156,7 +144,7 @@ func TrimAndStripInlineComments(str string) string {
str = str[:idx-1]
}
}
return strings.TrimFunc(str, unicode.IsSpace)
return strings.TrimSpace(str)
}
func ExtractHostAndPort(str string, defaultPort int) (host string, port int) {
@ -170,10 +158,40 @@ func ExtractHostAndPort(str string, defaultPort int) (host string, port int) {
}
func ReadTextFile(filename string) (string, error) {
bin, err := ioutil.ReadFile(filename)
bin, err := os.ReadFile(filename)
if err != nil {
return "", err
}
bin = bytes.TrimPrefix(bin, []byte{0xef, 0xbb, 0xbf})
return string(bin), nil
}
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
func maybeWritableByOtherUsers(p string) (bool, string, error) {
p = path.Clean(p)
for p != "/" && p != "." {
st, err := os.Stat(p)
if err != nil {
return false, p, err
}
mode := st.Mode()
if mode.Perm()&2 != 0 && !(st.IsDir() && mode&os.ModeSticky == os.ModeSticky) {
return true, p, nil
}
p = path.Dir(p)
}
return false, "", nil
}
func WarnIfMaybeWritableByOtherUsers(p string) {
if ok, px, err := maybeWritableByOtherUsers(p); ok {
if px == p {
dlog.Criticalf("[%s] is writable by other system users - If this is not intentional, it is recommended to fix the access permissions", p)
} else {
dlog.Warnf("[%s] can be modified by other system users because [%s] is writable by other users - If this is not intentional, it is recommended to fix the access permissions", p, px)
}
} else if err != nil {
dlog.Warnf("Error while checking if [%s] is accessible: [%s] : [%s]", p, px, err)
}
}

View File

@ -36,20 +36,21 @@ type Config struct {
DisabledServerNames []string `toml:"disabled_server_names"`
ListenAddresses []string `toml:"listen_addresses"`
LocalDoH LocalDoHConfig `toml:"local_doh"`
Daemonize bool
UserName string `toml:"user_name"`
ForceTCP bool `toml:"force_tcp"`
Timeout int `toml:"timeout"`
KeepAlive int `toml:"keepalive"`
Proxy string `toml:"proxy"`
CertRefreshDelay int `toml:"cert_refresh_delay"`
CertIgnoreTimestamp bool `toml:"cert_ignore_timestamp"`
EphemeralKeys bool `toml:"dnscrypt_ephemeral_keys"`
LBStrategy string `toml:"lb_strategy"`
LBEstimator bool `toml:"lb_estimator"`
BlockIPv6 bool `toml:"block_ipv6"`
BlockUnqualified bool `toml:"block_unqualified"`
BlockUndelegated bool `toml:"block_undelegated"`
UserName string `toml:"user_name"`
ForceTCP bool `toml:"force_tcp"`
HTTP3 bool `toml:"http3"`
Timeout int `toml:"timeout"`
KeepAlive int `toml:"keepalive"`
Proxy string `toml:"proxy"`
CertRefreshConcurrency int `toml:"cert_refresh_concurrency"`
CertRefreshDelay int `toml:"cert_refresh_delay"`
CertIgnoreTimestamp bool `toml:"cert_ignore_timestamp"`
EphemeralKeys bool `toml:"dnscrypt_ephemeral_keys"`
LBStrategy string `toml:"lb_strategy"`
LBEstimator bool `toml:"lb_estimator"`
BlockIPv6 bool `toml:"block_ipv6"`
BlockUnqualified bool `toml:"block_unqualified"`
BlockUndelegated bool `toml:"block_undelegated"`
Cache bool
CacheSize int `toml:"cache_size"`
CacheNegTTL uint32 `toml:"cache_neg_ttl"`
@ -79,11 +80,12 @@ type Config struct {
SourceRequireNoFilter bool `toml:"require_nofilter"`
SourceDNSCrypt bool `toml:"dnscrypt_servers"`
SourceDoH bool `toml:"doh_servers"`
SourceODoH bool `toml:"odoh_servers"`
SourceIPv4 bool `toml:"ipv4_servers"`
SourceIPv6 bool `toml:"ipv6_servers"`
MaxClients uint32 `toml:"max_clients"`
FallbackResolver string `toml:"fallback_resolver"`
FallbackResolvers []string `toml:"fallback_resolvers"`
BootstrapResolversLegacy []string `toml:"fallback_resolvers"`
BootstrapResolvers []string `toml:"bootstrap_resolvers"`
IgnoreSystemDNS bool `toml:"ignore_system_dns"`
AllWeeklyRanges map[string]WeeklyRangesStr `toml:"schedules"`
LogMaxSize int `toml:"log_files_max_size"`
@ -91,6 +93,7 @@ type Config struct {
LogMaxBackups int `toml:"log_files_max_backups"`
TLSDisableSessionTickets bool `toml:"tls_disable_session_tickets"`
TLSCipherSuite []uint16 `toml:"tls_cipher_suite"`
TLSKeyLogFile string `toml:"tls_key_log_file"`
NetprobeAddress string `toml:"netprobe_address"`
NetprobeTimeout int `toml:"netprobe_timeout"`
OfflineMode bool `toml:"offline_mode"`
@ -98,6 +101,7 @@ type Config struct {
RefusedCodeInResponses bool `toml:"refused_code_in_responses"`
BlockedQueryResponse string `toml:"blocked_query_response"`
QueryMeta []string `toml:"query_meta"`
CloakedPTR bool `toml:"cloak_ptr"`
AnonymizedDNS AnonymizedDNSConfig `toml:"anonymized_dns"`
DoHClientX509Auth DoHClientX509AuthConfig `toml:"doh_client_x509_auth"`
DoHClientX509AuthLegacy DoHClientX509AuthConfig `toml:"tls_client_auth"`
@ -113,7 +117,9 @@ func newConfig() Config {
LocalDoH: LocalDoHConfig{Path: "/dns-query"},
Timeout: 5000,
KeepAlive: 5,
CertRefreshConcurrency: 10,
CertRefreshDelay: 240,
HTTP3: false,
CertIgnoreTimestamp: false,
EphemeralKeys: false,
Cache: true,
@ -131,14 +137,16 @@ func newConfig() Config {
SourceIPv6: false,
SourceDNSCrypt: true,
SourceDoH: true,
SourceODoH: false,
MaxClients: 250,
FallbackResolvers: []string{DefaultFallbackResolver},
BootstrapResolvers: []string{DefaultBootstrapResolver},
IgnoreSystemDNS: false,
LogMaxSize: 10,
LogMaxAge: 7,
LogMaxBackups: 1,
TLSDisableSessionTickets: false,
TLSCipherSuite: nil,
TLSKeyLogFile: "",
NetprobeTimeout: 60,
OfflineMode: false,
RefusedCodeInResponses: false,
@ -153,6 +161,7 @@ func newConfig() Config {
AnonymizedDNS: AnonymizedDNSConfig{
DirectCertFallback: true,
},
CloakedPTR: false,
}
}
@ -252,7 +261,7 @@ type ServerSummary struct {
IPv6 bool `json:"ipv6"`
Addrs []string `json:"addrs,omitempty"`
Ports []int `json:"ports"`
DNSSEC bool `json:"dnssec"`
DNSSEC *bool `json:"dnssec,omitempty"`
NoLog bool `json:"nolog"`
NoFilter bool `json:"nofilter"`
Description string `json:"description,omitempty"`
@ -283,6 +292,7 @@ type ConfigFlags struct {
Resolve *string
List *bool
ListAll *bool
IncludeRelays *bool
JSONOutput *bool
Check *bool
ConfigFile *string
@ -311,8 +321,12 @@ func findConfigFile(configFile *string) (string, error) {
func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
foundConfigFile, err := findConfigFile(flags.ConfigFile)
if err != nil {
return fmt.Errorf("Unable to load the configuration file [%s] -- Maybe use the -config command-line switch?", *flags.ConfigFile)
return fmt.Errorf(
"Unable to load the configuration file [%s] -- Maybe use the -config command-line switch?",
*flags.ConfigFile,
)
}
WarnIfMaybeWritableByOtherUsers(foundConfigFile)
config := newConfig()
md, err := toml.DecodeFile(foundConfigFile, &config)
if err != nil {
@ -338,15 +352,18 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
dlog.SetLogLevel(dlog.SeverityInfo)
}
dlog.TruncateLogFile(config.LogFileLatest)
if config.UseSyslog {
proxy.showCerts = *flags.ShowCerts || len(os.Getenv("SHOW_CERTS")) > 0
isCommandMode := *flags.Check || proxy.showCerts || *flags.List || *flags.ListAll
if isCommandMode {
} else if config.UseSyslog {
dlog.UseSyslog(true)
} else if config.LogFile != nil {
dlog.UseLogFile(*config.LogFile)
if !*flags.Child {
FileDescriptors = append(FileDescriptors, dlog.GetFileDescriptor())
} else {
dlog.SetFileDescriptor(os.NewFile(uintptr(InheritedDescriptorsBase+FileDescriptorNum), "logFile"))
FileDescriptorNum++
dlog.SetFileDescriptor(os.NewFile(uintptr(3), "logFile"))
}
}
if !*flags.Child {
@ -368,18 +385,20 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
proxy.xTransport.tlsDisableSessionTickets = config.TLSDisableSessionTickets
proxy.xTransport.tlsCipherSuite = config.TLSCipherSuite
proxy.xTransport.mainProto = proxy.mainProto
if len(config.FallbackResolver) > 0 {
config.FallbackResolvers = []string{config.FallbackResolver}
proxy.xTransport.http3 = config.HTTP3
if len(config.BootstrapResolvers) == 0 && len(config.BootstrapResolversLegacy) > 0 {
dlog.Warnf("fallback_resolvers was renamed to bootstrap_resolvers - Please update your configuration")
config.BootstrapResolvers = config.BootstrapResolversLegacy
}
if len(config.FallbackResolvers) > 0 {
for _, resolver := range config.FallbackResolvers {
if len(config.BootstrapResolvers) > 0 {
for _, resolver := range config.BootstrapResolvers {
if err := isIPAndPort(resolver); err != nil {
return fmt.Errorf("Fallback resolver [%v]: %v", resolver, err)
return fmt.Errorf("Bootstrap resolver [%v]: %v", resolver, err)
}
}
proxy.xTransport.ignoreSystemDNS = config.IgnoreSystemDNS
}
proxy.xTransport.fallbackResolvers = config.FallbackResolvers
proxy.xTransport.bootstrapResolvers = config.BootstrapResolvers
proxy.xTransport.useIPv4 = config.SourceIPv4
proxy.xTransport.useIPv6 = config.SourceIPv6
proxy.xTransport.keepAlive = time.Duration(config.KeepAlive) * time.Second
@ -421,6 +440,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
if config.ForceTCP {
proxy.mainProto = "tcp"
}
proxy.certRefreshConcurrency = Max(1, config.CertRefreshConcurrency)
proxy.certRefreshDelay = time.Duration(Max(60, config.CertRefreshDelay)) * time.Minute
proxy.certRefreshDelayAfterFailure = time.Duration(10 * time.Second)
proxy.certIgnoreTimestamp = config.CertIgnoreTimestamp
@ -464,7 +484,6 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
proxy.localDoHPath = config.LocalDoH.Path
proxy.localDoHCertFile = config.LocalDoH.CertFile
proxy.localDoHCertKeyFile = config.LocalDoH.CertKeyFile
proxy.daemonize = config.Daemonize
proxy.pluginBlockIPv6 = config.BlockIPv6
proxy.pluginBlockUnqualified = config.BlockUnqualified
proxy.pluginBlockUndelegated = config.BlockUndelegated
@ -483,6 +502,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
proxy.cacheMaxTTL = config.CacheMaxTTL
proxy.rejectTTL = config.RejectTTL
proxy.cloakTTL = config.CloakTTL
proxy.cloakedPTR = config.CloakedPTR
proxy.queryMeta = config.QueryMeta
@ -558,9 +578,9 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
if config.AllowedName.Format != "tsv" && config.AllowedName.Format != "ltsv" {
return errors.New("Unsupported allowed_names log format")
}
proxy.whitelistNameFile = config.AllowedName.File
proxy.whitelistNameFormat = config.AllowedName.Format
proxy.whitelistNameLogFile = config.AllowedName.LogFile
proxy.allowNameFile = config.AllowedName.File
proxy.allowNameFormat = config.AllowedName.Format
proxy.allowNameLogFile = config.AllowedName.LogFile
if len(config.BlockIP.File) > 0 && len(config.BlockIPLegacy.File) > 0 {
return errors.New("Don't specify both [blocked_ips] and [ip_blacklist] sections - Update your config file")
@ -615,23 +635,38 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
proxy.skipAnonIncompatibleResolvers = config.AnonymizedDNS.SkipIncompatible
proxy.anonDirectCertFallback = config.AnonymizedDNS.DirectCertFallback
if len(config.TLSKeyLogFile) > 0 {
f, err := os.OpenFile(config.TLSKeyLogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
dlog.Fatalf("Unable to create key log file [%s]: [%s]", config.TLSKeyLogFile, err)
}
dlog.Warnf("TLS key log file [%s] enabled", config.TLSKeyLogFile)
proxy.xTransport.keyLogWriter = f
proxy.xTransport.rebuildTransport()
}
if config.DoHClientX509AuthLegacy.Creds != nil {
return errors.New("[tls_client_auth] has been renamed to [doh_client_x509_auth] - Update your config file")
}
configClientCreds := config.DoHClientX509Auth.Creds
creds := make(map[string]DOHClientCreds)
for _, configClientCred := range configClientCreds {
credFiles := DOHClientCreds{
dohClientCreds := config.DoHClientX509Auth.Creds
if len(dohClientCreds) > 0 {
dlog.Noticef("Enabling TLS authentication")
configClientCred := dohClientCreds[0]
if len(dohClientCreds) > 1 {
dlog.Fatal("Only one tls_client_auth entry is currently supported")
}
proxy.xTransport.tlsClientCreds = DOHClientCreds{
clientCert: configClientCred.ClientCert,
clientKey: configClientCred.ClientKey,
rootCA: configClientCred.RootCA,
}
creds[configClientCred.ServerName] = credFiles
proxy.xTransport.rebuildTransport()
}
proxy.dohCreds = &creds
// Backwards compatibility
config.BrokenImplementations.FragmentsBlocked = append(config.BrokenImplementations.FragmentsBlocked, config.BrokenImplementations.BrokenQueryPadding...)
config.BrokenImplementations.FragmentsBlocked = append(
config.BrokenImplementations.FragmentsBlocked,
config.BrokenImplementations.BrokenQueryPadding...)
proxy.serversBlockingFragments = config.BrokenImplementations.FragmentsBlocked
@ -648,6 +683,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
config.SourceIPv6 = true
config.SourceDNSCrypt = true
config.SourceDoH = true
config.SourceODoH = true
}
var requiredProps stamps.ServerInformalProperties
@ -667,6 +703,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
proxy.SourceIPv6 = config.SourceIPv6
proxy.SourceDNSCrypt = config.SourceDNSCrypt
proxy.SourceDoH = config.SourceDoH
proxy.SourceODoH = config.SourceODoH
netprobeTimeout := config.NetprobeTimeout
flag.Visit(func(flag *flag.Flag) {
@ -677,11 +714,10 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
netprobeAddress := DefaultNetprobeAddress
if len(config.NetprobeAddress) > 0 {
netprobeAddress = config.NetprobeAddress
} else if len(config.FallbackResolvers) > 0 {
netprobeAddress = config.FallbackResolvers[0]
} else if len(config.BootstrapResolvers) > 0 {
netprobeAddress = config.BootstrapResolvers[0]
}
proxy.showCerts = *flags.ShowCerts || len(os.Getenv("SHOW_CERTS")) > 0
if !*flags.Check && !*flags.ShowCerts && !*flags.List && !*flags.ListAll {
if !isCommandMode {
if err := NetProbe(proxy, netprobeAddress, netprobeTimeout); err != nil {
return err
}
@ -698,7 +734,9 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
// if 'userName' is set and we are the parent process drop privilege and exit
if len(proxy.userName) > 0 && !proxy.child {
proxy.dropPrivilege(proxy.userName, FileDescriptors)
return errors.New("Dropping privileges is not supporting on this operating system. Unset `user_name` in the configuration file")
return errors.New(
"Dropping privileges is not supporting on this operating system. Unset `user_name` in the configuration file",
)
}
if !config.OfflineMode {
if err := config.loadSources(proxy); err != nil {
@ -709,7 +747,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
}
}
if *flags.List || *flags.ListAll {
if err := config.printRegisteredServers(proxy, *flags.JSONOutput); err != nil {
if err := config.printRegisteredServers(proxy, *flags.JSONOutput, *flags.IncludeRelays); err != nil {
return err
}
os.Exit(0)
@ -718,8 +756,12 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
hasSpecificRoutes := false
for _, server := range proxy.registeredServers {
if via, ok := (*proxy.routes)[server.name]; ok {
if server.stamp.Proto != stamps.StampProtoTypeDNSCrypt {
dlog.Errorf("DNS anonymization is only supported with the DNSCrypt protocol - Connections to [%v] cannot be anonymized", server.name)
if server.stamp.Proto != stamps.StampProtoTypeDNSCrypt &&
server.stamp.Proto != stamps.StampProtoTypeODoHTarget {
dlog.Errorf(
"DNS anonymization is only supported with the DNSCrypt and ODoH protocols - Connections to [%v] cannot be anonymized",
server.name,
)
} else {
dlog.Noticef("Anonymized DNS: routing [%v] via %v", server.name, via)
}
@ -741,14 +783,54 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
return nil
}
func (config *Config) printRegisteredServers(proxy *Proxy, jsonOutput bool) error {
func (config *Config) printRegisteredServers(proxy *Proxy, jsonOutput bool, includeRelays bool) error {
var summary []ServerSummary
if includeRelays {
for _, registeredRelay := range proxy.registeredRelays {
addrStr, port := registeredRelay.stamp.ServerAddrStr, stamps.DefaultPort
var hostAddr string
hostAddr, port = ExtractHostAndPort(addrStr, port)
addrs := make([]string, 0)
if (registeredRelay.stamp.Proto == stamps.StampProtoTypeDoH || registeredRelay.stamp.Proto == stamps.StampProtoTypeODoHTarget) &&
len(registeredRelay.stamp.ProviderName) > 0 {
providerName := registeredRelay.stamp.ProviderName
var host string
host, port = ExtractHostAndPort(providerName, port)
addrs = append(addrs, host)
}
if len(addrStr) > 0 {
addrs = append(addrs, hostAddr)
}
nolog := true
nofilter := true
if registeredRelay.stamp.Proto == stamps.StampProtoTypeODoHRelay {
nolog = registeredRelay.stamp.Props&stamps.ServerInformalPropertyNoLog != 0
}
serverSummary := ServerSummary{
Name: registeredRelay.name,
Proto: registeredRelay.stamp.Proto.String(),
IPv6: strings.HasPrefix(addrStr, "["),
Ports: []int{port},
Addrs: addrs,
NoLog: nolog,
NoFilter: nofilter,
Description: registeredRelay.description,
Stamp: registeredRelay.stamp.String(),
}
if jsonOutput {
summary = append(summary, serverSummary)
} else {
fmt.Println(serverSummary.Name)
}
}
}
for _, registeredServer := range proxy.registeredServers {
addrStr, port := registeredServer.stamp.ServerAddrStr, stamps.DefaultPort
var hostAddr string
hostAddr, port = ExtractHostAndPort(addrStr, port)
addrs := make([]string, 0)
if registeredServer.stamp.Proto == stamps.StampProtoTypeDoH && len(registeredServer.stamp.ProviderName) > 0 {
if (registeredServer.stamp.Proto == stamps.StampProtoTypeDoH || registeredServer.stamp.Proto == stamps.StampProtoTypeODoHTarget) &&
len(registeredServer.stamp.ProviderName) > 0 {
providerName := registeredServer.stamp.ProviderName
var host string
host, port = ExtractHostAndPort(providerName, port)
@ -757,13 +839,14 @@ func (config *Config) printRegisteredServers(proxy *Proxy, jsonOutput bool) erro
if len(addrStr) > 0 {
addrs = append(addrs, hostAddr)
}
dnssec := registeredServer.stamp.Props&stamps.ServerInformalPropertyDNSSEC != 0
serverSummary := ServerSummary{
Name: registeredServer.name,
Proto: registeredServer.stamp.Proto.String(),
IPv6: strings.HasPrefix(addrStr, "["),
Ports: []int{port},
Addrs: addrs,
DNSSEC: registeredServer.stamp.Props&stamps.ServerInformalPropertyDNSSEC != 0,
DNSSEC: &dnssec,
NoLog: registeredServer.stamp.Props&stamps.ServerInformalPropertyNoLog != 0,
NoFilter: registeredServer.stamp.Props&stamps.ServerInformalPropertyNoFilter != 0,
Description: registeredServer.description,
@ -788,6 +871,9 @@ func (config *Config) printRegisteredServers(proxy *Proxy, jsonOutput bool) erro
func (config *Config) loadSources(proxy *Proxy) error {
for cfgSourceName, cfgSource_ := range config.SourcesConfig {
cfgSource := cfgSource_
rand.Shuffle(len(cfgSource.URLs), func(i, j int) {
cfgSource.URLs[i], cfgSource.URLs[j] = cfgSource.URLs[j], cfgSource.URLs[i]
})
if err := config.loadSource(proxy, cfgSourceName, &cfgSource); err != nil {
return err
}
@ -820,7 +906,9 @@ func (config *Config) loadSources(proxy *Proxy) error {
}
proxy.registeredServers = append(proxy.registeredServers, RegisteredServer{name: serverName, stamp: stamp})
}
proxy.updateRegisteredServers()
if err := proxy.updateRegisteredServers(); err != nil {
return err
}
rs1 := proxy.registeredServers
rs2 := proxy.serversInfo.registeredServers
rand.Shuffle(len(rs1), func(i, j int) {
@ -851,12 +939,20 @@ func (config *Config) loadSource(proxy *Proxy, cfgSourceName string, cfgSource *
}
if cfgSource.RefreshDelay <= 0 {
cfgSource.RefreshDelay = 72
} else if cfgSource.RefreshDelay > 168 {
cfgSource.RefreshDelay = 168
}
source, err := NewSource(cfgSourceName, proxy.xTransport, cfgSource.URLs, cfgSource.MinisignKeyStr, cfgSource.CacheFile, cfgSource.FormatStr, time.Duration(cfgSource.RefreshDelay)*time.Hour, cfgSource.Prefix)
cfgSource.RefreshDelay = Min(168, Max(24, cfgSource.RefreshDelay))
source, err := NewSource(
cfgSourceName,
proxy.xTransport,
cfgSource.URLs,
cfgSource.MinisignKeyStr,
cfgSource.CacheFile,
cfgSource.FormatStr,
time.Duration(cfgSource.RefreshDelay)*time.Hour,
cfgSource.Prefix,
)
if err != nil {
if len(source.in) <= 0 {
if len(source.bin) <= 0 {
dlog.Criticalf("Unable to retrieve source [%s]: [%s]", cfgSourceName, err)
return err
}
@ -882,7 +978,10 @@ func cdFileDir(fileName string) error {
func cdLocal() {
exeFileName, err := os.Executable()
if err != nil {
dlog.Warnf("Unable to determine the executable directory: [%s] -- You will need to specify absolute paths in the configuration file", err)
dlog.Warnf(
"Unable to determine the executable directory: [%s] -- You will need to specify absolute paths in the configuration file",
err,
)
} else if err := os.Chdir(filepath.Dir(exeFileName)); err != nil {
dlog.Warnf("Unable to change working directory to [%s]: %s", exeFileName, err)
}

View File

@ -5,7 +5,6 @@ import (
crypto_rand "crypto/rand"
"crypto/sha512"
"errors"
"math/rand"
"github.com/jedisct1/dlog"
"github.com/jedisct1/xsecretbox"
@ -45,7 +44,12 @@ func unpad(packet []byte) ([]byte, error) {
}
}
func ComputeSharedKey(cryptoConstruction CryptoConstruction, secretKey *[32]byte, serverPk *[32]byte, providerName *string) (sharedKey [32]byte) {
func ComputeSharedKey(
cryptoConstruction CryptoConstruction,
secretKey *[32]byte,
serverPk *[32]byte,
providerName *string,
) (sharedKey [32]byte) {
if cryptoConstruction == XChacha20Poly1305 {
var err error
sharedKey, err = xsecretbox.SharedKey(*secretKey, *serverPk)
@ -68,9 +72,15 @@ func ComputeSharedKey(cryptoConstruction CryptoConstruction, secretKey *[32]byte
return
}
func (proxy *Proxy) Encrypt(serverInfo *ServerInfo, packet []byte, proto string) (sharedKey *[32]byte, encrypted []byte, clientNonce []byte, err error) {
func (proxy *Proxy) Encrypt(
serverInfo *ServerInfo,
packet []byte,
proto string,
) (sharedKey *[32]byte, encrypted []byte, clientNonce []byte, err error) {
nonce, clientNonce := make([]byte, NonceSize), make([]byte, HalfNonceSize)
crypto_rand.Read(clientNonce)
if _, err := crypto_rand.Read(clientNonce); err != nil {
return nil, nil, nil, err
}
copy(nonce, clientNonce)
var publicKey *[PublicKeySize]byte
if proxy.ephemeralKeys {
@ -93,14 +103,15 @@ func (proxy *Proxy) Encrypt(serverInfo *ServerInfo, packet []byte, proto string)
minQuestionSize = Max(proxy.questionSizeEstimator.MinQuestionSize(), minQuestionSize)
} else {
var xpad [1]byte
rand.Read(xpad[:])
if _, err := crypto_rand.Read(xpad[:]); err != nil {
return nil, nil, nil, err
}
minQuestionSize += int(xpad[0])
}
paddedLength := Min(MaxDNSUDPPacketSize, (Max(minQuestionSize, QueryOverhead)+1+63) & ^63)
if proto == "udp" && serverInfo.knownBugs.fragmentsBlocked {
if serverInfo.knownBugs.fragmentsBlocked && proto == "udp" {
paddedLength = MaxDNSUDPSafePacketSize
}
if serverInfo.Relay != nil && proto == "tcp" {
} else if serverInfo.Relay != nil && proto == "tcp" {
paddedLength = MaxDNSPacketSize
}
if QueryOverhead+len(packet)+1 > paddedLength {
@ -120,7 +131,12 @@ func (proxy *Proxy) Encrypt(serverInfo *ServerInfo, packet []byte, proto string)
return
}
func (proxy *Proxy) Decrypt(serverInfo *ServerInfo, sharedKey *[32]byte, encrypted []byte, nonce []byte) ([]byte, error) {
func (proxy *Proxy) Decrypt(
serverInfo *ServerInfo,
sharedKey *[32]byte,
encrypted []byte,
nonce []byte,
) ([]byte, error) {
serverMagicLen := len(ServerMagic)
responseHeaderLen := serverMagicLen + NonceSize
if len(encrypted) < responseHeaderLen+TagSize+int(MinDNSPacketSize) ||

View File

@ -20,12 +20,22 @@ type CertInfo struct {
ForwardSecurity bool
}
func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk ed25519.PublicKey, serverAddress string, providerName string, isNew bool, relay *DNSCryptRelay, knownBugs ServerBugs) (CertInfo, int, bool, error) {
func FetchCurrentDNSCryptCert(
proxy *Proxy,
serverName *string,
proto string,
pk ed25519.PublicKey,
serverAddress string,
providerName string,
isNew bool,
relay *DNSCryptRelay,
knownBugs ServerBugs,
) (CertInfo, int, bool, error) {
if len(pk) != ed25519.PublicKeySize {
return CertInfo{}, 0, false, errors.New("Invalid public key length")
}
if !strings.HasSuffix(providerName, ".") {
providerName = providerName + "."
providerName += "."
}
if serverName == nil {
serverName = &providerName
@ -34,7 +44,11 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk
query.SetQuestion(providerName, dns.TypeTXT)
if !strings.HasPrefix(providerName, "2.dnscrypt-cert.") {
if relay != nil && !proxy.anonDirectCertFallback {
dlog.Warnf("[%v] uses a non-standard provider name, enable direct cert fallback to use with a relay ('%v' doesn't start with '2.dnscrypt-cert.')", *serverName, providerName)
dlog.Warnf(
"[%v] uses a non-standard provider name, enable direct cert fallback to use with a relay ('%v' doesn't start with '2.dnscrypt-cert.')",
*serverName,
providerName,
)
} else {
dlog.Warnf("[%v] uses a non-standard provider name ('%v' doesn't start with '2.dnscrypt-cert.')", *serverName, providerName)
relay = nil
@ -44,7 +58,15 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk
if knownBugs.fragmentsBlocked {
tryFragmentsSupport = false
}
in, rtt, fragmentsBlocked, err := DNSExchange(proxy, proto, &query, serverAddress, relay, serverName, tryFragmentsSupport)
in, rtt, fragmentsBlocked, err := DNSExchange(
proxy,
proto,
&query,
serverAddress,
relay,
serverName,
tryFragmentsSupport,
)
if err != nil {
dlog.Noticef("[%s] TIMEOUT", *serverName)
return CertInfo{}, 0, fragmentsBlocked, err
@ -95,10 +117,17 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk
}
ttl := tsEnd - tsBegin
if ttl > 86400*7 {
dlog.Infof("[%v] the key validity period for this server is excessively long (%d days), significantly reducing reliability and forward security.", *serverName, ttl/86400)
dlog.Infof(
"[%v] the key validity period for this server is excessively long (%d days), significantly reducing reliability and forward security.",
*serverName,
ttl/86400,
)
daysLeft := (tsEnd - now) / 86400
if daysLeft < 1 {
dlog.Criticalf("[%v] certificate will expire today -- Switch to a different resolver as soon as possible", *serverName)
dlog.Criticalf(
"[%v] certificate will expire today -- Switch to a different resolver as soon as possible",
*serverName,
)
} else if daysLeft <= 7 {
dlog.Warnf("[%v] certificate is about to expire -- if you don't manage this server, tell the server operator about it", *serverName)
} else if daysLeft <= 30 {
@ -112,7 +141,13 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk
}
if !proxy.certIgnoreTimestamp {
if now > tsEnd || now < tsBegin {
dlog.Debugf("[%v] Certificate not valid at the current date (now: %v is not in [%v..%v])", *serverName, now, tsBegin, tsEnd)
dlog.Debugf(
"[%v] Certificate not valid at the current date (now: %v is not in [%v..%v])",
*serverName,
now,
tsBegin,
tsEnd,
)
continue
}
}
@ -148,7 +183,7 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk
certCountStr = " - additional certificate"
}
if certInfo.CryptoConstruction == UndefinedConstruction {
return certInfo, 0, fragmentsBlocked, errors.New("No useable certificate found")
return certInfo, 0, fragmentsBlocked, errors.New("No usable certificate found")
}
return certInfo, int(rtt.Nanoseconds() / 1000000), fragmentsBlocked, nil
}

View File

@ -40,6 +40,11 @@ func TruncatedResponse(packet []byte) ([]byte, error) {
func RefusedResponseFromMessage(srcMsg *dns.Msg, refusedCode bool, ipv4 net.IP, ipv6 net.IP, ttl uint32) *dns.Msg {
dstMsg := EmptyResponseFromMessage(srcMsg)
ede := new(dns.EDNS0_EDE)
if edns0 := dstMsg.IsEdns0(); edns0 != nil {
edns0.Option = append(edns0.Option, ede)
}
ede.InfoCode = dns.ExtendedErrorCodeFiltered
if refusedCode {
dstMsg.Rcode = dns.RcodeRefused
} else {
@ -58,6 +63,7 @@ func RefusedResponseFromMessage(srcMsg *dns.Msg, refusedCode bool, ipv4 net.IP,
if rr.A != nil {
dstMsg.Answer = []dns.RR{rr}
sendHInfoResponse = false
ede.InfoCode = dns.ExtendedErrorCodeForgedAnswer
}
} else if ipv6 != nil && question.Qtype == dns.TypeAAAA {
rr := new(dns.AAAA)
@ -66,18 +72,24 @@ func RefusedResponseFromMessage(srcMsg *dns.Msg, refusedCode bool, ipv4 net.IP,
if rr.AAAA != nil {
dstMsg.Answer = []dns.RR{rr}
sendHInfoResponse = false
ede.InfoCode = dns.ExtendedErrorCodeForgedAnswer
}
}
if sendHInfoResponse {
hinfo := new(dns.HINFO)
hinfo.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypeHINFO,
Class: dns.ClassINET, Ttl: 1}
hinfo.Hdr = dns.RR_Header{
Name: question.Name, Rrtype: dns.TypeHINFO,
Class: dns.ClassINET, Ttl: ttl,
}
hinfo.Cpu = "This query has been locally blocked"
hinfo.Os = "by dnscrypt-proxy"
dstMsg.Answer = []dns.RR{hinfo}
} else {
ede.ExtraText = "This query has been locally blocked by dnscrypt-proxy"
}
}
return dstMsg
}
@ -135,7 +147,8 @@ func NormalizeQName(str string) (string, error) {
}
func getMinTTL(msg *dns.Msg, minTTL uint32, maxTTL uint32, cacheNegMinTTL uint32, cacheNegMaxTTL uint32) time.Duration {
if (msg.Rcode != dns.RcodeSuccess && msg.Rcode != dns.RcodeNameError) || (len(msg.Answer) <= 0 && len(msg.Ns) <= 0) {
if (msg.Rcode != dns.RcodeSuccess && msg.Rcode != dns.RcodeNameError) ||
(len(msg.Answer) <= 0 && len(msg.Ns) <= 0) {
return time.Duration(cacheNegMinTTL) * time.Second
}
var ttl uint32
@ -196,6 +209,9 @@ func updateTTL(msg *dns.Msg, expiration time.Time) {
ttl := uint32(0)
if until > 0 {
ttl = uint32(until / time.Second)
if until-time.Duration(ttl)*time.Second >= time.Second/2 {
ttl += 1
}
}
for _, rr := range msg.Answer {
rr.Header().Ttl = ttl
@ -258,8 +274,6 @@ func removeEDNS0Options(msg *dns.Msg) bool {
return true
}
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
func dddToByte(s []byte) byte {
return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
}
@ -301,44 +315,53 @@ type DNSExchangeResponse struct {
err error
}
func DNSExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress string, relay *DNSCryptRelay, serverName *string, tryFragmentsSupport bool) (*dns.Msg, time.Duration, bool, error) {
func DNSExchange(
proxy *Proxy,
proto string,
query *dns.Msg,
serverAddress string,
relay *DNSCryptRelay,
serverName *string,
tryFragmentsSupport bool,
) (*dns.Msg, time.Duration, bool, error) {
for {
cancelChannel := make(chan struct{})
channel := make(chan DNSExchangeResponse)
maxTries := 3
channel := make(chan DNSExchangeResponse, 2*maxTries)
var err error
options := 0
for tries := 0; tries < 3; tries++ {
for tries := 0; tries < maxTries; tries++ {
if tryFragmentsSupport {
queryCopy := query.Copy()
queryCopy.Id += uint16(options)
go func(query *dns.Msg, delay time.Duration) {
option := _dnsExchange(proxy, proto, query, serverAddress, relay, 1500)
time.Sleep(delay)
option := DNSExchangeResponse{err: errors.New("Canceled")}
select {
case <-cancelChannel:
default:
option = _dnsExchange(proxy, proto, query, serverAddress, relay, 1500)
}
option.fragmentsBlocked = false
option.priority = 0
channel <- option
time.Sleep(delay)
select {
case <-cancelChannel:
return
default:
}
}(queryCopy, time.Duration(200*tries)*time.Millisecond)
options++
}
queryCopy := query.Copy()
queryCopy.Id += uint16(options)
go func(query *dns.Msg, delay time.Duration) {
option := _dnsExchange(proxy, proto, query, serverAddress, relay, 480)
time.Sleep(delay)
option := DNSExchangeResponse{err: errors.New("Canceled")}
select {
case <-cancelChannel:
default:
option = _dnsExchange(proxy, proto, query, serverAddress, relay, 480)
}
option.fragmentsBlocked = true
option.priority = 1
channel <- option
time.Sleep(delay)
select {
case <-cancelChannel:
return
default:
}
}(queryCopy, time.Duration(250*tries)*time.Millisecond)
options++
}
@ -372,12 +395,23 @@ func DNSExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress strin
}
return nil, 0, false, err
}
dlog.Infof("Unable to get the public key for [%v] via relay [%v], retrying over a direct connection", *serverName, relay.RelayUDPAddr.IP)
dlog.Infof(
"Unable to get the public key for [%v] via relay [%v], retrying over a direct connection",
*serverName,
relay.RelayUDPAddr.IP,
)
relay = nil
}
}
func _dnsExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress string, relay *DNSCryptRelay, paddedLen int) DNSExchangeResponse {
func _dnsExchange(
proxy *Proxy,
proto string,
query *dns.Msg,
serverAddress string,
relay *DNSCryptRelay,
paddedLen int,
) DNSExchangeResponse {
var packet []byte
var rtt time.Duration

View File

@ -6,10 +6,6 @@ import (
"github.com/VividCortex/ewma"
)
const (
SizeEstimatorEwmaDecay = 100.0
)
type QuestionSizeEstimator struct {
sync.RWMutex
minQuestionSize int
@ -17,7 +13,10 @@ type QuestionSizeEstimator struct {
}
func NewQuestionSizeEstimator() QuestionSizeEstimator {
return QuestionSizeEstimator{minQuestionSize: InitialMinQuestionSize, ewma: ewma.NewMovingAverage(SizeEstimatorEwmaDecay)}
return QuestionSizeEstimator{
minQuestionSize: InitialMinQuestionSize,
ewma: &ewma.SimpleEWMA{},
}
}
func (questionSizeEstimator *QuestionSizeEstimator) MinQuestionSize() int {

View File

@ -9,6 +9,7 @@
## - https://github.com/tg12/bad_packets_blocklist
## - https://isc.sans.edu/block.txt
## - https://block.energized.pro/extensions/ips/formats/list.txt
## - https://www.iblocklist.com/lists
163.5.1.4
94.46.118.*

View File

@ -31,6 +31,13 @@ eth0.me
*.workgroup
## Prevent usage of Apple private relay, that bypasses DNS
# mask.apple-dns.net
# mask.icloud.com
# mask-api.icloud.com
# doh.dns.apple.com
## Time-based rules

View File

@ -7,7 +7,7 @@
## going through a captive portal.
##
## This is a list of hard-coded IP addresses that will be returned when queries
## for these names are received, even before the operating system an interface
## for these names are received, even before the operating system reports an interface
## as usable for reaching the Internet.
##
## Note that IPv6 addresses don't need to be specified within brackets,
@ -16,7 +16,12 @@
captive.apple.com 17.253.109.201, 17.253.113.202
connectivitycheck.gstatic.com 64.233.162.94, 64.233.164.94, 64.233.165.94, 64.233.177.94, 64.233.185.94, 74.125.132.94, 74.125.136.94, 74.125.20.94, 74.125.21.94, 74.125.28.94
connectivitycheck.android.com 64.233.162.100, 64.233.162.101, 64.233.162.102, 64.233.162.113, 64.233.162.138, 64.233.162.139
www.msftncsi.com 95.100.252.49, 95.100.252.8, 2.19.98.8, 2.19.98.59, 88.221.113.88, 88.221.113.43, 88.221.113.49, 88.221.113.75
www.msftncsi.com 2.16.106.89, 2.16.106.91, 23.0.175.137, 23.0.175.146, 23.192.47.155, 23.192.47.203, 23.199.63.160, 23.199.63.184, 23.199.63.208, 23.204.146.160, 23.204.146.163, 23.46.238.243, 23.46.239.24, 23.48.39.16, 23.48.39.48, 23.55.38.139, 23.55.38.146, 23.59.190.185, 23.59.190.195
dns.msftncsi.com 131.107.255.255, fd3e:4f5a:5b81::1
www.msftconnecttest.com 13.107.4.52
ipv6.msftconnecttest.com 2a01:111:2003::52
ipv4only.arpa 192.0.0.170, 192.0.0.171
## Adding IP addresses of NTP servers is also a good idea
time.google.com 216.239.35.0, 2001:4860:4806::

View File

@ -35,3 +35,10 @@ localhost ::1
# ads.* 192.168.100.1
# ads.* 192.168.100.2
# ads.* ::1
# PTR records can be created by setting cloak_ptr in the main configuration file
# Entries with wild cards will not have PTR records created, but multiple
# names for the same IP are supported
# example.com 192.168.100.1
# my.example.com 192.168.100.1

View File

@ -35,6 +35,9 @@
## List of local addresses and ports to listen to. Can be IPv4 and/or IPv6.
## Example with both IPv4 and IPv6:
## listen_addresses = ['127.0.0.1:53', '[::1]:53']
##
## To listen to all IPv4 addresses, use `listen_addresses = ['0.0.0.0:53']`
## To listen to all IPv4+IPv6 addresses, use `listen_addresses = ['[::]:53']`
listen_addresses = ['127.0.0.1:53']
@ -52,7 +55,7 @@ max_clients = 250
# user_name = 'nobody'
## Require servers (from static + remote sources) to satisfy specific properties
## Require servers (from remote sources) to satisfy specific properties
# Use servers reachable over IPv4
ipv4_servers = true
@ -66,6 +69,9 @@ dnscrypt_servers = true
# Use servers implementing the DNS-over-HTTPS protocol
doh_servers = true
# Use servers implementing the Oblivious DoH protocol
odoh_servers = false
## Require servers defined by remote sources to satisfy specific properties
@ -91,6 +97,13 @@ disabled_server_names = []
force_tcp = false
## Enable *experimental* support for HTTP/3 (DoH3, HTTP over QUIC)
## Note that, like DNSCrypt but unlike other HTTP versions, this uses
## UDP and (usually) port 443 instead of TCP.
http3 = false
## SOCKS proxy
## Uncomment the following line to route all TCP connections to a local Tor node
## Tor doesn't support UDP, so set `force_tcp` to `true` as well.
@ -112,7 +125,7 @@ force_tcp = false
timeout = 5000
## Keepalive for HTTP (HTTPS, HTTP/2) queries, in seconds
## Keepalive for HTTP (HTTPS, HTTP/2, HTTP/3) queries, in seconds
keepalive = 30
@ -122,7 +135,7 @@ keepalive = 30
## Multiple networks can be listed; they will be randomly chosen.
## These networks don't have to match your actual networks.
# edns_client_subnet = ["0.0.0.0/0", "2001:db8::/32"]
# edns_client_subnet = ['0.0.0.0/0', '2001:db8::/32']
## Response for blocked queries. Options are `refused`, `hinfo` (default) or
@ -170,11 +183,24 @@ keepalive = 30
# use_syslog = true
## The maximum concurrency to reload certificates from the resolvers.
## Default is 10.
# cert_refresh_concurrency = 10
## Delay, in minutes, after which certificates are reloaded
cert_refresh_delay = 240
## Initially don't check DNSCrypt server certificates for expiration, and
## only start checking them after a first successful connection to a resolver.
## This can be useful on routers with no battery-backed clock.
# cert_ignore_timestamp = false
## DNSCrypt: Create a new, unique key for every single DNS query
## This may improve privacy but can also have a significant impact on CPU usage
## Only enable if you don't have a lot of network load
@ -187,48 +213,74 @@ cert_refresh_delay = 240
# tls_disable_session_tickets = false
## DoH: Use a specific cipher suite instead of the server preference
## DoH: Use TLS 1.2 and specific cipher suite instead of the server preference
## 49199 = TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
## 49195 = TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
## 52392 = TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
## 52393 = TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
## 4865 = TLS_AES_128_GCM_SHA256
## 4867 = TLS_CHACHA20_POLY1305_SHA256
##
## On non-Intel CPUs such as MIPS routers and ARM systems (Android, Raspberry Pi...),
## the following suite improves performance.
## This may also help on Intel CPUs running 32-bit operating systems.
##
## Keep tls_cipher_suite empty if you have issues fetching sources or
## connecting to some DoH servers. Google and Cloudflare are fine with it.
## connecting to some DoH servers.
# tls_cipher_suite = [52392, 49199]
## Fallback resolvers
## Log TLS key material to a file, for debugging purposes only.
## This file will contain the TLS master key, which can be used to decrypt
## all TLS traffic to/from DoH servers.
## Never ever enable except for debugging purposes with a tool such as mitmproxy.
# tls_key_log_file = '/tmp/keylog.txt'
## Bootstrap resolvers
##
## These are normal, non-encrypted DNS resolvers, that will be only used
## for one-shot queries when retrieving the initial resolvers list, and
## only if the system DNS configuration doesn't work.
## for one-shot queries when retrieving the initial resolvers list and if
## the system DNS configuration doesn't work.
##
## No user application queries will ever be leaked through these resolvers,
## and they will not be used after IP addresses of resolvers URLs have been found.
## They will never be used if lists have already been cached, and if stamps
## don't include host names without IP addresses.
## No user queries will ever be leaked through these resolvers, and they will
## not be used after IP addresses of DoH resolvers have been found (if you are
## using DoH).
##
## They will never be used if lists have already been cached, and if the stamps
## of the configured servers already include IP addresses (which is the case for
## most of DoH servers, and for all DNSCrypt servers and relays).
##
## They will not be used if the configured system DNS works, or after the
## proxy already has at least one usable secure resolver.
##
## They will not be used if the configured system DNS works.
## Resolvers supporting DNSSEC are recommended, and, if you are using
## DoH, fallback resolvers should ideally be operated by a different entity than
## the DoH servers you will be using, especially if you have IPv6 enabled.
## DoH, bootstrap resolvers should ideally be operated by a different entity
## than the DoH servers you will be using, especially if you have IPv6 enabled.
##
## People in China may need to use 114.114.114.114:53 here.
## Other popular options include 8.8.8.8 and 1.1.1.1.
## People in China may want to use 114.114.114.114:53 here.
## Other popular options include 8.8.8.8, 9.9.9.9 and 1.1.1.1.
##
## If more than one resolver is specified, they will be tried in sequence.
##
## TL;DR: put valid standard resolver addresses here. Your actual queries will
## not be sent there. If you're using DNSCrypt or Anonymized DNS and your
## lists are up to date, these resolvers will not even be used.
fallback_resolvers = ['9.9.9.9:53', '8.8.8.8:53']
bootstrap_resolvers = ['9.9.9.11:53', '8.8.8.8:53']
## Always use the fallback resolver before the system DNS settings.
## When internal DNS resolution is required, for example to retrieve
## the resolvers list:
##
## - queries will be sent to dnscrypt-proxy itself, if it is already
## running with active servers (*)
## - or else, queries will be sent to fallback servers
## - finally, if `ignore_system_dns` is `false`, queries will be sent
## to the system DNS
##
## (*) this is incompatible with systemd sockets.
## `listen_addrs` must not be empty.
ignore_system_dns = true
@ -302,6 +354,7 @@ block_ipv6 = false
## Immediately respond to A and AAAA queries for host names without a domain name
## This also prevents "dotless domain names" from being resolved upstream.
block_unqualified = true
@ -315,7 +368,7 @@ block_undelegated = true
## TTL for synthetic responses sent when a request has been blocked (due to
## IPv6 or blocklists).
reject_ttl = 600
reject_ttl = 10
@ -336,6 +389,8 @@ reject_ttl = 600
## Cloaking returns a predefined address for a specific name.
## In addition to acting as a HOSTS file, it can also return the IP address
## of a different name. It will also do CNAME flattening.
## If 'cloak_ptr' is set, then PTR (reverse lookups) are enabled
## for cloaking rules that do not contain wild cards.
##
## See the `example-cloaking-rules.txt` file for an example
@ -344,6 +399,7 @@ reject_ttl = 600
## TTL used when serving entries in cloaking-rules.txt
# cloak_ttl = 600
# cloak_ptr = false
@ -420,6 +476,8 @@ cache_neg_max_ttl = 600
## Certificate file and key - Note that the certificate has to be trusted.
## Can be generated using the following command:
## openssl req -x509 -nodes -newkey rsa:2048 -days 5000 -sha256 -keyout localhost.pem -out localhost.pem
## See the documentation (wiki) for more information.
# cert_file = 'localhost.pem'
@ -435,20 +493,20 @@ cache_neg_max_ttl = 600
[query_log]
## Path to the query log file (absolute, or relative to the same directory as the config file)
## Can be set to /dev/stdout in order to log to the standard output.
## Path to the query log file (absolute, or relative to the same directory as the config file)
## Can be set to /dev/stdout in order to log to the standard output.
# file = 'query.log'
# file = 'query.log'
## Query log format (currently supported: tsv and ltsv)
## Query log format (currently supported: tsv and ltsv)
format = 'tsv'
format = 'tsv'
## Do not log these query types, to reduce verbosity. Keep empty to log everything.
## Do not log these query types, to reduce verbosity. Keep empty to log everything.
# ignored_qtypes = ['DNSKEY', 'NS']
# ignored_qtypes = ['DNSKEY', 'NS']
@ -462,19 +520,19 @@ cache_neg_max_ttl = 600
[nx_log]
## Path to the query log file (absolute, or relative to the same directory as the config file)
## Path to the query log file (absolute, or relative to the same directory as the config file)
# file = 'nx.log'
# file = 'nx.log'
## Query log format (currently supported: tsv and ltsv)
## Query log format (currently supported: tsv and ltsv)
format = 'tsv'
format = 'tsv'
######################################################
# Pattern-based blocking (blocklists) #
# Pattern-based blocking (blocklists) #
######################################################
## Blocklists are made of one pattern per line. Example of valid patterns:
@ -492,19 +550,19 @@ cache_neg_max_ttl = 600
[blocked_names]
## Path to the file of blocking rules (absolute, or relative to the same directory as the config file)
## Path to the file of blocking rules (absolute, or relative to the same directory as the config file)
# blocked_names_file = 'blocked-names.txt'
# blocked_names_file = 'blocked-names.txt'
## Optional path to a file logging blocked queries
## Optional path to a file logging blocked queries
# log_file = 'blocked-names.log'
# log_file = 'blocked-names.log'
## Optional log format: tsv or ltsv (default: tsv)
## Optional log format: tsv or ltsv (default: tsv)
# log_format = 'tsv'
# log_format = 'tsv'
@ -520,24 +578,24 @@ cache_neg_max_ttl = 600
[blocked_ips]
## Path to the file of blocking rules (absolute, or relative to the same directory as the config file)
## Path to the file of blocking rules (absolute, or relative to the same directory as the config file)
# blocked_ips_file = 'blocked-ips.txt'
# blocked_ips_file = 'blocked-ips.txt'
## Optional path to a file logging blocked queries
## Optional path to a file logging blocked queries
# log_file = 'blocked-ips.log'
# log_file = 'blocked-ips.log'
## Optional log format: tsv or ltsv (default: tsv)
## Optional log format: tsv or ltsv (default: tsv)
# log_format = 'tsv'
# log_format = 'tsv'
######################################################
# Pattern-based allow lists (blocklists bypass) #
# Pattern-based allow lists (blocklists bypass) #
######################################################
## Allowlists support the same patterns as blocklists
@ -548,19 +606,19 @@ cache_neg_max_ttl = 600
[allowed_names]
## Path to the file of allow list rules (absolute, or relative to the same directory as the config file)
## Path to the file of allow list rules (absolute, or relative to the same directory as the config file)
# allowed_names_file = 'allowed-names.txt'
# allowed_names_file = 'allowed-names.txt'
## Optional path to a file logging allowed queries
## Optional path to a file logging allowed queries
# log_file = 'allowed-names.log'
# log_file = 'allowed-names.log'
## Optional log format: tsv or ltsv (default: tsv)
## Optional log format: tsv or ltsv (default: tsv)
# log_format = 'tsv'
# log_format = 'tsv'
@ -569,25 +627,25 @@ cache_neg_max_ttl = 600
#########################################################
## Allowed IP lists support the same patterns as IP blocklists
## If an IP response matches an allow ip entry, the corresponding session
## If an IP response matches an allowed entry, the corresponding session
## will bypass IP filters.
##
## Time-based rules are also supported to make some websites only accessible at specific times of the day.
[allowed_ips]
## Path to the file of allowed ip rules (absolute, or relative to the same directory as the config file)
## Path to the file of allowed ip rules (absolute, or relative to the same directory as the config file)
# allowed_ips_file = 'allowed-ips.txt'
# allowed_ips_file = 'allowed-ips.txt'
## Optional path to a file logging allowed queries
## Optional path to a file logging allowed queries
# log_file = 'allowed-ips.log'
# log_file = 'allowed-ips.log'
## Optional log format: tsv or ltsv (default: tsv)
## Optional log format: tsv or ltsv (default: tsv)
# log_format = 'tsv'
# log_format = 'tsv'
@ -608,21 +666,21 @@ cache_neg_max_ttl = 600
[schedules]
# [schedules.'time-to-sleep']
# mon = [{after='21:00', before='7:00'}]
# tue = [{after='21:00', before='7:00'}]
# wed = [{after='21:00', before='7:00'}]
# thu = [{after='21:00', before='7:00'}]
# fri = [{after='23:00', before='7:00'}]
# sat = [{after='23:00', before='7:00'}]
# sun = [{after='21:00', before='7:00'}]
# [schedules.time-to-sleep]
# mon = [{after='21:00', before='7:00'}]
# tue = [{after='21:00', before='7:00'}]
# wed = [{after='21:00', before='7:00'}]
# thu = [{after='21:00', before='7:00'}]
# fri = [{after='23:00', before='7:00'}]
# sat = [{after='23:00', before='7:00'}]
# sun = [{after='21:00', before='7:00'}]
# [schedules.'work']
# mon = [{after='9:00', before='18:00'}]
# tue = [{after='9:00', before='18:00'}]
# wed = [{after='9:00', before='18:00'}]
# thu = [{after='9:00', before='18:00'}]
# fri = [{after='9:00', before='17:00'}]
# [schedules.work]
# mon = [{after='9:00', before='18:00'}]
# tue = [{after='9:00', before='18:00'}]
# wed = [{after='9:00', before='18:00'}]
# thu = [{after='9:00', before='18:00'}]
# fri = [{after='9:00', before='17:00'}]
@ -644,46 +702,69 @@ cache_neg_max_ttl = 600
## If the `urls` property is missing, cache files and valid signatures
## must already be present. This doesn't prevent these cache files from
## expiring after `refresh_delay` hours.
## Cache freshness is checked every 24 hours, so values for 'refresh_delay'
## of less than 24 hours will have no effect.
## A maximum delay of 168 hours (1 week) is imposed to ensure cache freshness.
## `refreshed_delay` must be in the [24..168] interval.
## The minimum delay of 24 hours (1 day) avoids unnecessary requests to servers.
## The maximum delay of 168 hours (1 week) ensures cache freshness.
[sources]
## An example of a remote source from https://github.com/DNSCrypt/dnscrypt-resolvers
### An example of a remote source from https://github.com/DNSCrypt/dnscrypt-resolvers
[sources.'public-resolvers']
urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/public-resolvers.md', 'https://download.dnscrypt.net/resolvers-list/v3/public-resolvers.md']
cache_file = 'public-resolvers.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 72
prefix = ''
[sources.public-resolvers]
urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md']
cache_file = 'public-resolvers.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 72
prefix = ''
## Anonymized DNS relays
### Anonymized DNS relays
[sources.'relays']
urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/relays.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/relays.md', 'https://download.dnscrypt.net/resolvers-list/v3/relays.md']
cache_file = 'relays.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 72
prefix = ''
[sources.relays]
urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/relays.md']
cache_file = 'relays.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 72
prefix = ''
## Quad9 over DNSCrypt - https://quad9.net/
### ODoH (Oblivious DoH) servers and relays
# [sources.odoh-servers]
# urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-servers.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-servers.md']
# cache_file = 'odoh-servers.md'
# minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
# refresh_delay = 24
# prefix = ''
# [sources.odoh-relays]
# urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-relays.md']
# cache_file = 'odoh-relays.md'
# minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
# refresh_delay = 24
# prefix = ''
### Quad9
# [sources.quad9-resolvers]
# urls = ['https://www.quad9.net/quad9-resolvers.md']
# minisign_key = 'RWQBphd2+f6eiAqBsvDZEBXBGHQBJfeG6G+wJPPKxCZMoEQYpmoysKUN'
# cache_file = 'quad9-resolvers.md'
# prefix = 'quad9-'
# urls = ['https://www.quad9.net/quad9-resolvers.md']
# minisign_key = 'RWQBphd2+f6eiAqBsvDZEBXBGHQBJfeG6G+wJPPKxCZMoEQYpmoysKUN'
# cache_file = 'quad9-resolvers.md'
# prefix = 'quad9-'
## Another example source, with resolvers censoring some websites not appropriate for children
## This is a subset of the `public-resolvers` list, so enabling both is useless
### Another example source, with resolvers censoring some websites not appropriate for children
### This is a subset of the `public-resolvers` list, so enabling both is useless.
# [sources.'parental-control']
# urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/parental-control.md', 'https://download.dnscrypt.info/resolvers-list/v3/parental-control.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/parental-control.md', 'https://download.dnscrypt.net/resolvers-list/v3/parental-control.md']
# cache_file = 'parental-control.md'
# minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
# [sources.parental-control]
# urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/parental-control.md', 'https://download.dnscrypt.info/resolvers-list/v3/parental-control.md']
# cache_file = 'parental-control.md'
# minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
### dnscry.pt servers - See https://www.dnscry.pt
# [sources.dnscry-pt-resolvers]
# urls = ["https://www.dnscry.pt/resolvers.md"]
# minisign_key = "RWQM31Nwkqh01x88SvrBL8djp1NH56Rb4mKLHz16K7qsXgEomnDv6ziQ"
# cache_file = "dnscry.pt-resolvers.md"
# refresh_delay = 72
# prefix = "dnscry.pt-"
#########################################
@ -692,16 +773,16 @@ cache_neg_max_ttl = 600
[broken_implementations]
# Cisco servers currently cannot handle queries larger than 1472 bytes, and don't
# truncate reponses larger than questions as expected by the DNSCrypt protocol.
# This prevents large responses from being received over UDP and over relays.
#
# Older versions of the `dnsdist` server software had a bug with queries larger
# than 1500 bytes. This is fixed since `dnsdist` version 1.5.0, but
# some server may still run an outdated version.
#
# The list below enables workarounds to make non-relayed usage more reliable
# until the servers are fixed.
## Cisco servers currently cannot handle queries larger than 1472 bytes, and don't
## truncate responses larger than questions as expected by the DNSCrypt protocol.
## This prevents large responses from being received over UDP and over relays.
##
## Older versions of the `dnsdist` server software had a bug with queries larger
## than 1500 bytes. This is fixed since `dnsdist` version 1.5.0, but
## some server may still run an outdated version.
##
## The list below enables workarounds to make non-relayed usage more reliable
## until the servers are fixed.
fragments_blocked = ['cisco', 'cisco-ipv6', 'cisco-familyshield', 'cisco-familyshield-ipv6', 'cleanbrowsing-adult', 'cleanbrowsing-adult-ipv6', 'cleanbrowsing-family', 'cleanbrowsing-family-ipv6', 'cleanbrowsing-security', 'cleanbrowsing-security-ipv6']
@ -711,17 +792,16 @@ fragments_blocked = ['cisco', 'cisco-ipv6', 'cisco-familyshield', 'cisco-familys
# Certificate-based client authentication for DoH #
#################################################################
# Use a X509 certificate to authenticate yourself when connecting to DoH servers.
# This is only useful if you are operating your own, private DoH server(s).
# 'creds' maps servers to certificates, and supports multiple entries.
# If you are not using the standard root CA, an optional "root_ca"
# property set to the path to a root CRT file can be added to a server entry.
## Use a X509 certificate to authenticate yourself when connecting to DoH servers.
## This is only useful if you are operating your own, private DoH server(s).
## 'creds' maps servers to certificates, and supports multiple entries.
## If you are not using the standard root CA, an optional "root_ca"
## property set to the path to a root CRT file can be added to a server entry.
[doh_client_x509_auth]
#
# creds = [
# { server_name='myserver', client_cert='client.crt', client_key='client.key' }
# { server_name='*', client_cert='client.crt', client_key='client.key' }
# ]
@ -767,14 +847,14 @@ fragments_blocked = ['cisco', 'cisco-ipv6', 'cisco-familyshield', 'cisco-familys
# ]
# Skip resolvers incompatible with anonymization instead of using them directly
## Skip resolvers incompatible with anonymization instead of using them directly
skip_incompatible = false
# If public server certificates for a non-conformant server cannot be
# retrieved via a relay, try getting them directly. Actual queries
# will then always go through relays.
## If public server certificates for a non-conformant server cannot be
## retrieved via a relay, try getting them directly. Actual queries
## will then always go through relays.
# direct_cert_fallback = false
@ -802,13 +882,15 @@ skip_incompatible = false
[dns64]
## (Option 1) Static prefix(es) as Pref64::/n CIDRs.
## Static prefix(es) as Pref64::/n CIDRs
# prefix = ['64:ff9b::/96']
## (Option 2) DNS64-enabled resolver(s) to discover Pref64::/n CIDRs.
## DNS64-enabled resolver(s) to discover Pref64::/n CIDRs
## These resolvers are used to query for Well-Known IPv4-only Name (WKN) "ipv4only.arpa." to discover only.
## Set with your ISP's resolvers in case of custom prefixes (other than Well-Known Prefix 64:ff9b::/96).
## IMPORTANT: Default resolvers listed below support Well-Known Prefix 64:ff9b::/96 only.
# resolver = ['[2606:4700:4700::64]:53', '[2001:4860:4860::64]:53']
@ -822,5 +904,5 @@ skip_incompatible = false
[static]
# [static.'myserver']
# stamp = 'sdns://AQcAAAAAAAAAAAAQMi5kbnNjcnlwdC1jZXJ0Lg'
# [static.myserver]
# stamp = 'sdns://AQcAAAAAAAAAAAAQMi5kbnNjcnlwdC1jZXJ0Lg'

View File

@ -14,12 +14,23 @@
## If this happens, set `block_ipv6` to `false` in the main config file.
## Forward *.lan, *.local, *.home, *.home.arpa, *.internal and *.localdomain to 192.168.1.1
# lan 192.168.1.1
# local 192.168.1.1
# home 192.168.1.1
# home.arpa 192.168.1.1
# internal 192.168.1.1
# localdomain 192.168.1.1
# lan 192.168.1.1
# local 192.168.1.1
# home 192.168.1.1
# home.arpa 192.168.1.1
# internal 192.168.1.1
# localdomain 192.168.1.1
# 192.in-addr.arpa 192.168.1.1
## Forward queries for example.com and *.example.com to 9.9.9.9 and 8.8.8.8
# example.com 9.9.9.9,8.8.8.8
# example.com 9.9.9.9,8.8.8.8
## Forward queries to a resolver using IPv6
# ipv6.example.com [2001:DB8::42]:53
## Forward queries for .onion names to a local Tor client
## Tor must be configured with the following in the torrc file:
## DNSPort 9053
## AutomapHostsOnResolve 1
# onion 127.0.0.1:9053

View File

@ -0,0 +1,41 @@
//go:build gofuzzbeta
// +build gofuzzbeta
package main
import (
"encoding/hex"
"testing"
stamps "github.com/jedisct1/go-dnsstamps"
)
func FuzzParseODoHTargetConfigs(f *testing.F) {
configs_hex := "0020000100010020aacc53b3df0c6eb2d7d5ce4ddf399593376c9903ba6a52a52c3a2340f97bb764"
configs, _ := hex.DecodeString(configs_hex)
f.Add(configs)
f.Fuzz(func(t *testing.T, configs []byte) {
if _, err := parseODoHTargetConfigs(configs); err != nil {
t.Skip()
}
})
}
func FuzzParseStampParser(f *testing.F) {
f.Add("sdns://AgcAAAAAAAAACzEwNC4yMS42Ljc4AA1kb2guY3J5cHRvLnN4Ci9kbnMtcXVlcnk")
f.Add("sdns://AgcAAAAAAAAAGlsyNjA2OjQ3MDA6MzAzNzo6NjgxNTo2NGVdABJkb2gtaXB2Ni5jcnlwdG8uc3gKL2Rucy1xdWVyeQ")
f.Add(
"sdns://AQcAAAAAAAAADTUxLjE1LjEyMi4yNTAg6Q3ZfapcbHgiHKLF7QFoli0Ty1Vsz3RXs1RUbxUrwZAcMi5kbnNjcnlwdC1jZXJ0LnNjYWxld2F5LWFtcw",
)
f.Add(
"sdns://AQcAAAAAAAAAFlsyMDAxOmJjODoxODIwOjUwZDo6MV0g6Q3ZfapcbHgiHKLF7QFoli0Ty1Vsz3RXs1RUbxUrwZAcMi5kbnNjcnlwdC1jZXJ0LnNjYWxld2F5LWFtcw",
)
f.Add("sdns://gQ8xNjMuMTcyLjE4MC4xMjU")
f.Add("sdns://BQcAAAAAAAAADm9kb2guY3J5cHRvLnN4Ci9kbnMtcXVlcnk")
f.Add("sdns://hQcAAAAAAAAAACCi3jNJDEdtNW4tvHN8J3lpIklSa2Wrj7qaNCgEgci9_BpvZG9oLXJlbGF5LmVkZ2Vjb21wdXRlLmFwcAEv")
f.Fuzz(func(t *testing.T, stamp string) {
if _, err := stamps.NewServerStampFromString(stamp); err != nil {
t.Skip()
}
})
}

View File

@ -1,8 +1,9 @@
package main
import (
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"strings"
@ -29,30 +30,44 @@ func (handler localDoHHandler) ServeHTTP(writer http.ResponseWriter, request *ht
writer.WriteHeader(404)
return
}
if request.Header.Get("Content-Type") != dataType {
packet := []byte{}
var err error
start := time.Now()
if request.Method == "POST" &&
request.Header.Get("Content-Type") == dataType {
packet, err = io.ReadAll(io.LimitReader(request.Body, int64(MaxDNSPacketSize)))
if err != nil {
dlog.Warnf("No body in a local DoH query")
return
}
} else if request.Method == "GET" && request.Header.Get("Accept") == dataType {
encodedPacket := request.URL.Query().Get("dns")
if len(encodedPacket) >= MinDNSPacketSize*4/3 && len(encodedPacket) <= MaxDNSPacketSize*4/3 {
packet, err = base64.RawURLEncoding.DecodeString(encodedPacket)
if err != nil {
dlog.Warnf("Invalid base64 in a local DoH query")
return
}
}
}
if len(packet) < MinDNSPacketSize {
writer.Header().Set("Content-Type", "text/plain")
writer.WriteHeader(400)
writer.Write([]byte("dnscrypt-proxy local DoH server\n"))
return
}
start := time.Now()
clientAddr, err := net.ResolveTCPAddr("tcp", request.RemoteAddr)
if err != nil {
dlog.Errorf("Unable to get the client address: [%v]", err)
return
}
xClientAddr := net.Addr(clientAddr)
packet, err := ioutil.ReadAll(io.LimitReader(request.Body, MaxHTTPBodyLength))
if err != nil {
dlog.Warnf("No body in a local DoH query")
return
}
hasEDNS0Padding, err := hasEDNS0Padding(packet)
if err != nil {
writer.WriteHeader(400)
return
}
response := proxy.processIncomingQuery("local_doh", proxy.mainProto, packet, &xClientAddr, nil, start)
response := proxy.processIncomingQuery("local_doh", proxy.mainProto, packet, &xClientAddr, nil, start, false)
if len(response) == 0 {
writer.WriteHeader(500)
return
@ -76,6 +91,7 @@ func (handler localDoHHandler) ServeHTTP(writer http.ResponseWriter, request *ht
writer.Header().Set("X-Pad", pad)
}
writer.Header().Set("Content-Type", dataType)
writer.Header().Set("Content-Length", fmt.Sprint(len(response)))
writer.WriteHeader(200)
writer.Write(response)
}
@ -97,7 +113,25 @@ func (proxy *Proxy) localDoHListener(acceptPc *net.TCPListener) {
}
func dohPaddedLen(unpaddedLen int) int {
boundaries := [...]int{64, 128, 192, 256, 320, 384, 512, 704, 768, 896, 960, 1024, 1088, 1152, 2688, 4080, MaxDNSPacketSize}
boundaries := [...]int{
64,
128,
192,
256,
320,
384,
512,
704,
768,
896,
960,
1024,
1088,
1152,
2688,
4080,
MaxDNSPacketSize,
}
for _, boundary := range boundaries {
if boundary >= unpaddedLen {
return boundary

View File

@ -16,13 +16,25 @@ func Logger(logMaxSize int, logMaxAge int, logMaxBackups int, fileName string) i
if st.Mode().IsDir() {
dlog.Fatalf("[%v] is a directory", fileName)
}
fp, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
fp, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o644)
if err != nil {
dlog.Fatalf("Unable to access [%v]: [%v]", fileName, err)
}
return fp
}
logger := &lumberjack.Logger{LocalTime: true, MaxSize: logMaxSize, MaxAge: logMaxAge, MaxBackups: logMaxBackups, Filename: fileName, Compress: true}
if fp, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o644); err == nil {
fp.Close()
} else {
dlog.Errorf("Unable to create [%v]: [%v]", fileName, err)
}
logger := &lumberjack.Logger{
LocalTime: true,
MaxSize: logMaxSize,
MaxAge: logMaxAge,
MaxBackups: logMaxBackups,
Filename: fileName,
Compress: true,
}
return logger
}

View File

@ -15,7 +15,7 @@ import (
)
const (
AppVersion = "2.0.45"
AppVersion = "2.1.5"
DefaultConfigFileName = "dnscrypt-proxy.toml"
)
@ -27,12 +27,18 @@ type App struct {
}
func main() {
TimezoneSetup()
tzErr := TimezoneSetup()
dlog.Init("dnscrypt-proxy", dlog.SeverityNotice, "DAEMON")
if tzErr != nil {
dlog.Warnf("Timezone setup failed: [%v]", tzErr)
}
runtime.MemProfileRate = 0
seed := make([]byte, 8)
crypto_rand.Read(seed)
rand.Seed(int64(binary.LittleEndian.Uint64(seed[:])))
if _, err := crypto_rand.Read(seed); err != nil {
dlog.Fatal(err)
}
rand.Seed(int64(binary.LittleEndian.Uint64(seed)))
pwd, err := os.Getwd()
if err != nil {
@ -45,6 +51,7 @@ func main() {
flags.Resolve = flag.String("resolve", "", "resolve a DNS name (string can be <name> or <name>,<resolver address>)")
flags.List = flag.Bool("list", false, "print the list of available resolvers for the enabled filters")
flags.ListAll = flag.Bool("list-all", false, "print the complete list of available resolvers, ignoring filters")
flags.IncludeRelays = flag.Bool("include-relays", false, "include the list of available relays in the output of -list and -list-all")
flags.JSONOutput = flag.Bool("json", false, "output list as JSON")
flags.Check = flag.Bool("check", false, "check the configuration file and exit")
flags.ConfigFile = flag.String("config", DefaultConfigFileName, "Path to the configuration file")
@ -59,6 +66,10 @@ func main() {
os.Exit(0)
}
if fullexecpath, err := os.Executable(); err == nil {
WarnIfMaybeWritableByOtherUsers(fullexecpath)
}
app := &App{
flags: &flags,
}
@ -123,7 +134,7 @@ func (app *App) AppMain() {
dlog.Fatal(err)
}
if err := PidFileCreate(); err != nil {
dlog.Criticalf("Unable to create the PID file: %v", err)
dlog.Errorf("Unable to create the PID file: [%v]", err)
}
if err := app.proxy.InitPluginsGlobals(); err != nil {
dlog.Fatal(err)
@ -138,7 +149,9 @@ func (app *App) AppMain() {
}
func (app *App) Stop(service service.Service) error {
PidFileRemove()
if err := PidFileRemove(); err != nil {
dlog.Warnf("Failed to remove the PID file: [%v]", err)
}
dlog.Notice("Stopped.")
return nil
}

View File

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package main

View File

@ -32,8 +32,9 @@ func NetProbe(proxy *Proxy, address string, timeout int) error {
pc, err := net.DialUDP("udp", nil, remoteUDPAddr)
if err == nil {
// Write at least 1 byte. This ensures that sockets are ready to use for writing.
// Windows specific: during the system startup, sockets can be created but the underlying buffers may not be setup yet. If this is the case
// Write fails with WSAENOBUFS: "An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full"
// Windows specific: during the system startup, sockets can be created but the underlying buffers may not be
// setup yet. If this is the case Write fails with WSAENOBUFS: "An operation on a socket could not be
// performed because the system lacked sufficient buffer space or because a queue was full"
_, err = pc.Write([]byte{0})
}
if err != nil {

View File

@ -0,0 +1,191 @@
package main
import (
"crypto/subtle"
"encoding/binary"
"fmt"
"github.com/jedisct1/dlog"
hpkecompact "github.com/jedisct1/go-hpke-compact"
)
const (
odohVersion = uint16(0x0001)
odohTestVersion = uint16(0xff06)
maxODoHConfigs = 10
)
type ODoHTargetConfig struct {
suite *hpkecompact.Suite
keyID []byte
publicKey []byte
}
func encodeLengthValue(b []byte) []byte {
lengthBuffer := make([]byte, 2)
binary.BigEndian.PutUint16(lengthBuffer, uint16(len(b)))
return append(lengthBuffer, b...)
}
func parseODoHTargetConfig(config []byte) (ODoHTargetConfig, error) {
if len(config) < 8 {
return ODoHTargetConfig{}, fmt.Errorf("Malformed config")
}
kemID := binary.BigEndian.Uint16(config[0:2])
kdfID := binary.BigEndian.Uint16(config[2:4])
aeadID := binary.BigEndian.Uint16(config[4:6])
publicKeyLength := binary.BigEndian.Uint16(config[6:8])
publicKey := config[8:]
if len(publicKey) != int(publicKeyLength) {
return ODoHTargetConfig{}, fmt.Errorf("Malformed config")
}
suite, err := hpkecompact.NewSuite(hpkecompact.KemID(kemID), hpkecompact.KdfID(kdfID), hpkecompact.AeadID(aeadID))
if err != nil {
return ODoHTargetConfig{}, err
}
_, _, err = suite.NewClientContext(publicKey, []byte("odoh query"), nil)
if err != nil {
return ODoHTargetConfig{}, err
}
keyID, err := suite.Expand(suite.Extract(config, nil), []byte("odoh key id"), uint16(suite.Hash().Size()))
if err != nil {
return ODoHTargetConfig{}, err
}
return ODoHTargetConfig{
suite: suite,
publicKey: publicKey,
keyID: encodeLengthValue(keyID),
}, nil
}
func parseODoHTargetConfigs(configs []byte) ([]ODoHTargetConfig, error) {
if len(configs) <= 2 {
return nil, fmt.Errorf("Server didn't return any ODoH configurations")
}
length := binary.BigEndian.Uint16(configs)
if len(configs) != int(length)+2 {
return nil, fmt.Errorf("Malformed configs")
}
targets := make([]ODoHTargetConfig, 0)
offset := 2
for {
if offset+4 > len(configs) || len(targets) >= maxODoHConfigs {
break
}
configVersion := binary.BigEndian.Uint16(configs[offset : offset+2])
configLength := binary.BigEndian.Uint16(configs[offset+2 : offset+4])
if configVersion == odohVersion || configVersion == odohTestVersion {
if configVersion != odohVersion {
dlog.Debugf("Server still uses the legacy 0x%x ODoH version", configVersion)
}
target, err := parseODoHTargetConfig(configs[offset+4 : offset+4+int(configLength)])
if err == nil {
targets = append(targets, target)
}
}
offset = offset + int(configLength) + 4
}
return targets, nil
}
type ODoHQuery struct {
suite *hpkecompact.Suite
ctx hpkecompact.ClientContext
odohPlaintext []byte
odohMessage []byte
}
func (t ODoHTargetConfig) encryptQuery(query []byte) (ODoHQuery, error) {
clientCtx, encryptedSharedSecret, err := t.suite.NewClientContext(t.publicKey, []byte("odoh query"), nil)
if err != nil {
return ODoHQuery{}, err
}
odohPlaintext := make([]byte, 4+len(query))
binary.BigEndian.PutUint16(odohPlaintext[0:2], uint16(len(query)))
copy(odohPlaintext[2:], query)
aad := append([]byte{0x01}, t.keyID...)
ciphertext, err := clientCtx.EncryptToServer(odohPlaintext, aad)
if err != nil {
return ODoHQuery{}, err
}
encryptedMessage := encodeLengthValue(append(encryptedSharedSecret, ciphertext...))
odohMessage := append(append([]byte{0x01}, t.keyID...), encryptedMessage...)
return ODoHQuery{
suite: t.suite,
odohPlaintext: odohPlaintext,
odohMessage: odohMessage,
ctx: clientCtx,
}, nil
}
func (q ODoHQuery) decryptResponse(response []byte) ([]byte, error) {
if len(response) < 3 {
return nil, fmt.Errorf("Malformed response")
}
messageType := response[0]
if messageType != uint8(0x02) {
return nil, fmt.Errorf("Malformed response")
}
responseNonceLength := binary.BigEndian.Uint16(response[1:3])
if len(response) < 5+int(responseNonceLength) {
return nil, fmt.Errorf("Malformed response")
}
responseNonceEnc := response[1 : 3+responseNonceLength]
secret, err := q.ctx.Export([]byte("odoh response"), q.suite.KeyBytes)
if err != nil {
return nil, err
}
salt := append(q.odohPlaintext, responseNonceEnc...)
prk := q.suite.Extract(secret, salt)
key, err := q.suite.Expand(prk, []byte("odoh key"), q.suite.KeyBytes)
if err != nil {
return nil, err
}
nonce, err := q.suite.Expand(prk, []byte("odoh nonce"), q.suite.NonceBytes)
if err != nil {
return nil, err
}
cipher, err := q.suite.NewRawCipher(key)
if err != nil {
return nil, err
}
ctLength := binary.BigEndian.Uint16(response[3+int(responseNonceLength) : 5+int(responseNonceLength)])
if int(ctLength) != len(response[5+int(responseNonceLength):]) {
return nil, fmt.Errorf("Malformed response")
}
ct := response[5+int(responseNonceLength):]
aad := response[0 : 3+int(responseNonceLength)]
responsePlaintext, err := cipher.Open(nil, nonce, ct, aad)
if err != nil {
return nil, err
}
responseLength := binary.BigEndian.Uint16(responsePlaintext[0:2])
valid := 1
for i := 4 + int(responseLength); i < len(responsePlaintext); i++ {
valid &= subtle.ConstantTimeByteEq(response[i], 0x00)
}
if valid != 1 {
return nil, fmt.Errorf("Malformed response")
}
return responsePlaintext[2 : 2+int(responseLength)], nil
}

View File

@ -15,10 +15,10 @@ func PidFileCreate() error {
if pidFile == nil || len(*pidFile) == 0 {
return nil
}
if err := os.MkdirAll(filepath.Dir(*pidFile), 0755); err != nil {
if err := os.MkdirAll(filepath.Dir(*pidFile), 0o755); err != nil {
return err
}
return safefile.WriteFile(*pidFile, []byte(strconv.Itoa(os.Getpid())), 0644)
return safefile.WriteFile(*pidFile, []byte(strconv.Itoa(os.Getpid())), 0o644)
}
func PidFileRemove() error {

View File

@ -30,13 +30,13 @@ func (plugin *PluginAllowedIP) Description() string {
func (plugin *PluginAllowedIP) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of allowed IP rules from [%s]", proxy.allowedIPFile)
bin, err := ReadTextFile(proxy.allowedIPFile)
lines, err := ReadTextFile(proxy.allowedIPFile)
if err != nil {
return err
}
plugin.allowedPrefixes = iradix.New()
plugin.allowedIPs = make(map[string]interface{})
for lineNo, line := range strings.Split(string(bin), "\n") {
for lineNo, line := range strings.Split(lines, "\n") {
line = TrimAndStripInlineComments(line)
if len(line) == 0 {
continue
@ -119,10 +119,14 @@ func (plugin *PluginAllowedIP) Eval(pluginsState *PluginsState, msg *dns.Msg) er
if plugin.logger != nil {
qName := pluginsState.qName
var clientIPStr string
if pluginsState.clientProto == "udp" {
switch pluginsState.clientProto {
case "udp":
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
} else {
case "tcp", "local_doh":
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
default:
// Ignore internal flow.
return nil
}
var line string
if plugin.format == "tsv" {
@ -130,7 +134,14 @@ func (plugin *PluginAllowedIP) Eval(pluginsState *PluginsState, msg *dns.Msg) er
year, month, day := now.Date()
hour, minute, second := now.Clock()
tsStr := fmt.Sprintf("[%d-%02d-%02d %02d:%02d:%02d]", year, int(month), day, hour, minute, second)
line = fmt.Sprintf("%s\t%s\t%s\t%s\t%s\n", tsStr, clientIPStr, StringQuote(qName), StringQuote(ipStr), StringQuote(reason))
line = fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\n",
tsStr,
clientIPStr,
StringQuote(qName),
StringQuote(ipStr),
StringQuote(reason),
)
} else if plugin.format == "ltsv" {
line = fmt.Sprintf("time:%d\thost:%s\tqname:%s\tip:%s\tmessage:%s\n", time.Now().Unix(), clientIPStr, StringQuote(qName), StringQuote(ipStr), StringQuote(reason))
} else {

View File

@ -7,36 +7,35 @@ import (
"net"
"strings"
"time"
"unicode"
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
)
type PluginWhitelistName struct {
type PluginAllowName struct {
allWeeklyRanges *map[string]WeeklyRanges
patternMatcher *PatternMatcher
logger io.Writer
format string
}
func (plugin *PluginWhitelistName) Name() string {
return "whitelist_name"
func (plugin *PluginAllowName) Name() string {
return "allow_name"
}
func (plugin *PluginWhitelistName) Description() string {
return "Whitelists DNS queries matching name patterns"
func (plugin *PluginAllowName) Description() string {
return "Allow names matching patterns"
}
func (plugin *PluginWhitelistName) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of whitelisting rules from [%s]", proxy.whitelistNameFile)
bin, err := ReadTextFile(proxy.whitelistNameFile)
func (plugin *PluginAllowName) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of allowed names from [%s]", proxy.allowNameFile)
lines, err := ReadTextFile(proxy.allowNameFile)
if err != nil {
return err
}
plugin.allWeeklyRanges = proxy.allWeeklyRanges
plugin.patternMatcher = NewPatternMatcher()
for lineNo, line := range strings.Split(string(bin), "\n") {
for lineNo, line := range strings.Split(lines, "\n") {
line = TrimAndStripInlineComments(line)
if len(line) == 0 {
continue
@ -44,10 +43,10 @@ func (plugin *PluginWhitelistName) Init(proxy *Proxy) error {
parts := strings.Split(line, "@")
timeRangeName := ""
if len(parts) == 2 {
line = strings.TrimFunc(parts[0], unicode.IsSpace)
timeRangeName = strings.TrimFunc(parts[1], unicode.IsSpace)
line = strings.TrimSpace(parts[0])
timeRangeName = strings.TrimSpace(parts[1])
} else if len(parts) > 2 {
dlog.Errorf("Syntax error in whitelist rules at line %d -- Unexpected @ character", 1+lineNo)
dlog.Errorf("Syntax error in allowed names at line %d -- Unexpected @ character", 1+lineNo)
continue
}
var weeklyRanges *WeeklyRanges
@ -64,43 +63,47 @@ func (plugin *PluginWhitelistName) Init(proxy *Proxy) error {
continue
}
}
if len(proxy.whitelistNameLogFile) == 0 {
if len(proxy.allowNameLogFile) == 0 {
return nil
}
plugin.logger = Logger(proxy.logMaxSize, proxy.logMaxAge, proxy.logMaxBackups, proxy.whitelistNameLogFile)
plugin.format = proxy.whitelistNameFormat
plugin.logger = Logger(proxy.logMaxSize, proxy.logMaxAge, proxy.logMaxBackups, proxy.allowNameLogFile)
plugin.format = proxy.allowNameFormat
return nil
}
func (plugin *PluginWhitelistName) Drop() error {
func (plugin *PluginAllowName) Drop() error {
return nil
}
func (plugin *PluginWhitelistName) Reload() error {
func (plugin *PluginAllowName) Reload() error {
return nil
}
func (plugin *PluginWhitelistName) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
func (plugin *PluginAllowName) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
qName := pluginsState.qName
whitelist, reason, xweeklyRanges := plugin.patternMatcher.Eval(qName)
allowList, reason, xweeklyRanges := plugin.patternMatcher.Eval(qName)
var weeklyRanges *WeeklyRanges
if xweeklyRanges != nil {
weeklyRanges = xweeklyRanges.(*WeeklyRanges)
}
if whitelist {
if allowList {
if weeklyRanges != nil && !weeklyRanges.Match() {
whitelist = false
allowList = false
}
}
if whitelist {
if allowList {
pluginsState.sessionData["whitelisted"] = true
if plugin.logger != nil {
var clientIPStr string
if pluginsState.clientProto == "udp" {
switch pluginsState.clientProto {
case "udp":
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
} else {
case "tcp", "local_doh":
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
default:
// Ignore internal flow.
return nil
}
var line string
if plugin.format == "tsv" {

View File

@ -30,13 +30,13 @@ func (plugin *PluginBlockIP) Description() string {
func (plugin *PluginBlockIP) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of IP blocking rules from [%s]", proxy.blockIPFile)
bin, err := ReadTextFile(proxy.blockIPFile)
lines, err := ReadTextFile(proxy.blockIPFile)
if err != nil {
return err
}
plugin.blockedPrefixes = iradix.New()
plugin.blockedIPs = make(map[string]interface{})
for lineNo, line := range strings.Split(string(bin), "\n") {
for lineNo, line := range strings.Split(lines, "\n") {
line = TrimAndStripInlineComments(line)
if len(line) == 0 {
continue
@ -123,10 +123,14 @@ func (plugin *PluginBlockIP) Eval(pluginsState *PluginsState, msg *dns.Msg) erro
if plugin.logger != nil {
qName := pluginsState.qName
var clientIPStr string
if pluginsState.clientProto == "udp" {
switch pluginsState.clientProto {
case "udp":
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
} else {
case "tcp", "local_doh":
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
default:
// Ignore internal flow.
return nil
}
var line string
if plugin.format == "tsv" {
@ -134,7 +138,14 @@ func (plugin *PluginBlockIP) Eval(pluginsState *PluginsState, msg *dns.Msg) erro
year, month, day := now.Date()
hour, minute, second := now.Clock()
tsStr := fmt.Sprintf("[%d-%02d-%02d %02d:%02d:%02d]", year, int(month), day, hour, minute, second)
line = fmt.Sprintf("%s\t%s\t%s\t%s\t%s\n", tsStr, clientIPStr, StringQuote(qName), StringQuote(ipStr), StringQuote(reason))
line = fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\n",
tsStr,
clientIPStr,
StringQuote(qName),
StringQuote(ipStr),
StringQuote(reason),
)
} else if plugin.format == "ltsv" {
line = fmt.Sprintf("time:%d\thost:%s\tqname:%s\tip:%s\tmessage:%s\n", time.Now().Unix(), clientIPStr, StringQuote(qName), StringQuote(ipStr), StringQuote(reason))
} else {

View File

@ -35,10 +35,12 @@ func (plugin *PluginBlockIPv6) Eval(pluginsState *PluginsState, msg *dns.Msg) er
}
synth := EmptyResponseFromMessage(msg)
hinfo := new(dns.HINFO)
hinfo.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypeHINFO,
Class: dns.ClassINET, Ttl: 86400}
hinfo.Hdr = dns.RR_Header{
Name: question.Name, Rrtype: dns.TypeHINFO,
Class: dns.ClassINET, Ttl: 86400,
}
hinfo.Cpu = "AAAA queries have been locally blocked by dnscrypt-proxy"
hinfo.Os = "Set block_ipv6 to false to disable this feature"
hinfo.Os = "Set block_ipv6 to false to disable that feature"
synth.Answer = []dns.RR{hinfo}
qName := question.Name
i := strings.Index(qName, ".")
@ -54,8 +56,10 @@ func (plugin *PluginBlockIPv6) Eval(pluginsState *PluginsState, msg *dns.Msg) er
soa.Minttl = 2400
soa.Expire = 604800
soa.Retry = 300
soa.Hdr = dns.RR_Header{Name: parentZone, Rrtype: dns.TypeSOA,
Class: dns.ClassINET, Ttl: 60}
soa.Hdr = dns.RR_Header{
Name: parentZone, Rrtype: dns.TypeSOA,
Class: dns.ClassINET, Ttl: 60,
}
synth.Ns = []dns.RR{soa}
pluginsState.synthResponse = synth
pluginsState.action = PluginsActionSynth

View File

@ -7,7 +7,6 @@ import (
"net"
"strings"
"time"
"unicode"
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
@ -45,10 +44,14 @@ func (blockedNames *BlockedNames) check(pluginsState *PluginsState, qName string
pluginsState.returnCode = PluginsReturnCodeReject
if blockedNames.logger != nil {
var clientIPStr string
if pluginsState.clientProto == "udp" {
switch pluginsState.clientProto {
case "udp":
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
} else {
case "tcp", "local_doh":
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
default:
// Ignore internal flow.
return false, nil
}
var line string
if blockedNames.format == "tsv" {
@ -72,8 +75,7 @@ func (blockedNames *BlockedNames) check(pluginsState *PluginsState, qName string
// ---
type PluginBlockName struct {
}
type PluginBlockName struct{}
func (plugin *PluginBlockName) Name() string {
return "block_name"
@ -85,7 +87,7 @@ func (plugin *PluginBlockName) Description() string {
func (plugin *PluginBlockName) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of blocking rules from [%s]", proxy.blockNameFile)
bin, err := ReadTextFile(proxy.blockNameFile)
lines, err := ReadTextFile(proxy.blockNameFile)
if err != nil {
return err
}
@ -93,7 +95,7 @@ func (plugin *PluginBlockName) Init(proxy *Proxy) error {
allWeeklyRanges: proxy.allWeeklyRanges,
patternMatcher: NewPatternMatcher(),
}
for lineNo, line := range strings.Split(string(bin), "\n") {
for lineNo, line := range strings.Split(lines, "\n") {
line = TrimAndStripInlineComments(line)
if len(line) == 0 {
continue
@ -101,8 +103,8 @@ func (plugin *PluginBlockName) Init(proxy *Proxy) error {
parts := strings.Split(line, "@")
timeRangeName := ""
if len(parts) == 2 {
line = strings.TrimFunc(parts[0], unicode.IsSpace)
timeRangeName = strings.TrimFunc(parts[1], unicode.IsSpace)
line = strings.TrimSpace(parts[0])
timeRangeName = strings.TrimSpace(parts[1])
} else if len(parts) > 2 {
dlog.Errorf("Syntax error in block rules at line %d -- Unexpected @ character", 1+lineNo)
continue
@ -149,8 +151,7 @@ func (plugin *PluginBlockName) Eval(pluginsState *PluginsState, msg *dns.Msg) er
// ---
type PluginBlockNameResponse struct {
}
type PluginBlockNameResponse struct{}
func (plugin *PluginBlockNameResponse) Name() string {
return "block_name"

View File

@ -119,9 +119,11 @@ var undelegatedSet = []string{
"envoy",
"example",
"f.f.ip6.arpa",
"fritz.box",
"grp",
"gw==",
"home",
"home.arpa",
"hub",
"internal",
"intra",
@ -134,6 +136,7 @@ var undelegatedSet = []string{
"localdomain",
"localhost",
"localnet",
"mail",
"modem",
"mynet",
"myrouter",

View File

@ -6,8 +6,7 @@ import (
"github.com/miekg/dns"
)
type PluginBlockUnqualified struct {
}
type PluginBlockUnqualified struct{}
func (plugin *PluginBlockUnqualified) Name() string {
return "block_unqualified"

View File

@ -6,10 +6,12 @@ import (
"sync"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/miekg/dns"
sieve "github.com/opencoff/go-sieve"
)
const StaleResponseTTL = 30 * time.Second
type CachedResponse struct {
expiration time.Time
msg dns.Msg
@ -17,7 +19,7 @@ type CachedResponse struct {
type CachedResponses struct {
sync.RWMutex
cache *lru.ARCCache
cache *sieve.Sieve[[32]byte, CachedResponse]
}
var cachedResponses CachedResponses
@ -43,8 +45,7 @@ func computeCacheKey(pluginsState *PluginsState, msg *dns.Msg) [32]byte {
// ---
type PluginCache struct {
}
type PluginCache struct{}
func (plugin *PluginCache) Name() string {
return "cache"
@ -68,29 +69,34 @@ func (plugin *PluginCache) Reload() error {
func (plugin *PluginCache) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
cacheKey := computeCacheKey(pluginsState, msg)
cachedResponses.RLock()
defer cachedResponses.RUnlock()
if cachedResponses.cache == nil {
return nil
}
cachedAny, ok := cachedResponses.cache.Get(cacheKey)
if !ok {
return nil
}
cached := cachedAny.(CachedResponse)
cachedResponses.RLock()
if cachedResponses.cache == nil {
cachedResponses.RUnlock()
return nil
}
cached, ok := cachedResponses.cache.Get(cacheKey)
if !ok {
cachedResponses.RUnlock()
return nil
}
expiration := cached.expiration
synth := cached.msg.Copy()
cachedResponses.RUnlock()
synth.Id = msg.Id
synth.Response = true
synth.Compress = true
synth.Question = msg.Question
if time.Now().After(cached.expiration) {
if time.Now().After(expiration) {
expiration2 := time.Now().Add(StaleResponseTTL)
updateTTL(synth, expiration2)
pluginsState.sessionData["stale"] = synth
return nil
}
updateTTL(&cached.msg, cached.expiration)
updateTTL(synth, expiration)
pluginsState.synthResponse = synth
pluginsState.action = PluginsActionSynth
@ -100,8 +106,7 @@ func (plugin *PluginCache) Eval(pluginsState *PluginsState, msg *dns.Msg) error
// ---
type PluginCacheResponse struct {
}
type PluginCacheResponse struct{}
func (plugin *PluginCacheResponse) Name() string {
return "cache_response"
@ -131,7 +136,13 @@ func (plugin *PluginCacheResponse) Eval(pluginsState *PluginsState, msg *dns.Msg
return nil
}
cacheKey := computeCacheKey(pluginsState, msg)
ttl := getMinTTL(msg, pluginsState.cacheMinTTL, pluginsState.cacheMaxTTL, pluginsState.cacheNegMinTTL, pluginsState.cacheNegMaxTTL)
ttl := getMinTTL(
msg,
pluginsState.cacheMinTTL,
pluginsState.cacheMaxTTL,
pluginsState.cacheNegMinTTL,
pluginsState.cacheNegMaxTTL,
)
cachedResponse := CachedResponse{
expiration: time.Now().Add(ttl),
msg: *msg,
@ -139,8 +150,8 @@ func (plugin *PluginCacheResponse) Eval(pluginsState *PluginsState, msg *dns.Msg
cachedResponses.Lock()
if cachedResponses.cache == nil {
var err error
cachedResponses.cache, err = lru.NewARC(pluginsState.cacheSize)
if err != nil {
cachedResponses.cache = sieve.New[[32]byte, CachedResponse](pluginsState.cacheSize)
if cachedResponses.cache == nil {
cachedResponses.Unlock()
return err
}

View File

@ -19,12 +19,14 @@ type CloakedName struct {
lastUpdate *time.Time
lineNo int
isIP bool
PTR []string
}
type PluginCloak struct {
sync.RWMutex
patternMatcher *PatternMatcher
ttl uint32
createPTR bool
}
func (plugin *PluginCloak) Name() string {
@ -37,14 +39,15 @@ func (plugin *PluginCloak) Description() string {
func (plugin *PluginCloak) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of cloaking rules from [%s]", proxy.cloakFile)
bin, err := ReadTextFile(proxy.cloakFile)
lines, err := ReadTextFile(proxy.cloakFile)
if err != nil {
return err
}
plugin.ttl = proxy.cloakTTL
plugin.createPTR = proxy.cloakedPTR
plugin.patternMatcher = NewPatternMatcher()
cloakedNames := make(map[string]*CloakedName)
for lineNo, line := range strings.Split(string(bin), "\n") {
for lineNo, line := range strings.Split(lines, "\n") {
line = TrimAndStripInlineComments(line)
if len(line) == 0 {
continue
@ -52,8 +55,8 @@ func (plugin *PluginCloak) Init(proxy *Proxy) error {
var target string
parts := strings.FieldsFunc(line, unicode.IsSpace)
if len(parts) == 2 {
line = strings.TrimFunc(parts[0], unicode.IsSpace)
target = strings.TrimFunc(parts[1], unicode.IsSpace)
line = strings.TrimSpace(parts[0])
target = strings.TrimSpace(parts[1])
} else if len(parts) > 2 {
dlog.Errorf("Syntax error in cloaking rules at line %d -- Unexpected space character", 1+lineNo)
continue
@ -67,11 +70,12 @@ func (plugin *PluginCloak) Init(proxy *Proxy) error {
if !found {
cloakedName = &CloakedName{}
}
if ip := net.ParseIP(target); ip != nil {
ip := net.ParseIP(target)
if ip != nil {
if ipv4 := ip.To4(); ipv4 != nil {
cloakedName.ipv4 = append((*cloakedName).ipv4, ipv4)
cloakedName.ipv4 = append(cloakedName.ipv4, ipv4)
} else if ipv6 := ip.To16(); ipv6 != nil {
cloakedName.ipv6 = append((*cloakedName).ipv6, ipv6)
cloakedName.ipv6 = append(cloakedName.ipv6, ipv6)
} else {
dlog.Errorf("Invalid IP address in cloaking rule at line %d", 1+lineNo)
continue
@ -82,6 +86,28 @@ func (plugin *PluginCloak) Init(proxy *Proxy) error {
}
cloakedName.lineNo = lineNo + 1
cloakedNames[line] = cloakedName
if !plugin.createPTR || strings.Contains(line, "*") || !cloakedName.isIP {
continue
}
var ptrLine string
if ipv4 := ip.To4(); ipv4 != nil {
reversed, _ := dns.ReverseAddr(ip.To4().String())
ptrLine = strings.TrimSuffix(reversed, ".")
} else {
reversed, _ := dns.ReverseAddr(cloakedName.ipv6[0].To16().String())
ptrLine = strings.TrimSuffix(reversed, ".")
}
ptrQueryLine := ptrEntryToQuery(ptrLine)
ptrCloakedName, found := cloakedNames[ptrQueryLine]
if !found {
ptrCloakedName = &CloakedName{}
}
ptrCloakedName.isIP = true
ptrCloakedName.PTR = append((*ptrCloakedName).PTR, ptrNameToFQDN(line))
ptrCloakedName.lineNo = lineNo + 1
cloakedNames[ptrQueryLine] = ptrCloakedName
}
for line, cloakedName := range cloakedNames {
if err := plugin.patternMatcher.Add(line, cloakedName, cloakedName.lineNo); err != nil {
@ -91,6 +117,15 @@ func (plugin *PluginCloak) Init(proxy *Proxy) error {
return nil
}
func ptrEntryToQuery(ptrEntry string) string {
return "=" + ptrEntry
}
func ptrNameToFQDN(ptrLine string) string {
ptrLine = strings.TrimPrefix(ptrLine, "=")
return ptrLine + "."
}
func (plugin *PluginCloak) Drop() error {
return nil
}
@ -101,7 +136,7 @@ func (plugin *PluginCloak) Reload() error {
func (plugin *PluginCloak) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
question := msg.Question[0]
if question.Qclass != dns.ClassINET || (question.Qtype != dns.TypeA && question.Qtype != dns.TypeAAAA) {
if question.Qclass != dns.ClassINET || question.Qtype == dns.TypeNS || question.Qtype == dns.TypeSOA {
return nil
}
now := time.Now()
@ -111,6 +146,12 @@ func (plugin *PluginCloak) Eval(pluginsState *PluginsState, msg *dns.Msg) error
plugin.RUnlock()
return nil
}
if question.Qtype != dns.TypeA && question.Qtype != dns.TypeAAAA && question.Qtype != dns.TypePTR {
plugin.RUnlock()
pluginsState.action = PluginsActionReject
pluginsState.returnCode = PluginsReturnCodeCloak
return nil
}
cloakedName := xcloakedName.(*CloakedName)
ttl, expired := plugin.ttl, false
if cloakedName.lastUpdate != nil {
@ -157,15 +198,25 @@ func (plugin *PluginCloak) Eval(pluginsState *PluginsState, msg *dns.Msg) error
rr.A = ip
synth.Answer = append(synth.Answer, rr)
}
} else {
} else if question.Qtype == dns.TypeAAAA {
for _, ip := range cloakedName.ipv6 {
rr := new(dns.AAAA)
rr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl}
rr.AAAA = ip
synth.Answer = append(synth.Answer, rr)
}
} else if question.Qtype == dns.TypePTR {
for _, ptr := range cloakedName.PTR {
rr := new(dns.PTR)
rr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: ttl}
rr.Ptr = ptr
synth.Answer = append(synth.Answer, rr)
}
}
rand.Shuffle(len(synth.Answer), func(i, j int) { synth.Answer[i], synth.Answer[j] = synth.Answer[j], synth.Answer[i] })
rand.Shuffle(
len(synth.Answer),
func(i, j int) { synth.Answer[i], synth.Answer[j] = synth.Answer[j], synth.Answer[i] },
)
pluginsState.synthResponse = synth
pluginsState.action = PluginsActionSynth
pluginsState.returnCode = PluginsReturnCodeCloak

View File

@ -34,19 +34,22 @@ func (plugin *PluginDNS64) Description() string {
}
func (plugin *PluginDNS64) Init(proxy *Proxy) error {
plugin.ipv4Resolver = proxy.listenAddresses[0] //recursively to ourselves
if len(proxy.listenAddresses) == 0 {
return errors.New("At least one listening IP address must be configured for the DNS64 plugin to work")
}
plugin.ipv4Resolver = proxy.listenAddresses[0] // query is sent to ourselves
plugin.pref64Mutex = new(sync.RWMutex)
plugin.proxy = proxy
if len(proxy.dns64Prefixes) != 0 {
plugin.pref64Mutex.RLock()
defer plugin.pref64Mutex.RUnlock()
plugin.pref64Mutex.Lock()
defer plugin.pref64Mutex.Unlock()
for _, prefStr := range proxy.dns64Prefixes {
_, pref, err := net.ParseCIDR(prefStr)
if err != nil {
return err
}
dlog.Infof("Registered DNS64 prefix [%s]", pref.String())
dlog.Noticef("Registered DNS64 prefix [%s]", pref.String())
plugin.pref64 = append(plugin.pref64, pref)
}
} else if len(proxy.dns64Resolvers) != 0 {
@ -54,7 +57,10 @@ func (plugin *PluginDNS64) Init(proxy *Proxy) error {
if err := plugin.refreshPref64(); err != nil {
return err
}
} else {
return nil
}
dlog.Notice("DNS64 map enabled")
return nil
}
@ -87,14 +93,22 @@ func (plugin *PluginDNS64) Eval(pluginsState *PluginsState, msg *dns.Msg) error
if !plugin.proxy.clientsCountInc() {
return errors.New("Too many concurrent connections to handle DNS64 subqueries")
}
respPacket := plugin.proxy.processIncomingQuery("trampoline", plugin.proxy.mainProto, msgAPacket, nil, nil, time.Now())
respPacket := plugin.proxy.processIncomingQuery(
"trampoline",
plugin.proxy.mainProto,
msgAPacket,
nil,
nil,
time.Now(),
false,
)
plugin.proxy.clientsCountDec()
resp := dns.Msg{}
if err := resp.Unpack(respPacket); err != nil {
return err
}
if err != nil || resp.Rcode != dns.RcodeSuccess {
if resp.Rcode != dns.RcodeSuccess {
return nil
}
@ -110,10 +124,12 @@ func (plugin *PluginDNS64) Eval(pluginsState *PluginsState, msg *dns.Msg) error
}
}
synthAAAAs := make([]dns.RR, 0)
synth64 := make([]dns.RR, 0)
for _, answer := range resp.Answer {
header := answer.Header()
if header.Rrtype == dns.TypeA {
if header.Rrtype == dns.TypeCNAME {
synth64 = append(synth64, answer)
} else if header.Rrtype == dns.TypeA {
ttl := initialTTL
if ttl > header.Ttl {
ttl = header.Ttl
@ -121,24 +137,28 @@ func (plugin *PluginDNS64) Eval(pluginsState *PluginsState, msg *dns.Msg) error
ipv4 := answer.(*dns.A).A.To4()
if ipv4 != nil {
plugin.pref64Mutex.Lock()
plugin.pref64Mutex.RLock()
for _, prefix := range plugin.pref64 {
ipv6 := translateToIPv6(ipv4, prefix)
synthAAAA := new(dns.AAAA)
synthAAAA.Hdr = dns.RR_Header{Name: header.Name, Rrtype: dns.TypeAAAA, Class: header.Class, Ttl: ttl}
synthAAAA.Hdr = dns.RR_Header{
Name: header.Name,
Rrtype: dns.TypeAAAA,
Class: header.Class,
Ttl: ttl,
}
synthAAAA.AAAA = ipv6
synthAAAAs = append(synthAAAAs, synthAAAA)
synth64 = append(synth64, synthAAAA)
}
plugin.pref64Mutex.Unlock()
plugin.pref64Mutex.RUnlock()
}
}
}
synth := EmptyResponseFromMessage(msg)
synth.Answer = append(synth.Answer, synthAAAAs...)
msg.Answer = synth64
msg.AuthenticatedData = false
msg.SetEdns0(uint16(MaxDNSUDPSafePacketSize), false)
pluginsState.synthResponse = synth
pluginsState.action = PluginsActionSynth
pluginsState.returnCode = PluginsReturnCodeCloak
return nil
@ -173,7 +193,6 @@ func (plugin *PluginDNS64) fetchPref64(resolver string) error {
client := new(dns.Client)
resp, _, err := client.Exchange(msg, resolver)
if err != nil {
return err
}
@ -190,17 +209,18 @@ func (plugin *PluginDNS64) fetchPref64(resolver string) error {
if ipv6 != nil && len(ipv6) == net.IPv6len {
prefEnd := 0
if wka := net.IPv4(ipv6[12], ipv6[13], ipv6[14], ipv6[15]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { //96
if wka := net.IPv4(ipv6[12], ipv6[13], ipv6[14], ipv6[15]); wka.Equal(rfc7050WKA1) ||
wka.Equal(rfc7050WKA2) { // 96
prefEnd = 12
} else if wka := net.IPv4(ipv6[9], ipv6[10], ipv6[11], ipv6[12]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { //64
} else if wka := net.IPv4(ipv6[9], ipv6[10], ipv6[11], ipv6[12]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { // 64
prefEnd = 8
} else if wka := net.IPv4(ipv6[7], ipv6[9], ipv6[10], ipv6[11]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { //56
} else if wka := net.IPv4(ipv6[7], ipv6[9], ipv6[10], ipv6[11]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { // 56
prefEnd = 7
} else if wka := net.IPv4(ipv6[6], ipv6[7], ipv6[9], ipv6[10]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { //48
} else if wka := net.IPv4(ipv6[6], ipv6[7], ipv6[9], ipv6[10]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { // 48
prefEnd = 6
} else if wka := net.IPv4(ipv6[5], ipv6[6], ipv6[7], ipv6[9]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { //40
} else if wka := net.IPv4(ipv6[5], ipv6[6], ipv6[7], ipv6[9]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { // 40
prefEnd = 5
} else if wka := net.IPv4(ipv6[4], ipv6[5], ipv6[6], ipv6[7]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { //32
} else if wka := net.IPv4(ipv6[4], ipv6[5], ipv6[6], ipv6[7]); wka.Equal(rfc7050WKA1) || wka.Equal(rfc7050WKA2) { // 32
prefEnd = 4
}
@ -222,8 +242,8 @@ func (plugin *PluginDNS64) fetchPref64(resolver string) error {
return errors.New("Empty Pref64 list")
}
plugin.pref64Mutex.RLock()
defer plugin.pref64Mutex.RUnlock()
plugin.pref64Mutex.Lock()
defer plugin.pref64Mutex.Unlock()
plugin.pref64 = prefixes
return nil
}
@ -235,8 +255,8 @@ func (plugin *PluginDNS64) refreshPref64() error {
}
}
plugin.pref64Mutex.Lock()
defer plugin.pref64Mutex.Unlock()
plugin.pref64Mutex.RLock()
defer plugin.pref64Mutex.RUnlock()
if len(plugin.pref64) == 0 {
return errors.New("Empty Pref64 list")
}

View File

@ -9,8 +9,7 @@ import (
"github.com/miekg/dns"
)
type PluginFirefox struct {
}
type PluginFirefox struct{}
func (plugin *PluginFirefox) Name() string {
return "firefox"

View File

@ -5,7 +5,6 @@ import (
"math/rand"
"net"
"strings"
"unicode"
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
@ -30,11 +29,11 @@ func (plugin *PluginForward) Description() string {
func (plugin *PluginForward) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of forwarding rules from [%s]", proxy.forwardFile)
bin, err := ReadTextFile(proxy.forwardFile)
lines, err := ReadTextFile(proxy.forwardFile)
if err != nil {
return err
}
for lineNo, line := range strings.Split(string(bin), "\n") {
for lineNo, line := range strings.Split(lines, "\n") {
line = TrimAndStripInlineComments(line)
if len(line) == 0 {
continue
@ -49,10 +48,17 @@ func (plugin *PluginForward) Init(proxy *Proxy) error {
domain = strings.ToLower(domain)
var servers []string
for _, server := range strings.Split(serversStr, ",") {
server = strings.TrimFunc(server, unicode.IsSpace)
if net.ParseIP(server) != nil {
server = fmt.Sprintf("%s:%d", server, 53)
server = strings.TrimSpace(server)
server = strings.TrimPrefix(server, "[")
server = strings.TrimSuffix(server, "]")
if ip := net.ParseIP(server); ip != nil {
if ip.To4() != nil {
server = fmt.Sprintf("%s:%d", server, 53)
} else {
server = fmt.Sprintf("[%s]:%d", server, 53)
}
}
dlog.Infof("Forwarding [%s] to %s", domain, server)
servers = append(servers, server)
}
if len(servers) == 0 {
@ -83,7 +89,9 @@ func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) erro
if candidateLen > qNameLen {
continue
}
if qName[qNameLen-candidateLen:] == candidate.domain && (candidateLen == qNameLen || (qName[qNameLen-candidateLen-1] == '.')) {
if (qName[qNameLen-candidateLen:] == candidate.domain &&
(candidateLen == qNameLen || (qName[qNameLen-candidateLen-1] == '.'))) ||
(candidate.domain == ".") {
servers = candidate.servers
break
}

View File

@ -30,12 +30,18 @@ func (plugin *PluginGetSetPayloadSize) Eval(pluginsState *PluginsState, msg *dns
dnssec := false
if edns0 != nil {
pluginsState.maxUnencryptedUDPSafePayloadSize = int(edns0.UDPSize())
pluginsState.originalMaxPayloadSize = Max(pluginsState.maxUnencryptedUDPSafePayloadSize-ResponseOverhead, pluginsState.originalMaxPayloadSize)
pluginsState.originalMaxPayloadSize = Max(
pluginsState.maxUnencryptedUDPSafePayloadSize-ResponseOverhead,
pluginsState.originalMaxPayloadSize,
)
dnssec = edns0.Do()
}
var options *[]dns.EDNS0
pluginsState.dnssec = dnssec
pluginsState.maxPayloadSize = Min(MaxDNSUDPPacketSize-ResponseOverhead, Max(pluginsState.originalMaxPayloadSize, pluginsState.maxPayloadSize))
pluginsState.maxPayloadSize = Min(
MaxDNSUDPPacketSize-ResponseOverhead,
Max(pluginsState.originalMaxPayloadSize, pluginsState.maxPayloadSize),
)
if pluginsState.maxPayloadSize > 512 {
extra2 := []dns.RR{}
for _, extra := range msg.Extra {

View File

@ -43,17 +43,21 @@ func (plugin *PluginNxLog) Eval(pluginsState *PluginsState, msg *dns.Msg) error
if msg.Rcode != dns.RcodeNameError {
return nil
}
var clientIPStr string
switch pluginsState.clientProto {
case "udp":
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
case "tcp", "local_doh":
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
default:
// Ignore internal flow.
return nil
}
question := msg.Question[0]
qType, ok := dns.TypeToString[question.Qtype]
if !ok {
qType = string(qType)
}
var clientIPStr string
if pluginsState.clientProto == "udp" {
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
} else {
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
}
qName := pluginsState.qName
var line string

View File

@ -43,6 +43,16 @@ func (plugin *PluginQueryLog) Reload() error {
}
func (plugin *PluginQueryLog) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
var clientIPStr string
switch pluginsState.clientProto {
case "udp":
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
case "tcp", "local_doh":
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
default:
// Ignore internal flow.
return nil
}
question := msg.Question[0]
qType, ok := dns.TypeToString[question.Qtype]
if !ok {
@ -55,14 +65,6 @@ func (plugin *PluginQueryLog) Eval(pluginsState *PluginsState, msg *dns.Msg) err
}
}
}
var clientIPStr string
if pluginsState.clientProto == "udp" {
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
} else if pluginsState.clientProto == "tcp" {
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
} else {
clientIPStr = "-"
}
qName := pluginsState.qName
if pluginsState.cacheHit {
@ -88,8 +90,16 @@ func (plugin *PluginQueryLog) Eval(pluginsState *PluginsState, msg *dns.Msg) err
year, month, day := now.Date()
hour, minute, second := now.Clock()
tsStr := fmt.Sprintf("[%d-%02d-%02d %02d:%02d:%02d]", year, int(month), day, hour, minute, second)
line = fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%dms\t%s\n", tsStr, clientIPStr, StringQuote(qName), qType, returnCode, requestDuration/time.Millisecond,
StringQuote(pluginsState.serverName))
line = fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\t%dms\t%s\n",
tsStr,
clientIPStr,
StringQuote(qName),
qType,
returnCode,
requestDuration/time.Millisecond,
StringQuote(pluginsState.serverName),
)
} else if plugin.format == "ltsv" {
cached := 0
if pluginsState.cacheHit {

View File

@ -18,8 +18,10 @@ func (plugin *PluginQueryMeta) Description() string {
func (plugin *PluginQueryMeta) Init(proxy *Proxy) error {
queryMetaRR := new(dns.TXT)
queryMetaRR.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeTXT,
Class: dns.ClassINET, Ttl: 86400}
queryMetaRR.Hdr = dns.RR_Header{
Name: ".", Rrtype: dns.TypeTXT,
Class: dns.ClassINET, Ttl: 86400,
}
queryMetaRR.Txt = proxy.queryMeta
plugin.queryMetaRR = queryMetaRR
return nil

View File

@ -101,8 +101,8 @@ func (proxy *Proxy) InitPluginsGlobals() error {
if len(proxy.queryMeta) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginQueryMeta)))
}
if len(proxy.whitelistNameFile) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginWhitelistName)))
if len(proxy.allowNameFile) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginAllowName)))
}
*queryPlugins = append(*queryPlugins, Plugin(new(PluginFirefox)))
@ -189,11 +189,11 @@ func parseBlockedQueryResponse(blockedResponse string, pluginsGlobals *PluginsGl
if strings.HasPrefix(blockedResponse, "a:") {
blockedIPStrings := strings.Split(blockedResponse, ",")
(*pluginsGlobals).respondWithIPv4 = net.ParseIP(strings.TrimPrefix(blockedIPStrings[0], "a:"))
pluginsGlobals.respondWithIPv4 = net.ParseIP(strings.TrimPrefix(blockedIPStrings[0], "a:"))
if (*pluginsGlobals).respondWithIPv4 == nil {
if pluginsGlobals.respondWithIPv4 == nil {
dlog.Notice("Error parsing IPv4 response given in blocked_query_response option, defaulting to `hinfo`")
(*pluginsGlobals).refusedCodeInResponses = false
pluginsGlobals.refusedCodeInResponses = false
return
}
@ -203,28 +203,30 @@ func parseBlockedQueryResponse(blockedResponse string, pluginsGlobals *PluginsGl
if strings.HasPrefix(ipv6Response, "[") {
ipv6Response = strings.Trim(ipv6Response, "[]")
}
(*pluginsGlobals).respondWithIPv6 = net.ParseIP(ipv6Response)
pluginsGlobals.respondWithIPv6 = net.ParseIP(ipv6Response)
if (*pluginsGlobals).respondWithIPv6 == nil {
dlog.Notice("Error parsing IPv6 response given in blocked_query_response option, defaulting to IPv4")
if pluginsGlobals.respondWithIPv6 == nil {
dlog.Notice(
"Error parsing IPv6 response given in blocked_query_response option, defaulting to IPv4",
)
}
} else {
dlog.Noticef("Invalid IPv6 response given in blocked_query_response option [%s], the option should take the form 'a:<IPv4>,aaaa:<IPv6>'", blockedIPStrings[1])
}
}
if (*pluginsGlobals).respondWithIPv6 == nil {
(*pluginsGlobals).respondWithIPv6 = (*pluginsGlobals).respondWithIPv4
if pluginsGlobals.respondWithIPv6 == nil {
pluginsGlobals.respondWithIPv6 = pluginsGlobals.respondWithIPv4
}
} else {
switch blockedResponse {
case "refused":
(*pluginsGlobals).refusedCodeInResponses = true
pluginsGlobals.refusedCodeInResponses = true
case "hinfo":
(*pluginsGlobals).refusedCodeInResponses = false
pluginsGlobals.refusedCodeInResponses = false
default:
dlog.Noticef("Invalid blocked_query_response option [%s], defaulting to `hinfo`", blockedResponse)
(*pluginsGlobals).refusedCodeInResponses = false
pluginsGlobals.refusedCodeInResponses = false
}
}
}
@ -238,7 +240,13 @@ type Plugin interface {
Eval(pluginsState *PluginsState, msg *dns.Msg) error
}
func NewPluginsState(proxy *Proxy, clientProto string, clientAddr *net.Addr, serverProto string, start time.Time) PluginsState {
func NewPluginsState(
proxy *Proxy,
clientProto string,
clientAddr *net.Addr,
serverProto string,
start time.Time,
) PluginsState {
return PluginsState{
action: PluginsActionContinue,
returnCode: PluginsReturnCodePass,
@ -262,7 +270,11 @@ func NewPluginsState(proxy *Proxy, clientProto string, clientAddr *net.Addr, ser
}
}
func (pluginsState *PluginsState) ApplyQueryPlugins(pluginsGlobals *PluginsGlobals, packet []byte, needsEDNS0Padding bool) ([]byte, error) {
func (pluginsState *PluginsState) ApplyQueryPlugins(
pluginsGlobals *PluginsGlobals,
packet []byte,
needsEDNS0Padding bool,
) ([]byte, error) {
msg := dns.Msg{}
if err := msg.Unpack(packet); err != nil {
return packet, err
@ -288,7 +300,13 @@ func (pluginsState *PluginsState) ApplyQueryPlugins(pluginsGlobals *PluginsGloba
return packet, err
}
if pluginsState.action == PluginsActionReject {
synth := RefusedResponseFromMessage(&msg, pluginsGlobals.refusedCodeInResponses, pluginsGlobals.respondWithIPv4, pluginsGlobals.respondWithIPv6, pluginsState.rejectTTL)
synth := RefusedResponseFromMessage(
&msg,
pluginsGlobals.refusedCodeInResponses,
pluginsGlobals.respondWithIPv4,
pluginsGlobals.respondWithIPv6,
pluginsState.rejectTTL,
)
pluginsState.synthResponse = synth
}
if pluginsState.action != PluginsActionContinue {
@ -309,7 +327,11 @@ func (pluginsState *PluginsState) ApplyQueryPlugins(pluginsGlobals *PluginsGloba
return packet2, nil
}
func (pluginsState *PluginsState) ApplyResponsePlugins(pluginsGlobals *PluginsGlobals, packet []byte, ttl *uint32) ([]byte, error) {
func (pluginsState *PluginsState) ApplyResponsePlugins(
pluginsGlobals *PluginsGlobals,
packet []byte,
ttl *uint32,
) ([]byte, error) {
msg := dns.Msg{Compress: true}
if err := msg.Unpack(packet); err != nil {
if len(packet) >= MinDNSPacketSize && HasTCFlag(packet) {
@ -336,7 +358,13 @@ func (pluginsState *PluginsState) ApplyResponsePlugins(pluginsGlobals *PluginsGl
return packet, err
}
if pluginsState.action == PluginsActionReject {
synth := RefusedResponseFromMessage(&msg, pluginsGlobals.refusedCodeInResponses, pluginsGlobals.respondWithIPv4, pluginsGlobals.respondWithIPv6, pluginsState.rejectTTL)
synth := RefusedResponseFromMessage(
&msg,
pluginsGlobals.refusedCodeInResponses,
pluginsGlobals.respondWithIPv4,
pluginsGlobals.respondWithIPv6,
pluginsState.rejectTTL,
)
pluginsState.synthResponse = synth
}
if pluginsState.action != PluginsActionContinue {

View File

@ -15,8 +15,7 @@ import (
)
func (proxy *Proxy) dropPrivilege(userStr string, fds []*os.File) {
currentUser, err := user.Current()
if err != nil && currentUser.Uid != "0" {
if os.Geteuid() != 0 {
dlog.Fatal("Root privileges are required in order to switch to a different user. Maybe try again with 'sudo'")
}
userInfo, err := user.Lookup(userStr)
@ -25,9 +24,19 @@ func (proxy *Proxy) dropPrivilege(userStr string, fds []*os.File) {
if err != nil {
uid, err2 := strconv.Atoi(userStr)
if err2 != nil || uid <= 0 {
dlog.Fatalf("Unable to retrieve any information about user [%s]: [%s] - Remove the user_name directive from the configuration file in order to avoid identity switch", userStr, err)
dlog.Fatalf(
"Unable to retrieve any information about user [%s]: [%s] - Remove the user_name directive from the configuration file in order to avoid identity switch",
userStr,
err,
)
}
dlog.Warnf("Unable to retrieve any information about user [%s]: [%s] - Switching to user id [%v] with the same group id, as [%v] looks like a user id. But you should remove or fix the user_name directive in the configuration file if possible", userStr, err, uid, uid)
dlog.Warnf(
"Unable to retrieve any information about user [%s]: [%s] - Switching to user id [%v] with the same group id, as [%v] looks like a user id. But you should remove or fix the user_name directive in the configuration file if possible",
userStr,
err,
uid,
uid,
)
userInfo = &user.User{Uid: userStr, Gid: userStr}
}
uid, err := strconv.Atoi(userInfo.Uid)

View File

@ -1,3 +1,4 @@
//go:build !windows && !linux
// +build !windows,!linux
package main
@ -16,8 +17,7 @@ import (
)
func (proxy *Proxy) dropPrivilege(userStr string, fds []*os.File) {
currentUser, err := user.Current()
if err != nil && currentUser.Uid != "0" {
if os.Geteuid() != 0 {
dlog.Fatal("Root privileges are required in order to switch to a different user. Maybe try again with 'sudo'")
}
userInfo, err := user.Lookup(userStr)
@ -26,9 +26,19 @@ func (proxy *Proxy) dropPrivilege(userStr string, fds []*os.File) {
if err != nil {
uid, err2 := strconv.Atoi(userStr)
if err2 != nil || uid <= 0 {
dlog.Fatalf("Unable to retrieve any information about user [%s]: [%s] - Remove the user_name directive from the configuration file in order to avoid identity switch", userStr, err)
dlog.Fatalf(
"Unable to retrieve any information about user [%s]: [%s] - Remove the user_name directive from the configuration file in order to avoid identity switch",
userStr,
err,
)
}
dlog.Warnf("Unable to retrieve any information about user [%s]: [%s] - Switching to user id [%v] with the same group id, as [%v] looks like a user id. But you should remove or fix the user_name directive in the configuration file if possible", userStr, err, uid, uid)
dlog.Warnf(
"Unable to retrieve any information about user [%s]: [%s] - Switching to user id [%v] with the same group id, as [%v] looks like a user id. But you should remove or fix the user_name directive in the configuration file if possible",
userStr,
err,
uid,
uid,
)
userInfo = &user.User{Uid: userStr, Gid: userStr}
}
uid, err := strconv.Atoi(userInfo.Uid)

View File

@ -4,6 +4,7 @@ import (
"context"
crypto_rand "crypto/rand"
"encoding/binary"
"math/rand"
"net"
"os"
"runtime"
@ -37,7 +38,6 @@ type Proxy struct {
listenAddresses []string
localDoHListenAddresses []string
xTransport *XTransport
dohCreds *map[string]DOHClientCreds
allWeeklyRanges *map[string]WeeklyRanges
routes *map[string][]string
captivePortalMap *CaptivePortalMap
@ -56,21 +56,25 @@ type Proxy struct {
allowedIPLogFile string
queryLogFormat string
blockIPFile string
whitelistNameFormat string
whitelistNameLogFile string
allowNameFile string
allowNameFormat string
allowNameLogFile string
blockNameLogFile string
whitelistNameFile string
blockNameFormat string
blockNameFile string
queryLogFile string
blockedQueryResponse string
userName string
nxLogFile string
blockNameFormat string
proxySecretKey [32]byte
proxyPublicKey [32]byte
ServerNames []string
DisabledServerNames []string
requiredProps stamps.ServerInformalProperties
certRefreshDelayAfterFailure time.Duration
timeout time.Duration
certRefreshDelay time.Duration
certRefreshConcurrency int
cacheSize int
logMaxBackups int
logMaxAge int
@ -83,6 +87,7 @@ type Proxy struct {
cacheMinTTL uint32
cacheNegMaxTTL uint32
cloakTTL uint32
cloakedPTR bool
cache bool
pluginBlockIPv6 bool
ephemeralKeys bool
@ -93,14 +98,11 @@ type Proxy struct {
anonDirectCertFallback bool
pluginBlockUndelegated bool
child bool
daemonize bool
requiredProps stamps.ServerInformalProperties
ServerNames []string
DisabledServerNames []string
SourceIPv4 bool
SourceIPv6 bool
SourceDNSCrypt bool
SourceDoH bool
SourceODoH bool
}
func (proxy *Proxy) registerUDPListener(conn *net.UDPConn) {
@ -116,11 +118,18 @@ func (proxy *Proxy) registerLocalDoHListener(listener *net.TCPListener) {
}
func (proxy *Proxy) addDNSListener(listenAddrStr string) {
listenUDPAddr, err := net.ResolveUDPAddr("udp", listenAddrStr)
udp := "udp"
tcp := "tcp"
isIPv4 := isDigit(listenAddrStr[0])
if isIPv4 {
udp = "udp4"
tcp = "tcp4"
}
listenUDPAddr, err := net.ResolveUDPAddr(udp, listenAddrStr)
if err != nil {
dlog.Fatal(err)
}
listenTCPAddr, err := net.ResolveTCPAddr("tcp", listenAddrStr)
listenTCPAddr, err := net.ResolveTCPAddr(tcp, listenAddrStr)
if err != nil {
dlog.Fatal(err)
}
@ -139,11 +148,11 @@ func (proxy *Proxy) addDNSListener(listenAddrStr string) {
// if 'userName' is set and we are the parent process
if !proxy.child {
// parent
listenerUDP, err := net.ListenUDP("udp", listenUDPAddr)
listenerUDP, err := net.ListenUDP(udp, listenUDPAddr)
if err != nil {
dlog.Fatal(err)
}
listenerTCP, err := net.ListenTCP("tcp", listenTCPAddr)
listenerTCP, err := net.ListenTCP(tcp, listenTCPAddr)
if err != nil {
dlog.Fatal(err)
}
@ -184,7 +193,12 @@ func (proxy *Proxy) addDNSListener(listenAddrStr string) {
}
func (proxy *Proxy) addLocalDoHListener(listenAddrStr string) {
listenTCPAddr, err := net.ResolveTCPAddr("tcp", listenAddrStr)
network := "tcp"
isIPv4 := isDigit(listenAddrStr[0])
if isIPv4 {
network = "tcp4"
}
listenTCPAddr, err := net.ResolveTCPAddr(network, listenAddrStr)
if err != nil {
dlog.Fatal(err)
}
@ -200,7 +214,7 @@ func (proxy *Proxy) addLocalDoHListener(listenAddrStr string) {
// if 'userName' is set and we are the parent process
if !proxy.child {
// parent
listenerTCP, err := net.ListenTCP("tcp", listenTCPAddr)
listenerTCP, err := net.ListenTCP(network, listenTCPAddr)
if err != nil {
dlog.Fatal(err)
}
@ -232,6 +246,17 @@ func (proxy *Proxy) StartProxy() {
}
curve25519.ScalarBaseMult(&proxy.proxyPublicKey, &proxy.proxySecretKey)
proxy.startAcceptingClients()
if !proxy.child {
// Notify the service manager that dnscrypt-proxy is ready. dnscrypt-proxy manages itself in case
// servers are not immediately live/reachable. The service manager may assume it is initialized and
// functioning properly. Note that the service manager 'Ready' signal is delayed if netprobe
// cannot reach the internet during start-up.
if err := ServiceManagerReadyNotify(); err != nil {
dlog.Fatal(err)
}
}
proxy.xTransport.internalResolverReady = false
proxy.xTransport.internalResolvers = proxy.listenAddresses
liveServers, err := proxy.serversInfo.refresh(proxy)
if liveServers > 0 {
proxy.certIgnoreTimestamp = false
@ -241,11 +266,6 @@ func (proxy *Proxy) StartProxy() {
}
if liveServers > 0 {
dlog.Noticef("dnscrypt-proxy is ready - live servers: %d", liveServers)
if !proxy.child {
if err := ServiceManagerReadyNotify(); err != nil {
dlog.Fatal(err)
}
}
} else if err != nil {
dlog.Error(err)
dlog.Notice("dnscrypt-proxy is waiting for at least one server to be reachable")
@ -283,10 +303,16 @@ func (proxy *Proxy) updateRegisteredServers() error {
dlog.Criticalf("Unable to use source [%s]: [%s]", source.name, err)
return err
}
dlog.Warnf("Error in source [%s]: [%s] -- Continuing with reduced server count [%d]", source.name, err, len(registeredServers))
dlog.Warnf(
"Error in source [%s]: [%s] -- Continuing with reduced server count [%d]",
source.name,
err,
len(registeredServers),
)
}
for _, registeredServer := range registeredServers {
if registeredServer.stamp.Proto != stamps.StampProtoTypeDNSCryptRelay && registeredServer.stamp.Proto != stamps.StampProtoTypeODoHRelay {
if registeredServer.stamp.Proto != stamps.StampProtoTypeDNSCryptRelay &&
registeredServer.stamp.Proto != stamps.StampProtoTypeODoHRelay {
if len(proxy.ServerNames) > 0 {
if !includesName(proxy.ServerNames, registeredServer.name) {
continue
@ -310,13 +336,19 @@ func (proxy *Proxy) updateRegisteredServers() error {
continue
}
}
if registeredServer.stamp.Proto == stamps.StampProtoTypeDNSCryptRelay || registeredServer.stamp.Proto == stamps.StampProtoTypeODoHRelay {
if registeredServer.stamp.Proto == stamps.StampProtoTypeDNSCryptRelay ||
registeredServer.stamp.Proto == stamps.StampProtoTypeODoHRelay {
var found bool
for i, currentRegisteredRelay := range proxy.registeredRelays {
if currentRegisteredRelay.name == registeredServer.name {
found = true
if currentRegisteredRelay.stamp.String() != registeredServer.stamp.String() {
dlog.Infof("Updating stamp for [%s] was: %s now: %s", registeredServer.name, currentRegisteredRelay.stamp.String(), registeredServer.stamp.String())
dlog.Infof(
"Updating stamp for [%s] was: %s now: %s",
registeredServer.name,
currentRegisteredRelay.stamp.String(),
registeredServer.stamp.String(),
)
proxy.registeredRelays[i].stamp = registeredServer.stamp
dlog.Debugf("Total count of registered relays %v", len(proxy.registeredRelays))
}
@ -328,7 +360,8 @@ func (proxy *Proxy) updateRegisteredServers() error {
}
} else {
if !((proxy.SourceDNSCrypt && registeredServer.stamp.Proto == stamps.StampProtoTypeDNSCrypt) ||
(proxy.SourceDoH && registeredServer.stamp.Proto == stamps.StampProtoTypeDoH)) {
(proxy.SourceDoH && registeredServer.stamp.Proto == stamps.StampProtoTypeDoH) ||
(proxy.SourceODoH && registeredServer.stamp.Proto == stamps.StampProtoTypeODoHTarget)) {
continue
}
var found bool
@ -352,6 +385,9 @@ func (proxy *Proxy) updateRegisteredServers() error {
for _, registeredServer := range proxy.registeredServers {
proxy.serversInfo.registerServer(registeredServer.name, registeredServer.stamp)
}
for _, registeredRelay := range proxy.registeredRelays {
proxy.serversInfo.registerRelay(registeredRelay.name, registeredRelay.stamp)
}
return nil
}
@ -364,14 +400,22 @@ func (proxy *Proxy) udpListener(clientPc *net.UDPConn) {
return
}
packet := buffer[:length]
if !proxy.clientsCountInc() {
dlog.Warnf("Too many incoming connections (max=%d)", proxy.maxClients)
proxy.processIncomingQuery(
"udp",
proxy.mainProto,
packet,
&clientAddr,
clientPc,
time.Now(),
true,
) // respond synchronously, but only to cached/synthesized queries
continue
}
go func() {
start := time.Now()
if !proxy.clientsCountInc() {
dlog.Warnf("Too many incoming connections (max=%d)", proxy.maxClients)
return
}
defer proxy.clientsCountDec()
proxy.processIncomingQuery("udp", proxy.mainProto, packet, &clientAddr, clientPc, start)
proxy.processIncomingQuery("udp", proxy.mainProto, packet, &clientAddr, clientPc, time.Now(), false)
}()
}
}
@ -383,23 +427,24 @@ func (proxy *Proxy) tcpListener(acceptPc *net.TCPListener) {
if err != nil {
continue
}
if !proxy.clientsCountInc() {
dlog.Warnf("Too many incoming connections (max=%d)", proxy.maxClients)
clientPc.Close()
continue
}
go func() {
start := time.Now()
defer clientPc.Close()
if !proxy.clientsCountInc() {
dlog.Warnf("Too many incoming connections (max=%d)", proxy.maxClients)
return
}
defer proxy.clientsCountDec()
if err := clientPc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil {
return
}
start := time.Now()
packet, err := ReadPrefixed(&clientPc)
if err != nil {
return
}
clientAddr := clientPc.RemoteAddr()
proxy.processIncomingQuery("tcp", "tcp", packet, &clientAddr, clientPc, start)
proxy.processIncomingQuery("tcp", "tcp", packet, &clientAddr, clientPc, start, false)
}()
}
}
@ -409,7 +454,13 @@ func (proxy *Proxy) udpListenerFromAddr(listenAddr *net.UDPAddr) error {
if err != nil {
return err
}
clientPc, err := listenConfig.ListenPacket(context.Background(), "udp", listenAddr.String())
listenAddrStr := listenAddr.String()
network := "udp"
isIPv4 := isDigit(listenAddrStr[0])
if isIPv4 {
network = "udp4"
}
clientPc, err := listenConfig.ListenPacket(context.Background(), network, listenAddrStr)
if err != nil {
return err
}
@ -423,7 +474,13 @@ func (proxy *Proxy) tcpListenerFromAddr(listenAddr *net.TCPAddr) error {
if err != nil {
return err
}
acceptPc, err := listenConfig.Listen(context.Background(), "tcp", listenAddr.String())
listenAddrStr := listenAddr.String()
network := "tcp"
isIPv4 := isDigit(listenAddrStr[0])
if isIPv4 {
network = "tcp4"
}
acceptPc, err := listenConfig.Listen(context.Background(), network, listenAddrStr)
if err != nil {
return err
}
@ -437,7 +494,13 @@ func (proxy *Proxy) localDoHListenerFromAddr(listenAddr *net.TCPAddr) error {
if err != nil {
return err
}
acceptPc, err := listenConfig.Listen(context.Background(), "tcp", listenAddr.String())
listenAddrStr := listenAddr.String()
network := "tcp"
isIPv4 := isDigit(listenAddrStr[0])
if isIPv4 {
network = "tcp4"
}
acceptPc, err := listenConfig.Listen(context.Background(), network, listenAddrStr)
if err != nil {
return err
}
@ -471,7 +534,12 @@ func (proxy *Proxy) prepareForRelay(ip net.IP, port int, encryptedQuery *[]byte)
*encryptedQuery = relayedQuery
}
func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, sharedKey *[32]byte, encryptedQuery []byte, clientNonce []byte) ([]byte, error) {
func (proxy *Proxy) exchangeWithUDPServer(
serverInfo *ServerInfo,
sharedKey *[32]byte,
encryptedQuery []byte,
clientNonce []byte,
) ([]byte, error) {
upstreamAddr := serverInfo.UDPAddr
if serverInfo.Relay != nil && serverInfo.Relay.Dnscrypt != nil {
upstreamAddr = serverInfo.Relay.Dnscrypt.RelayUDPAddr
@ -480,7 +548,7 @@ func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, sharedKey *[32
var pc net.Conn
proxyDialer := proxy.xTransport.proxyDialer
if proxyDialer == nil {
pc, err = net.DialUDP("udp", nil, upstreamAddr)
pc, err = net.DialTimeout("udp", upstreamAddr.String(), serverInfo.Timeout)
} else {
pc, err = (*proxyDialer).Dial("udp", upstreamAddr.String())
}
@ -509,7 +577,12 @@ func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, sharedKey *[32
return proxy.Decrypt(serverInfo, sharedKey, encryptedResponse, clientNonce)
}
func (proxy *Proxy) exchangeWithTCPServer(serverInfo *ServerInfo, sharedKey *[32]byte, encryptedQuery []byte, clientNonce []byte) ([]byte, error) {
func (proxy *Proxy) exchangeWithTCPServer(
serverInfo *ServerInfo,
sharedKey *[32]byte,
encryptedQuery []byte,
clientNonce []byte,
) ([]byte, error) {
upstreamAddr := serverInfo.TCPAddr
if serverInfo.Relay != nil && serverInfo.Relay.Dnscrypt != nil {
upstreamAddr = serverInfo.Relay.Dnscrypt.RelayTCPAddr
@ -518,7 +591,7 @@ func (proxy *Proxy) exchangeWithTCPServer(serverInfo *ServerInfo, sharedKey *[32
var pc net.Conn
proxyDialer := proxy.xTransport.proxyDialer
if proxyDialer == nil {
pc, err = net.DialTCP("tcp", nil, upstreamAddr)
pc, err = net.DialTimeout("tcp", upstreamAddr.String(), serverInfo.Timeout)
} else {
pc, err = (*proxyDialer).Dial("tcp", upstreamAddr.String())
}
@ -561,15 +634,25 @@ func (proxy *Proxy) clientsCountInc() bool {
func (proxy *Proxy) clientsCountDec() {
for {
if count := atomic.LoadUint32(&proxy.clientsCount); count == 0 || atomic.CompareAndSwapUint32(&proxy.clientsCount, count, count-1) {
if count := atomic.LoadUint32(&proxy.clientsCount); count == 0 ||
atomic.CompareAndSwapUint32(&proxy.clientsCount, count, count-1) {
break
}
}
}
func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string, query []byte, clientAddr *net.Addr, clientPc net.Conn, start time.Time) (response []byte) {
func (proxy *Proxy) processIncomingQuery(
clientProto string,
serverProto string,
query []byte,
clientAddr *net.Addr,
clientPc net.Conn,
start time.Time,
onlyCached bool,
) []byte {
var response []byte
if len(query) < MinDNSPacketSize {
return
return response
}
pluginsState := NewPluginsState(proxy, clientProto, clientAddr, serverProto, start)
serverName := "-"
@ -581,12 +664,12 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
}
query, _ = pluginsState.ApplyQueryPlugins(&proxy.pluginsGlobals, query, needsEDNS0Padding)
if len(query) < MinDNSPacketSize || len(query) > MaxDNSPacketSize {
return
return response
}
if pluginsState.action == PluginsActionDrop {
pluginsState.returnCode = PluginsReturnCodeDrop
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
return
return response
}
var err error
if pluginsState.synthResponse != nil {
@ -594,9 +677,15 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
if err != nil {
pluginsState.returnCode = PluginsReturnCodeParseError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
return
return response
}
}
if onlyCached {
if len(response) == 0 {
return response
}
serverInfo = nil
}
if len(response) == 0 && serverInfo != nil {
var ttl *uint32
pluginsState.serverName = serverName
@ -610,7 +699,7 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
if err != nil {
pluginsState.returnCode = PluginsReturnCodeParseError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
return
return response
}
serverInfo.noticeBegin(proxy)
if serverProto == "udp" {
@ -628,7 +717,7 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
if err != nil {
pluginsState.returnCode = PluginsReturnCodeParseError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
return
return response
}
response, err = proxy.exchangeWithTCPServer(serverInfo, sharedKey, encryptedQuery, clientNonce)
}
@ -649,25 +738,26 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
}
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
serverInfo.noticeFailure(proxy)
return
return response
}
} else if serverInfo.Proto == stamps.StampProtoTypeDoH {
tid := TransactionID(query)
SetTransactionID(query, 0)
serverInfo.noticeBegin(proxy)
serverResponse, tls, _, err := proxy.xTransport.DoHQuery(serverInfo.useGet, serverInfo.URL, query, proxy.timeout)
serverResponse, _, tls, _, err := proxy.xTransport.DoHQuery(serverInfo.useGet, serverInfo.URL, query, proxy.timeout)
SetTransactionID(query, tid)
if err == nil || tls == nil || !tls.HandshakeComplete {
response = nil
} else if stale, ok := pluginsState.sessionData["stale"]; ok {
dlog.Debug("Serving stale response")
response, err = (stale.(*dns.Msg)).Pack()
if err != nil || tls == nil || !tls.HandshakeComplete {
if stale, ok := pluginsState.sessionData["stale"]; ok {
dlog.Debug("Serving stale response")
response, err = (stale.(*dns.Msg)).Pack()
}
}
if err != nil {
pluginsState.returnCode = PluginsReturnCodeNetworkError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
serverInfo.noticeFailure(proxy)
return
return response
}
if response == nil {
response = serverResponse
@ -675,6 +765,58 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
if len(response) >= MinDNSPacketSize {
SetTransactionID(response, tid)
}
} else if serverInfo.Proto == stamps.StampProtoTypeODoHTarget {
tid := TransactionID(query)
if len(serverInfo.odohTargetConfigs) == 0 {
return response
}
target := serverInfo.odohTargetConfigs[rand.Intn(len(serverInfo.odohTargetConfigs))]
odohQuery, err := target.encryptQuery(query)
if err != nil {
dlog.Errorf("Failed to encrypt query for [%v]", serverName)
response = nil
} else {
targetURL := serverInfo.URL
if serverInfo.Relay != nil && serverInfo.Relay.ODoH != nil {
targetURL = serverInfo.Relay.ODoH.URL
}
responseBody, responseCode, _, _, err := proxy.xTransport.ObliviousDoHQuery(serverInfo.useGet, targetURL, odohQuery.odohMessage, proxy.timeout)
if err == nil && len(responseBody) > 0 && responseCode == 200 {
response, err = odohQuery.decryptResponse(responseBody)
if err != nil {
dlog.Warnf("Failed to decrypt response from [%v]", serverName)
response = nil
}
} else if responseCode == 401 || (responseCode == 200 && len(responseBody) == 0) {
if responseCode == 200 {
dlog.Warnf("ODoH relay for [%v] is buggy and returns a 200 status code instead of 401 after a key update", serverInfo.Name)
}
dlog.Infof("Forcing key update for [%v]", serverInfo.Name)
for _, registeredServer := range proxy.serversInfo.registeredServers {
if registeredServer.name == serverInfo.Name {
if err = proxy.serversInfo.refreshServer(proxy, registeredServer.name, registeredServer.stamp); err != nil {
// Failed to refresh the proxy server information.
dlog.Noticef("Key update failed for [%v]", serverName)
serverInfo.noticeFailure(proxy)
clocksmith.Sleep(10 * time.Second)
}
break
}
}
response = nil
} else {
dlog.Warnf("Failed to receive successful response from [%v]", serverName)
}
}
if len(response) >= MinDNSPacketSize {
SetTransactionID(response, tid)
} else if response == nil {
pluginsState.returnCode = PluginsReturnCodeNetworkError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
serverInfo.noticeFailure(proxy)
return response
}
} else {
dlog.Fatal("Unsupported protocol")
}
@ -682,33 +824,33 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
pluginsState.returnCode = PluginsReturnCodeParseError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
serverInfo.noticeFailure(proxy)
return
return response
}
response, err = pluginsState.ApplyResponsePlugins(&proxy.pluginsGlobals, response, ttl)
if err != nil {
pluginsState.returnCode = PluginsReturnCodeParseError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
serverInfo.noticeFailure(proxy)
return
return response
}
if pluginsState.action == PluginsActionDrop {
pluginsState.returnCode = PluginsReturnCodeDrop
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
return
return response
}
if pluginsState.synthResponse != nil {
response, err = pluginsState.synthResponse.PackBuffer(response)
if err != nil {
pluginsState.returnCode = PluginsReturnCodeParseError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
return
return response
}
}
if rcode := Rcode(response); rcode == dns.RcodeServerFailure { // SERVFAIL
if pluginsState.dnssec {
dlog.Debug("A response had an invalid DNSSEC signature")
} else {
dlog.Infof("Server [%v] returned temporary error code SERVFAIL -- Invalid DNSSEC signature received or server may be experiencing connectivity issues", serverInfo.Name)
dlog.Infof("A response with status code 2 was received - this is usually a temporary, remote issue with the configuration of the domain name")
serverInfo.noticeFailure(proxy)
}
} else {
@ -725,7 +867,7 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
if serverInfo != nil {
serverInfo.noticeFailure(proxy)
}
return
return response
}
if clientProto == "udp" {
if len(response) > pluginsState.maxUnencryptedUDPSafePayloadSize {
@ -733,7 +875,7 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
if err != nil {
pluginsState.returnCode = PluginsReturnCodeParseError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)
return
return response
}
}
clientPc.(net.PacketConn).WriteTo(response, *clientAddr)
@ -750,7 +892,7 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
if serverInfo != nil {
serverInfo.noticeFailure(proxy)
}
return
return response
}
if clientPc != nil {
clientPc.Write(response)

View File

@ -11,10 +11,12 @@ import (
"github.com/miekg/dns"
)
const myResolverHost string = "resolver.dnscrypt.info."
const nonexistentName string = "nonexistent-zone.dnscrypt-test."
const (
myResolverHost string = "resolver.dnscrypt.info."
nonexistentName string = "nonexistent-zone.dnscrypt-test."
)
func resolveQuery(server string, qName string, qType uint16) (*dns.Msg, error) {
func resolveQuery(server string, qName string, qType uint16, sendClientSubnet bool) (*dns.Msg, error) {
client := new(dns.Client)
client.ReadTimeout = 2 * time.Second
msg := &dns.Msg{
@ -30,9 +32,27 @@ func resolveQuery(server string, qName string, qType uint16) (*dns.Msg, error) {
Rrtype: dns.TypeOPT,
},
}
if sendClientSubnet {
subnet := net.IPNet{IP: net.IPv4(93, 184, 216, 0), Mask: net.CIDRMask(24, 32)}
prr := dns.EDNS0_SUBNET{}
prr.Code = dns.EDNS0SUBNET
bits, totalSize := subnet.Mask.Size()
if totalSize == 32 {
prr.Family = 1
} else if totalSize == 128 { // if we want to test with IPv6
prr.Family = 2
}
prr.SourceNetmask = uint8(bits)
prr.SourceScope = 0
prr.Address = subnet.IP
options.Option = append(options.Option, &prr)
}
msg.Extra = append(msg.Extra, options)
options.SetDo()
options.SetUDPSize(uint16(MaxDNSPacketSize))
msg.Question[0] = dns.Question{Name: qName, Qtype: qType, Qclass: dns.ClassINET}
msg.Id = dns.Id()
for i := 0; i < 3; i++ {
@ -69,9 +89,10 @@ func Resolve(server string, name string, singleResolver bool) {
name = dns.Fqdn(name)
cname := name
var clientSubnet string
for once := true; once; once = false {
response, err := resolveQuery(server, myResolverHost, dns.TypeA)
response, err := resolveQuery(server, myResolverHost, dns.TypeTXT, true)
if err != nil {
fmt.Printf("Unable to resolve: [%s]\n", err)
os.Exit(1)
@ -79,17 +100,22 @@ func Resolve(server string, name string, singleResolver bool) {
fmt.Printf("Resolver : ")
res := make([]string, 0)
for _, answer := range response.Answer {
if answer.Header().Class != dns.ClassINET {
if answer.Header().Class != dns.ClassINET || answer.Header().Rrtype != dns.TypeTXT {
continue
}
var ip string
if answer.Header().Rrtype == dns.TypeA {
ip = answer.(*dns.A).A.String()
} else if answer.Header().Rrtype == dns.TypeAAAA {
ip = answer.(*dns.AAAA).AAAA.String()
for _, txt := range answer.(*dns.TXT).Txt {
if strings.HasPrefix(txt, "Resolver IP: ") {
ip = strings.TrimPrefix(txt, "Resolver IP: ")
} else if strings.HasPrefix(txt, "EDNS0 client subnet: ") {
clientSubnet = strings.TrimPrefix(txt, "EDNS0 client subnet: ")
}
}
if ip == "" {
continue
}
if rev, err := dns.ReverseAddr(ip); err == nil {
response, err = resolveQuery(server, rev, dns.TypePTR)
response, err = resolveQuery(server, rev, dns.TypePTR, false)
if err != nil {
break
}
@ -113,7 +139,7 @@ func Resolve(server string, name string, singleResolver bool) {
if singleResolver {
for once := true; once; once = false {
fmt.Printf("Lying : ")
response, err := resolveQuery(server, nonexistentName, dns.TypeA)
response, err := resolveQuery(server, nonexistentName, dns.TypeA, false)
if err != nil {
break
}
@ -133,6 +159,13 @@ func Resolve(server string, name string, singleResolver bool) {
fmt.Println("no, the resolver doesn't support DNSSEC")
}
}
fmt.Printf("ECS : ")
if clientSubnet != "" {
fmt.Println("client network address is sent to authoritative servers")
} else {
fmt.Println("ignored or selective")
}
}
}
@ -142,7 +175,7 @@ cname:
for once := true; once; once = false {
fmt.Printf("Canonical name: ")
for i := 0; i < 100; i++ {
response, err := resolveQuery(server, cname, dns.TypeCNAME)
response, err := resolveQuery(server, cname, dns.TypeCNAME, false)
if err != nil {
break cname
}
@ -166,7 +199,7 @@ cname:
for once := true; once; once = false {
fmt.Printf("IPv4 addresses: ")
response, err := resolveQuery(server, cname, dns.TypeA)
response, err := resolveQuery(server, cname, dns.TypeA, false)
if err != nil {
break
}
@ -186,7 +219,7 @@ cname:
for once := true; once; once = false {
fmt.Printf("IPv6 addresses: ")
response, err := resolveQuery(server, cname, dns.TypeAAAA)
response, err := resolveQuery(server, cname, dns.TypeAAAA, false)
if err != nil {
break
}
@ -208,7 +241,7 @@ cname:
for once := true; once; once = false {
fmt.Printf("Name servers : ")
response, err := resolveQuery(server, cname, dns.TypeNS)
response, err := resolveQuery(server, cname, dns.TypeNS, false)
if err != nil {
break
}
@ -238,7 +271,7 @@ cname:
for once := true; once; once = false {
fmt.Printf("Mail servers : ")
response, err := resolveQuery(server, cname, dns.TypeMX)
response, err := resolveQuery(server, cname, dns.TypeMX, false)
if err != nil {
break
}
@ -262,7 +295,7 @@ cname:
for once := true; once; once = false {
fmt.Printf("HTTPS alias : ")
response, err := resolveQuery(server, cname, dns.TypeHTTPS)
response, err := resolveQuery(server, cname, dns.TypeHTTPS, false)
if err != nil {
break
}
@ -308,7 +341,7 @@ cname:
for once := true; once; once = false {
fmt.Printf("Host info : ")
response, err := resolveQuery(server, cname, dns.TypeHINFO)
response, err := resolveQuery(server, cname, dns.TypeHINFO, false)
if err != nil {
break
}
@ -328,7 +361,7 @@ cname:
for once := true; once; once = false {
fmt.Printf("TXT records : ")
response, err := resolveQuery(server, cname, dns.TypeTXT)
response, err := resolveQuery(server, cname, dns.TypeTXT, false)
if err != nil {
break
}

View File

@ -17,6 +17,7 @@ import (
"github.com/VividCortex/ewma"
"github.com/jedisct1/dlog"
clocksmith "github.com/jedisct1/go-clocksmith"
stamps "github.com/jedisct1/go-dnsstamps"
"github.com/miekg/dns"
"golang.org/x/crypto/ed25519"
@ -61,10 +62,12 @@ type ServerInfo struct {
knownBugs ServerBugs
Proto stamps.StampProtoType
useGet bool
odohTargetConfigs []ODoHTargetConfig
}
type LBStrategy interface {
getCandidate(serversCount int) int
getActiveCount(serversCount int) int
}
type LBStrategyP2 struct{}
@ -73,30 +76,50 @@ func (LBStrategyP2) getCandidate(serversCount int) int {
return rand.Intn(Min(serversCount, 2))
}
func (LBStrategyP2) getActiveCount(serversCount int) int {
return Min(serversCount, 2)
}
type LBStrategyPN struct{ n int }
func (s LBStrategyPN) getCandidate(serversCount int) int {
return rand.Intn(Min(serversCount, s.n))
}
func (s LBStrategyPN) getActiveCount(serversCount int) int {
return Min(serversCount, s.n)
}
type LBStrategyPH struct{}
func (LBStrategyPH) getCandidate(serversCount int) int {
return rand.Intn(Max(Min(serversCount, 2), serversCount/2))
}
func (LBStrategyPH) getActiveCount(serversCount int) int {
return Max(Min(serversCount, 2), serversCount/2)
}
type LBStrategyFirst struct{}
func (LBStrategyFirst) getCandidate(int) int {
return 0
}
func (LBStrategyFirst) getActiveCount(int) int {
return 1
}
type LBStrategyRandom struct{}
func (LBStrategyRandom) getCandidate(serversCount int) int {
return rand.Intn(serversCount)
}
func (LBStrategyRandom) getActiveCount(serversCount int) int {
return serversCount
}
var DefaultLBStrategy = LBStrategyP2{}
type DNSCryptRelay struct {
@ -104,7 +127,9 @@ type DNSCryptRelay struct {
RelayTCPAddr *net.TCPAddr
}
type ODoHRelay struct{}
type ODoHRelay struct {
URL *url.URL
}
type Relay struct {
Proto stamps.StampProtoType
@ -116,12 +141,18 @@ type ServersInfo struct {
sync.RWMutex
inner []*ServerInfo
registeredServers []RegisteredServer
registeredRelays []RegisteredServer
lbStrategy LBStrategy
lbEstimator bool
}
func NewServersInfo() ServersInfo {
return ServersInfo{lbStrategy: DefaultLBStrategy, lbEstimator: true, registeredServers: make([]RegisteredServer, 0)}
return ServersInfo{
lbStrategy: DefaultLBStrategy,
lbEstimator: true,
registeredServers: make([]RegisteredServer, 0),
registeredRelays: make([]RegisteredServer, 0),
}
}
func (serversInfo *ServersInfo) registerServer(name string, stamp stamps.ServerStamp) {
@ -137,6 +168,19 @@ func (serversInfo *ServersInfo) registerServer(name string, stamp stamps.ServerS
serversInfo.registeredServers = append(serversInfo.registeredServers, newRegisteredServer)
}
func (serversInfo *ServersInfo) registerRelay(name string, stamp stamps.ServerStamp) {
newRegisteredServer := RegisteredServer{name: name, stamp: stamp}
serversInfo.Lock()
defer serversInfo.Unlock()
for i, oldRegisteredServer := range serversInfo.registeredRelays {
if oldRegisteredServer.name == name {
serversInfo.registeredRelays[i] = newRegisteredServer
return
}
}
serversInfo.registeredRelays = append(serversInfo.registeredRelays, newRegisteredServer)
}
func (serversInfo *ServersInfo) refreshServer(proxy *Proxy, name string, stamp stamps.ServerStamp) error {
serversInfo.RLock()
isNew := true
@ -179,15 +223,35 @@ func (serversInfo *ServersInfo) refreshServer(proxy *Proxy, name string, stamp s
func (serversInfo *ServersInfo) refresh(proxy *Proxy) (int, error) {
dlog.Debug("Refreshing certificates")
serversInfo.RLock()
registeredServers := serversInfo.registeredServers
// Appending registeredServers slice from sources may allocate new memory.
serversCount := len(serversInfo.registeredServers)
registeredServers := make([]RegisteredServer, serversCount)
copy(registeredServers, serversInfo.registeredServers)
serversInfo.RUnlock()
countChannel := make(chan struct{}, proxy.certRefreshConcurrency)
errorChannel := make(chan error, serversCount)
for i := range registeredServers {
countChannel <- struct{}{}
go func(registeredServer *RegisteredServer) {
err := serversInfo.refreshServer(proxy, registeredServer.name, registeredServer.stamp)
if err == nil {
proxy.xTransport.internalResolverReady = true
}
errorChannel <- err
<-countChannel
}(&registeredServers[i])
}
liveServers := 0
var err error
for _, registeredServer := range registeredServers {
if err = serversInfo.refreshServer(proxy, registeredServer.name, registeredServer.stamp); err == nil {
for i := 0; i < serversCount; i++ {
err = <-errorChannel
if err == nil {
liveServers++
}
}
if liveServers > 0 {
err = nil
}
serversInfo.Lock()
sort.SliceStable(serversInfo.inner, func(i, j int) bool {
return serversInfo.inner[i].initialRtt < serversInfo.inner[j].initialRtt
@ -207,31 +271,44 @@ func (serversInfo *ServersInfo) refresh(proxy *Proxy) (int, error) {
return liveServers, err
}
func (serversInfo *ServersInfo) estimatorUpdate() {
func (serversInfo *ServersInfo) estimatorUpdate(currentActive int) {
// serversInfo.RWMutex is assumed to be Locked
candidate := rand.Intn(len(serversInfo.inner))
if candidate == 0 {
serversCount := len(serversInfo.inner)
activeCount := serversInfo.lbStrategy.getActiveCount(serversCount)
if activeCount == serversCount {
return
}
candidateRtt, currentBestRtt := serversInfo.inner[candidate].rtt.Value(), serversInfo.inner[0].rtt.Value()
if currentBestRtt < 0 {
currentBestRtt = candidateRtt
serversInfo.inner[0].rtt.Set(currentBestRtt)
candidate := rand.Intn(serversCount-activeCount) + activeCount
candidateRtt, currentActiveRtt := serversInfo.inner[candidate].rtt.Value(), serversInfo.inner[currentActive].rtt.Value()
if currentActiveRtt < 0 {
currentActiveRtt = candidateRtt
serversInfo.inner[currentActive].rtt.Set(currentActiveRtt)
return
}
partialSort := false
if candidateRtt < currentBestRtt {
serversInfo.inner[candidate], serversInfo.inner[0] = serversInfo.inner[0], serversInfo.inner[candidate]
if candidateRtt < currentActiveRtt {
serversInfo.inner[candidate], serversInfo.inner[currentActive] = serversInfo.inner[currentActive], serversInfo.inner[candidate]
dlog.Debugf(
"New preferred candidate: %s (RTT: %d vs previous: %d)",
serversInfo.inner[currentActive].Name,
int(candidateRtt),
int(currentActiveRtt),
)
partialSort = true
dlog.Debugf("New preferred candidate: %v (rtt: %d vs previous: %d)", serversInfo.inner[0].Name, int(candidateRtt), int(currentBestRtt))
} else if candidateRtt > 0 && candidateRtt >= currentBestRtt*4.0 {
} else if candidateRtt > 0 && candidateRtt >= (serversInfo.inner[0].rtt.Value()+serversInfo.inner[activeCount-1].rtt.Value())/2.0*4.0 {
if time.Since(serversInfo.inner[candidate].lastActionTS) > time.Duration(1*time.Minute) {
serversInfo.inner[candidate].rtt.Add(MinF(MaxF(candidateRtt/2.0, currentBestRtt*2.0), candidateRtt))
dlog.Debugf("Giving a new chance to candidate [%s], lowering its RTT from %d to %d (best: %d)", serversInfo.inner[candidate].Name, int(candidateRtt), int(serversInfo.inner[candidate].rtt.Value()), int(currentBestRtt))
serversInfo.inner[candidate].rtt.Add(candidateRtt / 2.0)
dlog.Debugf(
"Giving a new chance to candidate [%s], lowering its RTT from %d to %d (best: %d)",
serversInfo.inner[candidate].Name,
int(candidateRtt),
int(serversInfo.inner[candidate].rtt.Value()),
int(serversInfo.inner[0].rtt.Value()),
)
partialSort = true
}
}
if partialSort {
serversCount := len(serversInfo.inner)
for i := 1; i < serversCount; i++ {
if serversInfo.inner[i-1].rtt.Value() > serversInfo.inner[i].rtt.Value() {
serversInfo.inner[i-1], serversInfo.inner[i] = serversInfo.inner[i], serversInfo.inner[i-1]
@ -247,12 +324,12 @@ func (serversInfo *ServersInfo) getOne() *ServerInfo {
serversInfo.Unlock()
return nil
}
if serversInfo.lbEstimator {
serversInfo.estimatorUpdate()
}
candidate := serversInfo.lbStrategy.getCandidate(serversCount)
if serversInfo.lbEstimator {
serversInfo.estimatorUpdate(candidate)
}
serverInfo := serversInfo.inner[candidate]
dlog.Debugf("Using candidate [%s] RTT: %d", (*serverInfo).Name, int((*serverInfo).rtt.Value()))
dlog.Debugf("Using candidate [%s] RTT: %d", serverInfo.Name, int(serverInfo.rtt.Value()))
serversInfo.Unlock()
return serverInfo
@ -263,6 +340,8 @@ func fetchServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew
return fetchDNSCryptServerInfo(proxy, name, stamp, isNew)
} else if stamp.Proto == stamps.StampProtoTypeDoH {
return fetchDoHServerInfo(proxy, name, stamp, isNew)
} else if stamp.Proto == stamps.StampProtoTypeODoHTarget {
return fetchODoHTargetInfo(proxy, name, stamp, isNew)
}
return ServerInfo{}, fmt.Errorf("Unsupported protocol for [%s]: [%s]", name, stamp.Proto.String())
}
@ -277,21 +356,41 @@ func findFarthestRoute(proxy *Proxy, name string, relayStamps []stamps.ServerSta
}
}
if serverIdx < 0 {
proxy.serversInfo.RUnlock()
return nil
}
server := proxy.serversInfo.registeredServers[serverIdx]
proxy.serversInfo.RUnlock()
// Fall back to random relays until the logic is implemented for non-DNSCrypt relays
if server.stamp.Proto == stamps.StampProtoTypeODoHTarget {
candidates := make([]int, 0)
for relayIdx, relayStamp := range relayStamps {
if relayStamp.Proto != stamps.StampProtoTypeODoHRelay {
continue
}
candidates = append(candidates, relayIdx)
}
return &relayStamps[candidates[rand.Intn(len(candidates))]]
} else if server.stamp.Proto != stamps.StampProtoTypeDNSCrypt {
return nil
}
// Anonymized DNSCrypt relays
serverAddrStr, _ := ExtractHostAndPort(server.stamp.ServerAddrStr, 443)
serverAddr := net.ParseIP(serverAddrStr)
if serverAddr == nil {
return nil
}
if len(proxy.registeredRelays) == 0 {
if len(proxy.serversInfo.registeredRelays) == 0 {
return nil
}
bestRelayIdxs := make([]int, 0)
bestRelaySamePrefixBits := 128
for relayIdx, relayStamp := range relayStamps {
if relayStamp.Proto != stamps.StampProtoTypeDNSCryptRelay {
continue
}
relayAddrStr, _ := ExtractHostAndPort(relayStamp.ServerAddrStr, 443)
relayAddr := net.ParseIP(relayAddrStr)
if relayAddr == nil {
@ -321,7 +420,18 @@ func findFarthestRoute(proxy *Proxy, name string, relayStamps []stamps.ServerSta
return &relayStamps[bestRelayIdxs[rand.Intn(len(bestRelayIdxs))]]
}
func route(proxy *Proxy, name string) (*Relay, error) {
func relayProtoForServerProto(proto stamps.StampProtoType) (stamps.StampProtoType, error) {
switch proto {
case stamps.StampProtoTypeDNSCrypt:
return stamps.StampProtoTypeDNSCryptRelay, nil
case stamps.StampProtoTypeODoHTarget:
return stamps.StampProtoTypeODoHRelay, nil
default:
return 0, errors.New("protocol cannot be anonymized")
}
}
func route(proxy *Proxy, name string, serverProto stamps.StampProtoType) (*Relay, error) {
routes := proxy.routes
if routes == nil {
return nil, nil
@ -332,30 +442,40 @@ func route(proxy *Proxy, name string) (*Relay, error) {
wildcard = true
relayNames, ok = (*routes)["*"]
}
if !ok {
if !ok || len(relayNames) == 0 {
return nil, nil
}
relayProto, err := relayProtoForServerProto(serverProto)
if err != nil {
dlog.Errorf("Server [%v]'s protocol doesn't support anonymization", name)
return nil, nil
}
relayStamps := make([]stamps.ServerStamp, 0)
relayStampToName := make(map[string]string)
for _, relayName := range relayNames {
if relayStamp, err := stamps.NewServerStampFromString(relayName); err == nil {
relayStamps = append(relayStamps, relayStamp)
} else if relayName == "*" {
for _, registeredServer := range proxy.registeredRelays {
relayStamps = append(relayStamps, registeredServer.stamp)
if relayStamp.Proto == relayProto {
relayStamps = append(relayStamps, relayStamp)
relayStampToName[relayStamp.String()] = relayName
}
} else if relayName == "*" {
proxy.serversInfo.RLock()
for _, registeredServer := range proxy.serversInfo.registeredRelays {
if registeredServer.stamp.Proto == relayProto {
relayStamps = append(relayStamps, registeredServer.stamp)
relayStampToName[registeredServer.stamp.String()] = registeredServer.name
}
}
proxy.serversInfo.RUnlock()
wildcard = true
break
} else {
for _, registeredServer := range proxy.registeredRelays {
if registeredServer.name == relayName {
relayStamps = append(relayStamps, registeredServer.stamp)
break
}
}
proxy.serversInfo.RLock()
for _, registeredServer := range proxy.serversInfo.registeredServers {
if registeredServer.name == relayName {
for _, registeredServer := range proxy.serversInfo.registeredRelays {
if registeredServer.name == relayName && registeredServer.stamp.Proto == relayProto {
relayStamps = append(relayStamps, registeredServer.stamp)
relayStampToName[registeredServer.stamp.String()] = relayName
break
}
}
@ -363,7 +483,8 @@ func route(proxy *Proxy, name string) (*Relay, error) {
}
}
if len(relayStamps) == 0 {
return nil, fmt.Errorf("Empty relay set for [%v]", name)
err := fmt.Errorf("Non-existent relay set for server [%v]", name)
return nil, err
}
var relayCandidateStamp *stamps.ServerStamp
if !wildcard || len(relayStamps) == 1 {
@ -374,13 +495,7 @@ func route(proxy *Proxy, name string) (*Relay, error) {
if relayCandidateStamp == nil {
return nil, fmt.Errorf("No valid relay for server [%v]", name)
}
relayName := relayCandidateStamp.ServerAddrStr
for _, registeredServer := range proxy.registeredRelays {
if registeredServer.stamp.ServerAddrStr == relayCandidateStamp.ServerAddrStr {
relayName = registeredServer.name
break
}
}
relayName := relayStampToName[relayCandidateStamp.String()]
switch relayCandidateStamp.Proto {
case stamps.StampProtoTypeDNSCrypt, stamps.StampProtoTypeDNSCryptRelay:
relayUDPAddr, err := net.ResolveUDPAddr("udp", relayCandidateStamp.ServerAddrStr)
@ -392,16 +507,51 @@ func route(proxy *Proxy, name string) (*Relay, error) {
return nil, err
}
dlog.Noticef("Anonymizing queries for [%v] via [%v]", name, relayName)
return &Relay{Proto: stamps.StampProtoTypeDNSCryptRelay, Dnscrypt: &DNSCryptRelay{RelayUDPAddr: relayUDPAddr, RelayTCPAddr: relayTCPAddr}}, nil
return &Relay{
Proto: stamps.StampProtoTypeDNSCryptRelay,
Dnscrypt: &DNSCryptRelay{RelayUDPAddr: relayUDPAddr, RelayTCPAddr: relayTCPAddr},
}, nil
case stamps.StampProtoTypeODoHRelay:
return &Relay{Proto: stamps.StampProtoTypeODoHRelay, ODoH: &ODoHRelay{}}, nil
relayBaseURL, err := url.Parse(
"https://" + url.PathEscape(relayCandidateStamp.ProviderName) + relayCandidateStamp.Path,
)
if err != nil {
return nil, err
}
var relayURLforTarget *url.URL
for _, server := range proxy.registeredServers {
if server.name != name || server.stamp.Proto != stamps.StampProtoTypeODoHTarget {
continue
}
qs := relayBaseURL.Query()
qs.Add("targethost", server.stamp.ProviderName)
qs.Add("targetpath", server.stamp.Path)
tmp := *relayBaseURL
tmp.RawQuery = qs.Encode()
relayURLforTarget = &tmp
break
}
if relayURLforTarget == nil {
return nil, fmt.Errorf("Relay [%v] not found", relayName)
}
if len(relayCandidateStamp.ServerAddrStr) > 0 {
ipOnly, _ := ExtractHostAndPort(relayCandidateStamp.ServerAddrStr, -1)
if ip := ParseIP(ipOnly); ip != nil {
host, _ := ExtractHostAndPort(relayCandidateStamp.ProviderName, -1)
proxy.xTransport.saveCachedIP(host, ip, -1*time.Second)
}
}
dlog.Noticef("Anonymizing queries for [%v] via [%v]", name, relayName)
return &Relay{Proto: stamps.StampProtoTypeODoHRelay, ODoH: &ODoHRelay{
URL: relayURLforTarget,
}}, nil
}
return nil, fmt.Errorf("Invalid relay set for server [%v]", name)
}
func fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew bool) (ServerInfo, error) {
if len(stamp.ServerPk) != ed25519.PublicKeySize {
serverPk, err := hex.DecodeString(strings.Replace(string(stamp.ServerPk), ":", "", -1))
serverPk, err := hex.DecodeString(strings.ReplaceAll(string(stamp.ServerPk), ":", ""))
if err != nil || len(serverPk) != ed25519.PublicKeySize {
dlog.Fatalf("Unsupported public key for [%s]: [%s]", name, stamp.ServerPk)
}
@ -416,7 +566,7 @@ func fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp
break
}
}
relay, err := route(proxy, name)
relay, err := route(proxy, name, stamp.Proto)
if err != nil {
return ServerInfo{}, err
}
@ -424,7 +574,17 @@ func fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp
if relay != nil {
dnscryptRelay = relay.Dnscrypt
}
certInfo, rtt, fragmentsBlocked, err := FetchCurrentDNSCryptCert(proxy, &name, proxy.mainProto, stamp.ServerPk, stamp.ServerAddrStr, stamp.ProviderName, isNew, dnscryptRelay, knownBugs)
certInfo, rtt, fragmentsBlocked, err := FetchCurrentDNSCryptCert(
proxy,
&name,
proxy.mainProto,
stamp.ServerPk,
stamp.ServerAddrStr,
stamp.ProviderName,
isNew,
dnscryptRelay,
knownBugs,
)
if !knownBugs.fragmentsBlocked && fragmentsBlocked {
dlog.Debugf("[%v] drops fragmented queries", name)
knownBugs.fragmentsBlocked = true
@ -472,7 +632,7 @@ func dohTestPacket(msgID uint16) []byte {
msg.SetEdns0(uint16(MaxDNSPacketSize), false)
ext := new(dns.EDNS0_PADDING)
ext.Padding = make([]byte, 16)
crypto_rand.Read(ext.Padding)
_, _ = crypto_rand.Read(ext.Padding)
edns0 := msg.IsEdns0()
edns0.Option = append(edns0.Option, ext)
body, err := msg.Pack()
@ -495,7 +655,7 @@ func dohNXTestPacket(msgID uint16) []byte {
msg.SetEdns0(uint16(MaxDNSPacketSize), false)
ext := new(dns.EDNS0_PADDING)
ext.Padding = make([]byte, 16)
crypto_rand.Read(ext.Padding)
_, _ = crypto_rand.Read(ext.Padding)
edns0 := msg.IsEdns0()
edns0.Option = append(edns0.Option, ext)
body, err := msg.Pack()
@ -513,7 +673,8 @@ func fetchDoHServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isN
if len(stamp.ServerAddrStr) > 0 {
ipOnly, _ := ExtractHostAndPort(stamp.ServerAddrStr, -1)
if ip := ParseIP(ipOnly); ip != nil {
proxy.xTransport.saveCachedIP(stamp.ProviderName, ip, -1*time.Second)
host, _ := ExtractHostAndPort(stamp.ProviderName, -1)
proxy.xTransport.saveCachedIP(host, ip, -1*time.Second)
}
}
url := &url.URL{
@ -522,25 +683,16 @@ func fetchDoHServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isN
Path: stamp.Path,
}
body := dohTestPacket(0xcafe)
dohClientCreds, ok := (*proxy.dohCreds)[name]
if !ok {
dohClientCreds, ok = (*proxy.dohCreds)["*"]
}
if ok {
dlog.Noticef("Enabling TLS authentication for [%s]", name)
proxy.xTransport.tlsClientCreds = dohClientCreds
proxy.xTransport.rebuildTransport()
}
useGet := false
if _, _, _, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout); err != nil {
if _, _, _, _, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout); err != nil {
useGet = true
if _, _, _, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout); err != nil {
if _, _, _, _, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout); err != nil {
return ServerInfo{}, err
}
dlog.Debugf("Server [%s] doesn't appear to support POST; falling back to GET requests", name)
}
body = dohNXTestPacket(0xcafe)
serverResponse, tls, rtt, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout)
serverResponse, _, tls, rtt, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout)
if err != nil {
dlog.Infof("[%s] [%s]: %v", name, url, err)
return ServerInfo{}, err
@ -561,7 +713,7 @@ func fetchDoHServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isN
protocol = "http/1.x"
}
if strings.HasPrefix(protocol, "http/1.") {
dlog.Warnf("[%s] does not support HTTP/2", name)
dlog.Warnf("[%s] does not support HTTP/2 nor HTTP/3", name)
}
dlog.Infof("[%s] TLS version: %x - Protocol: %v - Cipher suite: %v", name, tls.Version, protocol, tls.CipherSuite)
showCerts := proxy.showCerts
@ -591,9 +743,8 @@ func fetchDoHServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isN
dlog.Criticalf("[%s] Certificate hash [%x] not found", name, wantedHash)
return ServerInfo{}, fmt.Errorf("Certificate hash not found")
}
respBody := serverResponse
if len(respBody) < MinDNSPacketSize || len(respBody) > MaxDNSPacketSize ||
respBody[0] != 0xca || respBody[1] != 0xfe || respBody[4] != 0x00 || respBody[5] != 0x01 {
if len(serverResponse) < MinDNSPacketSize || len(serverResponse) > MaxDNSPacketSize ||
serverResponse[0] != 0xca || serverResponse[1] != 0xfe || serverResponse[4] != 0x00 || serverResponse[5] != 0x01 {
dlog.Info("Webserver returned an unexpected response")
return ServerInfo{}, errors.New("Webserver returned an unexpected response")
}
@ -614,6 +765,202 @@ func fetchDoHServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isN
}, nil
}
func fetchTargetConfigsFromWellKnown(proxy *Proxy, url *url.URL) ([]ODoHTargetConfig, error) {
bin, statusCode, _, _, err := proxy.xTransport.Get(url, "application/binary", 0)
if err != nil {
return nil, err
}
if statusCode < 200 || statusCode >= 300 {
return nil, fmt.Errorf("HTTP status code was %v", statusCode)
}
return parseODoHTargetConfigs(bin)
}
func _fetchODoHTargetInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew bool) (ServerInfo, error) {
configURL := &url.URL{Scheme: "https", Host: stamp.ProviderName, Path: "/.well-known/odohconfigs"}
odohTargetConfigs, err := fetchTargetConfigsFromWellKnown(proxy, configURL)
if err != nil {
dlog.Debug(configURL)
return ServerInfo{}, fmt.Errorf("[%s] didn't return an ODoH configuration - [%v]", name, err)
} else if len(odohTargetConfigs) == 0 {
dlog.Debug(configURL)
return ServerInfo{}, fmt.Errorf("[%s] has an empty ODoH configuration", name)
}
relay, err := route(proxy, name, stamp.Proto)
if err != nil {
return ServerInfo{}, err
}
if relay == nil {
dlog.Criticalf(
"No relay defined for [%v] - Configuring a relay is required for ODoH servers (see the `[anonymized_dns]` section)",
name,
)
return ServerInfo{}, errors.New("No ODoH relay")
} else {
if relay.ODoH == nil {
dlog.Criticalf("Wrong relay type defined for [%v] - ODoH servers require an ODoH relay", name)
return ServerInfo{}, errors.New("Wrong ODoH relay type")
}
}
dlog.Debugf("Pausing after ODoH configuration retrieval")
delay := time.Duration(rand.Intn(5*1000)) * time.Millisecond
clocksmith.Sleep(time.Duration(delay))
dlog.Debugf("Pausing done")
targetURL := &url.URL{
Scheme: "https",
Host: stamp.ProviderName,
Path: stamp.Path,
}
workingConfigs := make([]ODoHTargetConfig, 0)
rand.Shuffle(len(odohTargetConfigs), func(i, j int) {
odohTargetConfigs[i], odohTargetConfigs[j] = odohTargetConfigs[j], odohTargetConfigs[i]
})
for _, odohTargetConfig := range odohTargetConfigs {
url := relay.ODoH.URL
query := dohTestPacket(0xcafe)
odohQuery, err := odohTargetConfig.encryptQuery(query)
if err != nil {
continue
}
useGet := false
if _, _, _, _, err := proxy.xTransport.ObliviousDoHQuery(useGet, url, odohQuery.odohMessage, proxy.timeout); err != nil {
useGet = true
if _, _, _, _, err := proxy.xTransport.ObliviousDoHQuery(useGet, url, odohQuery.odohMessage, proxy.timeout); err != nil {
continue
}
dlog.Debugf("Server [%s] doesn't appear to support POST; falling back to GET requests", name)
}
query = dohNXTestPacket(0xcafe)
odohQuery, err = odohTargetConfig.encryptQuery(query)
if err != nil {
continue
}
responseBody, responseCode, tls, rtt, err := proxy.xTransport.ObliviousDoHQuery(
useGet,
url,
odohQuery.odohMessage,
proxy.timeout,
)
if err != nil {
continue
}
if responseCode == 401 {
return ServerInfo{}, fmt.Errorf("Configuration changed during a probe")
}
serverResponse, err := odohQuery.decryptResponse(responseBody)
if err != nil {
dlog.Warnf("Unable to decrypt response from [%v]: [%v]", name, err)
continue
}
workingConfigs = append(workingConfigs, odohTargetConfig)
msg := dns.Msg{}
if err := msg.Unpack(serverResponse); err != nil {
dlog.Warnf("[%s]: %v", name, err)
return ServerInfo{}, err
}
if msg.Rcode != dns.RcodeNameError {
dlog.Criticalf("[%s] may be a lying resolver", name)
}
protocol := "http"
tlsVersion := uint16(0)
tlsCipherSuite := uint16(0)
if tls != nil {
protocol = tls.NegotiatedProtocol
if len(protocol) == 0 {
protocol = "http/1.x"
} else {
tlsVersion = tls.Version
tlsCipherSuite = tls.CipherSuite
}
}
if strings.HasPrefix(protocol, "http/1.") {
dlog.Warnf("[%s] does not support HTTP/2", name)
}
dlog.Infof(
"[%s] TLS version: %x - Protocol: %v - Cipher suite: %v",
name,
tlsVersion,
protocol,
tlsCipherSuite,
)
showCerts := proxy.showCerts
found := false
var wantedHash [32]byte
if tls != nil {
for _, cert := range tls.PeerCertificates {
h := sha256.Sum256(cert.RawTBSCertificate)
if showCerts {
dlog.Noticef("Advertised relay cert: [%s] [%x]", cert.Subject, h)
} else {
dlog.Debugf("Advertised relay cert: [%s] [%x]", cert.Subject, h)
}
for _, hash := range stamp.Hashes {
if len(hash) == len(wantedHash) {
copy(wantedHash[:], hash)
if h == wantedHash {
found = true
break
}
}
}
if found {
break
}
}
if !found && len(stamp.Hashes) > 0 {
dlog.Criticalf("[%s] Certificate hash [%x] not found", name, wantedHash)
return ServerInfo{}, fmt.Errorf("Certificate hash not found")
}
}
if len(serverResponse) < MinDNSPacketSize || len(serverResponse) > MaxDNSPacketSize ||
serverResponse[0] != 0xca || serverResponse[1] != 0xfe || serverResponse[4] != 0x00 || serverResponse[5] != 0x01 {
dlog.Info("Webserver returned an unexpected response")
return ServerInfo{}, errors.New("Webserver returned an unexpected response")
}
xrtt := int(rtt.Nanoseconds() / 1000000)
if isNew {
dlog.Noticef("[%s] OK (ODoH) - rtt: %dms", name, xrtt)
} else {
dlog.Infof("[%s] OK (ODoH) - rtt: %dms", name, xrtt)
}
return ServerInfo{
Proto: stamps.StampProtoTypeODoHTarget,
Name: name,
Timeout: proxy.timeout,
URL: targetURL,
HostName: stamp.ProviderName,
initialRtt: xrtt,
useGet: useGet,
Relay: relay,
odohTargetConfigs: workingConfigs,
}, nil
}
return ServerInfo{}, fmt.Errorf("No valid network configuration for [%v]", name)
}
func fetchODoHTargetInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew bool) (ServerInfo, error) {
var err error
var serverInfo ServerInfo
for i := 0; i < 3; i += 1 {
serverInfo, err = _fetchODoHTargetInfo(proxy, name, stamp, isNew)
if err == nil {
break
}
dlog.Infof("Trying to fetch the [%v] configuration again", name)
}
return serverInfo, err
}
func (serverInfo *ServerInfo) noticeFailure(proxy *Proxy) {
proxy.serversInfo.Lock()
serverInfo.rtt.Add(float64(proxy.timeout.Nanoseconds() / 1000000))

View File

@ -1,3 +1,4 @@
//go:build android
// +build android
package main

View File

@ -1,3 +1,4 @@
//go:build !android
// +build !android
package main
@ -7,13 +8,15 @@ import (
clocksmith "github.com/jedisct1/go-clocksmith"
)
const SdNotifyStatus = "STATUS="
func ServiceManagerStartNotify() error {
daemon.SdNotify(false, "STATUS=Starting")
daemon.SdNotify(false, SdNotifyStatus+"Starting...")
return nil
}
func ServiceManagerReadyNotify() error {
daemon.SdNotify(false, "READY=1")
daemon.SdNotify(false, daemon.SdNotifyReady+"\n"+SdNotifyStatus+"Ready")
return systemDWatchdog()
}
@ -25,10 +28,9 @@ func systemDWatchdog() error {
refreshInterval := watchdogFailureDelay / 3
go func() {
for {
daemon.SdNotify(false, "WATCHDOG=1")
daemon.SdNotify(false, daemon.SdNotifyWatchdog)
clocksmith.Sleep(refreshInterval)
}
}()
return nil
}

View File

@ -1,3 +1,4 @@
//go:build !linux && !windows
// +build !linux,!windows
package main

View File

@ -12,7 +12,12 @@ func (proxy *Proxy) udpListenerConfig() (*net.ListenConfig, error) {
_ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_FREEBIND, 1)
_ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_DF, 0)
_ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TOS, 0x70)
_ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DONT)
_ = syscall.SetsockoptInt(
int(fd),
syscall.IPPROTO_IP,
syscall.IP_MTU_DISCOVER,
syscall.IP_PMTUDISC_DONT,
)
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVBUFFORCE, 4096)
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_SNDBUFFORCE, 4096)
})

View File

@ -1,3 +1,4 @@
//go:build !freebsd && !openbsd && !windows && !darwin && !linux
// +build !freebsd,!openbsd,!windows,!darwin,!linux
package main

View File

@ -3,14 +3,12 @@ package main
import (
"bytes"
"fmt"
"io/ioutil"
"math/rand"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"unicode"
"github.com/dchest/safefile"
@ -34,7 +32,7 @@ type Source struct {
name string
urls []*url.URL
format SourceFormat
in []byte
bin []byte
minisignKey *minisign.PublicKey
cacheFile string
cacheTTL, prefetchDelay time.Duration
@ -42,83 +40,84 @@ type Source struct {
prefix string
}
func (source *Source) checkSignature(bin, sig []byte) (err error) {
var signature minisign.Signature
if signature, err = minisign.DecodeSignature(string(sig)); err == nil {
// timeNow() is replaced by tests to provide a static value
var timeNow = time.Now
func (source *Source) checkSignature(bin, sig []byte) error {
signature, err := minisign.DecodeSignature(string(sig))
if err == nil {
_, err = source.minisignKey.Verify(bin, signature)
}
return err
}
// timeNow can be replaced by tests to provide a static value
var timeNow = time.Now
func (source *Source) fetchFromCache(now time.Time) (delay time.Duration, err error) {
func (source *Source) fetchFromCache(now time.Time) (time.Duration, error) {
var err error
var bin, sig []byte
if bin, err = ioutil.ReadFile(source.cacheFile); err != nil {
return
if bin, err = os.ReadFile(source.cacheFile); err != nil {
return 0, err
}
if sig, err = ioutil.ReadFile(source.cacheFile + ".minisig"); err != nil {
return
if sig, err = os.ReadFile(source.cacheFile + ".minisig"); err != nil {
return 0, err
}
if err = source.checkSignature(bin, sig); err != nil {
return
return 0, err
}
source.in = bin
source.bin = bin
var fi os.FileInfo
if fi, err = os.Stat(source.cacheFile); err != nil {
return
return 0, err
}
var ttl time.Duration = 0
if elapsed := now.Sub(fi.ModTime()); elapsed < source.cacheTTL {
delay = source.prefetchDelay - elapsed
dlog.Debugf("Source [%s] cache file [%s] is still fresh, next update: %v", source.name, source.cacheFile, delay)
ttl = source.prefetchDelay - elapsed
dlog.Debugf("Source [%s] cache file [%s] is still fresh, next update: %v", source.name, source.cacheFile, ttl)
} else {
dlog.Debugf("Source [%s] cache file [%s] needs to be refreshed", source.name, source.cacheFile)
}
return
return ttl, nil
}
func writeSource(f string, bin, sig []byte) (err error) {
func writeSource(f string, bin, sig []byte) error {
var err error
var fSrc, fSig *safefile.File
if fSrc, err = safefile.Create(f, 0644); err != nil {
return
if fSrc, err = safefile.Create(f, 0o644); err != nil {
return err
}
defer fSrc.Close()
if fSig, err = safefile.Create(f+".minisig", 0644); err != nil {
return
if fSig, err = safefile.Create(f+".minisig", 0o644); err != nil {
return err
}
defer fSig.Close()
if _, err = fSrc.Write(bin); err != nil {
return
return err
}
if _, err = fSig.Write(sig); err != nil {
return
return err
}
if err = fSrc.Commit(); err != nil {
return
return err
}
return fSig.Commit()
}
func (source *Source) writeToCache(bin, sig []byte, now time.Time) {
f := source.cacheFile
var writeErr error // an error writing cache isn't fatal
defer func() {
source.in = bin
if writeErr == nil {
return
}
if absPath, absErr := filepath.Abs(f); absErr == nil {
f = absPath
}
dlog.Warnf("%s: %s", f, writeErr)
}()
if !bytes.Equal(source.in, bin) {
if writeErr = writeSource(f, bin, sig); writeErr != nil {
return
func (source *Source) updateCache(bin, sig []byte, now time.Time) {
file := source.cacheFile
absPath := file
if resolved, err := filepath.Abs(file); err != nil {
absPath = resolved
}
if !bytes.Equal(source.bin, bin) {
if err := writeSource(file, bin, sig); err != nil {
dlog.Warnf("Couldn't write cache file [%s]: %s", absPath, err) // an error writing to the cache isn't fatal
}
}
writeErr = os.Chtimes(f, now, now)
if err := os.Chtimes(file, now, now); err != nil {
dlog.Warnf("Couldn't update cache file [%s]: %s", absPath, err)
}
source.bin = bin
}
func (source *Source) parseURLs(urls []string) {
@ -131,28 +130,32 @@ func (source *Source) parseURLs(urls []string) {
}
}
func fetchFromURL(xTransport *XTransport, u *url.URL) (bin []byte, err error) {
bin, _, _, err = xTransport.Get(u, "", DefaultTimeout)
func fetchFromURL(xTransport *XTransport, u *url.URL) ([]byte, error) {
bin, _, _, _, err := xTransport.GetWithCompression(u, "", DefaultTimeout)
return bin, err
}
func (source *Source) fetchWithCache(xTransport *XTransport, now time.Time) (delay time.Duration, err error) {
if delay, err = source.fetchFromCache(now); err != nil {
func (source *Source) fetchWithCache(xTransport *XTransport, now time.Time) (time.Duration, error) {
var err error
var ttl time.Duration
if ttl, err = source.fetchFromCache(now); err != nil {
if len(source.urls) == 0 {
dlog.Errorf("Source [%s] cache file [%s] not present and no valid URL", source.name, source.cacheFile)
return
return 0, err
}
dlog.Debugf("Source [%s] cache file [%s] not present", source.name, source.cacheFile)
}
if len(source.urls) > 0 {
defer func() {
source.refresh = now.Add(delay)
}()
if len(source.urls) == 0 {
return 0, err
}
if len(source.urls) == 0 || delay > 0 {
return
if ttl > 0 {
source.refresh = now.Add(ttl)
return 0, err
}
delay = MinimumPrefetchInterval
ttl = MinimumPrefetchInterval
source.refresh = now.Add(ttl)
var bin, sig []byte
for _, srcURL := range source.urls {
dlog.Infof("Source [%s] loading from URL [%s]", source.name, srcURL)
@ -167,25 +170,43 @@ func (source *Source) fetchWithCache(xTransport *XTransport, now time.Time) (del
dlog.Debugf("Source [%s] failed to download signature from URL [%s]", source.name, sigURL)
continue
}
if err = source.checkSignature(bin, sig); err == nil {
break // valid signature
} // above err check inverted to make use of implicit continue
dlog.Debugf("Source [%s] failed signature check using URL [%s]", source.name, srcURL)
if err = source.checkSignature(bin, sig); err != nil {
dlog.Debugf("Source [%s] failed signature check using URL [%s]", source.name, srcURL)
continue
}
break // valid signature
}
if err != nil {
return
return 0, err
}
source.writeToCache(bin, sig, now)
delay = source.prefetchDelay
return
source.updateCache(bin, sig, now)
ttl = source.prefetchDelay
source.refresh = now.Add(ttl)
return ttl, nil
}
// NewSource loads a new source using the given cacheFile and urls, ensuring it has a valid signature
func NewSource(name string, xTransport *XTransport, urls []string, minisignKeyStr string, cacheFile string, formatStr string, refreshDelay time.Duration, prefix string) (source *Source, err error) {
func NewSource(
name string,
xTransport *XTransport,
urls []string,
minisignKeyStr string,
cacheFile string,
formatStr string,
refreshDelay time.Duration,
prefix string,
) (*Source, error) {
if refreshDelay < DefaultPrefetchDelay {
refreshDelay = DefaultPrefetchDelay
}
source = &Source{name: name, urls: []*url.URL{}, cacheFile: cacheFile, cacheTTL: refreshDelay, prefetchDelay: DefaultPrefetchDelay, prefix: prefix}
source := &Source{
name: name,
urls: []*url.URL{},
cacheFile: cacheFile,
cacheTTL: refreshDelay,
prefetchDelay: DefaultPrefetchDelay,
prefix: prefix,
}
if formatStr == "v2" {
source.format = SourceFormatV2
} else {
@ -197,10 +218,11 @@ func NewSource(name string, xTransport *XTransport, urls []string, minisignKeySt
return source, err
}
source.parseURLs(urls)
if _, err = source.fetchWithCache(xTransport, timeNow()); err == nil {
_, err := source.fetchWithCache(xTransport, timeNow())
if err == nil {
dlog.Noticef("Source [%s] loaded", name)
}
return
return source, err
}
// PrefetchSources downloads latest versions of given sources, ensuring they have a valid signature before caching
@ -215,7 +237,7 @@ func PrefetchSources(xTransport *XTransport, sources []*Source) time.Duration {
if delay, err := source.fetchWithCache(xTransport, now); err != nil {
dlog.Infof("Prefetching [%s] failed: %v, will retry in %v", source.name, err, interval)
} else {
dlog.Debugf("Prefetching [%s] succeeded, next update: %v", source.name, delay)
dlog.Debugf("Prefetching [%s] succeeded, next update in %v min", source.name, delay)
if delay >= MinimumPrefetchInterval && (interval == MinimumPrefetchInterval || interval > delay) {
interval = delay
}
@ -240,19 +262,19 @@ func (source *Source) parseV2() ([]RegisteredServer, error) {
stampErrs = append(stampErrs, stampErr)
dlog.Warn(stampErr)
}
in := string(source.in)
in := string(source.bin)
parts := strings.Split(in, "## ")
if len(parts) < 2 {
return registeredServers, fmt.Errorf("Invalid format for source at [%v]", source.urls)
}
parts = parts[1:]
for _, part := range parts {
part = strings.TrimFunc(part, unicode.IsSpace)
part = strings.TrimSpace(part)
subparts := strings.Split(part, "\n")
if len(subparts) < 2 {
return registeredServers, fmt.Errorf("Invalid format for source at [%v]", source.urls)
}
name := strings.TrimFunc(subparts[0], unicode.IsSpace)
name := strings.TrimSpace(subparts[0])
if len(name) == 0 {
return registeredServers, fmt.Errorf("Invalid format for source at [%v]", source.urls)
}
@ -261,7 +283,7 @@ func (source *Source) parseV2() ([]RegisteredServer, error) {
var stampStr, description string
stampStrs := make([]string, 0)
for _, subpart := range subparts {
subpart = strings.TrimFunc(subpart, unicode.IsSpace)
subpart = strings.TrimSpace(subpart)
if strings.HasPrefix(subpart, "sdns:") && len(subpart) >= 6 {
stampStrs = append(stampStrs, subpart)
continue

View File

@ -3,7 +3,6 @@ package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
@ -15,9 +14,9 @@ import (
"time"
"github.com/hectane/go-acl"
"github.com/powerman/check"
"github.com/jedisct1/dlog"
"github.com/jedisct1/go-minisign"
"github.com/powerman/check"
)
type SourceFixture struct {
@ -69,7 +68,7 @@ type SourceTestExpect struct {
}
func readFixture(t *testing.T, name string) []byte {
bin, err := ioutil.ReadFile(filepath.Join("testdata", name))
bin, err := os.ReadFile(filepath.Join("testdata", name))
if err != nil {
t.Fatalf("Unable to read test fixture %s: %v", name, err)
}
@ -84,9 +83,9 @@ func writeSourceCache(t *testing.T, e *SourceTestExpect) {
path := e.cachePath + f.suffix
perms := f.perms
if perms == 0 {
perms = 0644
perms = 0o644
}
if err := ioutil.WriteFile(path, f.content, perms); err != nil {
if err := os.WriteFile(path, f.content, perms); err != nil {
t.Fatalf("Unable to write cache file %s: %v", path, err)
}
if err := acl.Chmod(path, perms); err != nil {
@ -108,8 +107,8 @@ func writeSourceCache(t *testing.T, e *SourceTestExpect) {
func checkSourceCache(c *check.C, e *SourceTestExpect) {
for _, f := range e.cache {
path := e.cachePath + f.suffix
_ = acl.Chmod(path, 0644) // don't worry if this fails, reading it will catch the same problem
got, err := ioutil.ReadFile(path)
_ = acl.Chmod(path, 0o644) // don't worry if this fails, reading it will catch the same problem
got, err := os.ReadFile(path)
c.DeepEqual(got, f.content, "Unexpected content for cache file '%s', err %v", path, err)
if f.suffix != "" {
continue
@ -134,7 +133,7 @@ func loadSnakeoil(t *testing.T, d *SourceTestData) {
}
func loadTestSourceNames(t *testing.T, d *SourceTestData) {
files, err := ioutil.ReadDir(filepath.Join("testdata", "sources"))
files, err := os.ReadDir(filepath.Join("testdata", "sources"))
if err != nil {
t.Fatalf("Unable to load list of test sources: %v", err)
}
@ -145,7 +144,7 @@ func loadTestSourceNames(t *testing.T, d *SourceTestData) {
}
}
func generateFixtureState(t *testing.T, d *SourceTestData, suffix, file string, state SourceTestState) {
func generateFixtureState(_ *testing.T, d *SourceTestData, suffix, file string, state SourceTestState) {
if _, ok := d.fixtures[state]; !ok {
d.fixtures[state] = map[string]SourceFixture{}
}
@ -165,7 +164,7 @@ func generateFixtureState(t *testing.T, d *SourceTestData, suffix, file string,
case TestStateReadErr, TestStateReadSigErr:
f.content, f.length = []byte{}, "1"
case TestStateOpenErr, TestStateOpenSigErr:
f.content, f.perms = d.fixtures[TestStateCorrect][file].content[:1], 0200
f.content, f.perms = d.fixtures[TestStateCorrect][file].content[:1], 0o200
}
d.fixtures[state][file] = f
}
@ -196,7 +195,7 @@ func loadFixtures(t *testing.T, d *SourceTestData) {
}
func makeTempDir(t *testing.T, d *SourceTestData) {
name, err := ioutil.TempDir("", "sources_test.go."+t.Name())
name, err := os.MkdirTemp("", "sources_test.go."+t.Name())
if err != nil {
t.Fatalf("Unable to create temporary directory: %v", err)
}
@ -285,9 +284,9 @@ func prepSourceTestCache(t *testing.T, d *SourceTestData, e *SourceTestExpect, s
e.cache = []SourceFixture{d.fixtures[state][source], d.fixtures[state][source+".minisig"]}
switch state {
case TestStateCorrect:
e.Source.in, e.success = e.cache[0].content, true
e.Source.bin, e.success = e.cache[0].content, true
case TestStateExpired:
e.Source.in = e.cache[0].content
e.Source.bin = e.cache[0].content
case TestStatePartial, TestStatePartialSig:
e.err = "signature"
case TestStateMissing, TestStateMissingSig, TestStateOpenErr, TestStateOpenSigErr:
@ -296,7 +295,13 @@ func prepSourceTestCache(t *testing.T, d *SourceTestData, e *SourceTestExpect, s
writeSourceCache(t, e)
}
func prepSourceTestDownload(t *testing.T, d *SourceTestData, e *SourceTestExpect, source string, downloadTest []SourceTestState) {
func prepSourceTestDownload(
_ *testing.T,
d *SourceTestData,
e *SourceTestExpect,
source string,
downloadTest []SourceTestState,
) {
if len(downloadTest) == 0 {
return
}
@ -313,7 +318,11 @@ func prepSourceTestDownload(t *testing.T, d *SourceTestData, e *SourceTestExpect
case TestStateOpenErr, TestStateOpenSigErr:
if u, err := url.Parse(serverURL + path); err == nil {
host, port := ExtractHostAndPort(u.Host, -1)
u.Host = fmt.Sprintf("%s:%d", host, port|0x10000) // high numeric port is parsed but then fails to connect
u.Host = fmt.Sprintf(
"%s:%d",
host,
port|0x10000,
) // high numeric port is parsed but then fails to connect
serverURL = u.String()
}
e.err = "invalid port"
@ -330,7 +339,7 @@ func prepSourceTestDownload(t *testing.T, d *SourceTestData, e *SourceTestExpect
switch state {
case TestStateCorrect:
e.cache = []SourceFixture{d.fixtures[state][source], d.fixtures[state][source+".minisig"]}
e.Source.in, e.success = e.cache[0].content, true
e.Source.bin, e.success = e.cache[0].content, true
fallthrough
case TestStateMissingSig, TestStatePartial, TestStatePartialSig, TestStateReadSigErr:
d.reqExpect[path+".minisig"]++
@ -353,14 +362,17 @@ func prepSourceTestDownload(t *testing.T, d *SourceTestData, e *SourceTestExpect
}
func setupSourceTestCase(t *testing.T, d *SourceTestData, i int,
cacheTest *SourceTestState, downloadTest []SourceTestState) (id string, e *SourceTestExpect) {
cacheTest *SourceTestState, downloadTest []SourceTestState,
) (id string, e *SourceTestExpect) {
id = strconv.Itoa(d.n) + "-" + strconv.Itoa(i)
e = &SourceTestExpect{
cachePath: filepath.Join(d.tempDir, id),
mtime: d.timeNow,
}
e.Source = &Source{name: id, urls: []*url.URL{}, format: SourceFormatV2, minisignKey: d.key,
cacheFile: e.cachePath, cacheTTL: DefaultPrefetchDelay * 3, prefetchDelay: DefaultPrefetchDelay}
e.Source = &Source{
name: id, urls: []*url.URL{}, format: SourceFormatV2, minisignKey: d.key,
cacheFile: e.cachePath, cacheTTL: DefaultPrefetchDelay * 3, prefetchDelay: DefaultPrefetchDelay,
}
if cacheTest != nil {
prepSourceTestCache(t, d, e, d.sources[i], *cacheTest)
i = (i + 1) % len(d.sources) // make the cached and downloaded fixtures different
@ -370,6 +382,10 @@ func setupSourceTestCase(t *testing.T, d *SourceTestData, i int,
}
func TestNewSource(t *testing.T) {
if testing.Verbose() {
dlog.SetLogLevel(dlog.SeverityDebug)
dlog.UseSyslog(false)
}
teardown, d := setupSourceTest(t)
defer teardown()
checkResult := func(t *testing.T, e *SourceTestExpect, got *Source, err error) {
@ -394,7 +410,16 @@ func TestNewSource(t *testing.T) {
{"v2", "", DefaultPrefetchDelay * 3, &SourceTestExpect{err: "Invalid encoded public key", Source: &Source{name: "invalid public key", urls: []*url.URL{}, cacheTTL: DefaultPrefetchDelay * 3, prefetchDelay: DefaultPrefetchDelay}}},
} {
t.Run(tt.e.Source.name, func(t *testing.T) {
got, err := NewSource(tt.e.Source.name, d.xTransport, tt.e.urls, tt.key, tt.e.cachePath, tt.v, tt.refreshDelay, tt.e.prefix)
got, err := NewSource(
tt.e.Source.name,
d.xTransport,
tt.e.urls,
tt.key,
tt.e.cachePath,
tt.v,
tt.refreshDelay,
tt.e.prefix,
)
checkResult(t, tt.e, got, err)
})
}
@ -404,7 +429,16 @@ func TestNewSource(t *testing.T) {
for i := range d.sources {
id, e := setupSourceTestCase(t, d, i, &cacheTest, downloadTest)
t.Run("cache "+cacheTestName+", download "+downloadTestName+"/"+id, func(t *testing.T) {
got, err := NewSource(id, d.xTransport, e.urls, d.keyStr, e.cachePath, "v2", DefaultPrefetchDelay*3, "")
got, err := NewSource(
id,
d.xTransport,
e.urls,
d.keyStr,
e.cachePath,
"v2",
DefaultPrefetchDelay*3,
"",
)
checkResult(t, e, got, err)
})
}
@ -413,6 +447,10 @@ func TestNewSource(t *testing.T) {
}
func TestPrefetchSources(t *testing.T) {
if testing.Verbose() {
dlog.SetLogLevel(dlog.SeverityDebug)
dlog.UseSyslog(false)
}
teardown, d := setupSourceTest(t)
defer teardown()
checkResult := func(t *testing.T, expects []*SourceTestExpect, got time.Duration) {
@ -439,7 +477,7 @@ func TestPrefetchSources(t *testing.T) {
e.mtime = d.timeUpd
s := &Source{}
*s = *e.Source
s.in = nil
s.bin = nil
sources = append(sources, s)
expects = append(expects, e)
}

View File

@ -1,3 +1,4 @@
//go:build !linux
// +build !linux
package main

View File

@ -1,3 +1,4 @@
//go:build !android
// +build !android
package main
@ -14,7 +15,9 @@ func (proxy *Proxy) addSystemDListeners() error {
if len(files) > 0 {
if len(proxy.userName) > 0 || proxy.child {
dlog.Fatal("Systemd activated sockets are incompatible with privilege dropping. Remove activated sockets and fill `listen_addresses` in the dnscrypt-proxy configuration file instead.")
dlog.Fatal(
"Systemd activated sockets are incompatible with privilege dropping. Remove activated sockets and fill `listen_addresses` in the dnscrypt-proxy configuration file instead.",
)
}
dlog.Warn("Systemd sockets are untested and unsupported - use at your own risk")
}

View File

@ -62,7 +62,15 @@ func parseTimeRanges(timeRangesStr []TimeRangeStr) ([]TimeRange, error) {
func parseWeeklyRanges(weeklyRangesStr WeeklyRangesStr) (WeeklyRanges, error) {
weeklyRanges := WeeklyRanges{}
weeklyRangesStrX := [7][]TimeRangeStr{weeklyRangesStr.Sun, weeklyRangesStr.Mon, weeklyRangesStr.Tue, weeklyRangesStr.Wed, weeklyRangesStr.Thu, weeklyRangesStr.Fri, weeklyRangesStr.Sat}
weeklyRangesStrX := [7][]TimeRangeStr{
weeklyRangesStr.Sun,
weeklyRangesStr.Mon,
weeklyRangesStr.Tue,
weeklyRangesStr.Wed,
weeklyRangesStr.Thu,
weeklyRangesStr.Fri,
weeklyRangesStr.Sat,
}
for day, weeklyRangeStrX := range weeklyRangesStrX {
timeRanges, err := parseTimeRanges(weeklyRangeStrX)
if err != nil {

View File

@ -1,3 +1,4 @@
//go:build !android
// +build !android
package main

View File

@ -2,6 +2,7 @@ package main
import (
"bytes"
"compress/gzip"
"context"
"crypto/sha512"
"crypto/tls"
@ -10,11 +11,11 @@ import (
"encoding/hex"
"errors"
"io"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
@ -23,17 +24,19 @@ import (
"github.com/jedisct1/dlog"
stamps "github.com/jedisct1/go-dnsstamps"
"github.com/miekg/dns"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"golang.org/x/net/http2"
netproxy "golang.org/x/net/proxy"
)
const (
DefaultFallbackResolver = "9.9.9.9:53"
DefaultKeepAlive = 5 * time.Second
DefaultTimeout = 30 * time.Second
SystemResolverIPTTL = 24 * time.Hour
MinResolverIPTTL = 12 * time.Hour
ExpiredCachedIPGraceTTL = 15 * time.Minute
DefaultBootstrapResolver = "9.9.9.9:53"
DefaultKeepAlive = 5 * time.Second
DefaultTimeout = 30 * time.Second
SystemResolverIPTTL = 24 * time.Hour
MinResolverIPTTL = 12 * time.Hour
ExpiredCachedIPGraceTTL = 15 * time.Minute
)
type CachedIPItem struct {
@ -46,38 +49,51 @@ type CachedIPs struct {
cache map[string]*CachedIPItem
}
type AltSupport struct {
sync.RWMutex
cache map[string]uint16
}
type XTransport struct {
transport *http.Transport
h3Transport *http3.RoundTripper
keepAlive time.Duration
timeout time.Duration
cachedIPs CachedIPs
fallbackResolvers []string
altSupport AltSupport
internalResolvers []string
bootstrapResolvers []string
mainProto string
ignoreSystemDNS bool
internalResolverReady bool
useIPv4 bool
useIPv6 bool
http3 bool
tlsDisableSessionTickets bool
tlsCipherSuite []uint16
proxyDialer *netproxy.Dialer
httpProxyFunction func(*http.Request) (*url.URL, error)
tlsClientCreds DOHClientCreds
keyLogWriter io.Writer
}
func NewXTransport() *XTransport {
if err := isIPAndPort(DefaultFallbackResolver); err != nil {
panic("DefaultFallbackResolver does not parse")
if err := isIPAndPort(DefaultBootstrapResolver); err != nil {
panic("DefaultBootstrapResolver does not parse")
}
xTransport := XTransport{
cachedIPs: CachedIPs{cache: make(map[string]*CachedIPItem)},
altSupport: AltSupport{cache: make(map[string]uint16)},
keepAlive: DefaultKeepAlive,
timeout: DefaultTimeout,
fallbackResolvers: []string{DefaultFallbackResolver},
bootstrapResolvers: []string{DefaultBootstrapResolver},
mainProto: "",
ignoreSystemDNS: true,
useIPv4: true,
useIPv6: false,
tlsDisableSessionTickets: false,
tlsCipherSuite: nil,
keyLogWriter: nil,
}
return &xTransport
}
@ -121,7 +137,7 @@ func (xTransport *XTransport) loadCachedIP(host string) (ip net.IP, expired bool
func (xTransport *XTransport) rebuildTransport() {
dlog.Debug("Rebuilding transport")
if xTransport.transport != nil {
(*xTransport.transport).CloseIdleConnections()
xTransport.transport.CloseIdleConnections()
}
timeout := xTransport.timeout
transport := &http.Transport{
@ -145,7 +161,7 @@ func (xTransport *XTransport) rebuildTransport() {
ipOnly = "[" + cachedIP.String() + "]"
}
} else {
dlog.Debugf("[%s] IP address was not cached", host)
dlog.Debugf("[%s] IP address was not cached in DialContext", host)
}
addrStr = ipOnly + ":" + strconv.Itoa(port)
if xTransport.proxyDialer == nil {
@ -158,27 +174,49 @@ func (xTransport *XTransport) rebuildTransport() {
if xTransport.httpProxyFunction != nil {
transport.Proxy = xTransport.httpProxyFunction
}
tlsClientConfig := tls.Config{}
clientCreds := xTransport.tlsClientCreds
if (clientCreds != DOHClientCreds{}) {
tlsClientConfig := tls.Config{}
certPool, certPoolErr := x509.SystemCertPool()
if xTransport.keyLogWriter != nil {
tlsClientConfig.KeyLogWriter = xTransport.keyLogWriter
}
if clientCreds.rootCA != "" {
if certPool == nil {
dlog.Fatalf("Additional CAs not supported on this platform: %v", certPoolErr)
}
additionalCaCert, err := os.ReadFile(clientCreds.rootCA)
if err != nil {
dlog.Fatal(err)
}
certPool.AppendCertsFromPEM(additionalCaCert)
}
if certPool != nil {
// Some operating systems don't include Let's Encrypt ISRG Root X1 certificate yet
letsEncryptX1Cert := []byte(`-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZLubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----`)
certPool.AppendCertsFromPEM(letsEncryptX1Cert)
tlsClientConfig.RootCAs = certPool
}
if clientCreds.clientCert != "" {
cert, err := tls.LoadX509KeyPair(clientCreds.clientCert, clientCreds.clientKey)
if err != nil {
dlog.Fatalf("Unable to use certificate [%v] (key: [%v]): %v", clientCreds.clientCert, clientCreds.clientKey, err)
}
if clientCreds.rootCA != "" {
caCert, err := ioutil.ReadFile(clientCreds.rootCA)
if err != nil {
dlog.Fatal(err)
}
systemCertPool, err := x509.SystemCertPool()
if err != nil {
dlog.Fatal(err)
}
systemCertPool.AppendCertsFromPEM(caCert)
tlsClientConfig.RootCAs = systemCertPool
dlog.Fatalf(
"Unable to use certificate [%v] (key: [%v]): %v",
clientCreds.clientCert,
clientCreds.clientKey,
err,
)
}
tlsClientConfig.Certificates = []tls.Certificate{cert}
}
if xTransport.tlsDisableSessionTickets || xTransport.tlsCipherSuite != nil {
tlsClientConfig.SessionTicketsDisabled = xTransport.tlsDisableSessionTickets
if !xTransport.tlsDisableSessionTickets {
@ -187,11 +225,77 @@ func (xTransport *XTransport) rebuildTransport() {
if xTransport.tlsCipherSuite != nil {
tlsClientConfig.PreferServerCipherSuites = false
tlsClientConfig.CipherSuites = xTransport.tlsCipherSuite
// Go doesn't allow changing the cipher suite with TLS 1.3
// So, check if the requested set of ciphers matches the TLS 1.3 suite.
// If it doesn't, downgrade to TLS 1.2
compatibleSuitesCount := 0
for _, suite := range tls.CipherSuites() {
if suite.Insecure {
continue
}
for _, supportedVersion := range suite.SupportedVersions {
if supportedVersion != tls.VersionTLS13 {
for _, expectedSuiteID := range xTransport.tlsCipherSuite {
if expectedSuiteID == suite.ID {
compatibleSuitesCount += 1
break
}
}
}
}
}
if compatibleSuitesCount != len(tls.CipherSuites()) {
dlog.Notice("Explicit cipher suite configured - downgrading to TLS 1.2")
tlsClientConfig.MaxVersion = tls.VersionTLS12
}
}
}
transport.TLSClientConfig = &tlsClientConfig
http2.ConfigureTransport(transport)
if http2Transport, err := http2.ConfigureTransports(transport); err != nil {
http2Transport.ReadIdleTimeout = timeout
http2Transport.AllowHTTP = false
}
xTransport.transport = transport
if xTransport.http3 {
dial := func(ctx context.Context, addrStr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
dlog.Debugf("Dialing for H3: [%v]", addrStr)
host, port := ExtractHostAndPort(addrStr, stamps.DefaultPort)
ipOnly := host
cachedIP, _ := xTransport.loadCachedIP(host)
network := "udp4"
if cachedIP != nil {
if ipv4 := cachedIP.To4(); ipv4 != nil {
ipOnly = ipv4.String()
} else {
ipOnly = "[" + cachedIP.String() + "]"
network = "udp6"
}
} else {
dlog.Debugf("[%s] IP address was not cached in H3 context", host)
if xTransport.useIPv6 {
if xTransport.useIPv4 {
network = "udp"
} else {
network = "udp6"
}
}
}
addrStr = ipOnly + ":" + strconv.Itoa(port)
udpAddr, err := net.ResolveUDPAddr(network, addrStr)
if err != nil {
return nil, err
}
udpConn, err := net.ListenUDP(network, nil)
if err != nil {
return nil, err
}
tlsCfg.ServerName = host
return quic.DialEarly(ctx, udpConn, udpAddr, tlsCfg, cfg)
}
h3Transport := &http3.RoundTripper{DisableCompression: true, TLSClientConfig: &tlsClientConfig, Dial: dial}
xTransport.h3Transport = h3Transport
}
}
func (xTransport *XTransport) resolveUsingSystem(host string) (ip net.IP, ttl time.Duration, err error) {
@ -222,7 +326,10 @@ func (xTransport *XTransport) resolveUsingSystem(host string) (ip net.IP, ttl ti
return
}
func (xTransport *XTransport) resolveUsingResolver(proto, host string, resolver string) (ip net.IP, ttl time.Duration, err error) {
func (xTransport *XTransport) resolveUsingResolver(
proto, host string,
resolver string,
) (ip net.IP, ttl time.Duration, err error) {
dnsClient := dns.Client{Net: proto}
if xTransport.useIPv4 {
msg := dns.Msg{}
@ -267,17 +374,21 @@ func (xTransport *XTransport) resolveUsingResolver(proto, host string, resolver
return
}
func (xTransport *XTransport) resolveUsingResolvers(proto, host string, resolvers []string) (ip net.IP, ttl time.Duration, err error) {
func (xTransport *XTransport) resolveUsingResolvers(
proto, host string,
resolvers []string,
) (ip net.IP, ttl time.Duration, err error) {
err = errors.New("Empty resolvers")
for i, resolver := range resolvers {
ip, ttl, err = xTransport.resolveUsingResolver(proto, host, resolver)
if err == nil {
if i > 0 {
dlog.Infof("Resolution succeeded with fallback resolver %s[%s]", proto, resolver)
dlog.Infof("Resolution succeeded with resolver %s[%s]", proto, resolver)
resolvers[0], resolvers[i] = resolvers[i], resolvers[0]
}
break
}
dlog.Infof("Unable to resolve [%s] using fallback resolver %s[%s]: %v", host, proto, resolver, err)
dlog.Infof("Unable to resolve [%s] using resolver [%s] (%s): %v", host, resolver, proto, err)
}
return
}
@ -297,28 +408,46 @@ func (xTransport *XTransport) resolveAndUpdateCache(host string) error {
var foundIP net.IP
var ttl time.Duration
var err error
if !xTransport.ignoreSystemDNS {
foundIP, ttl, err = xTransport.resolveUsingSystem(host)
protos := []string{"udp", "tcp"}
if xTransport.mainProto == "tcp" {
protos = []string{"tcp", "udp"}
}
if xTransport.ignoreSystemDNS || err != nil {
protos := []string{"udp", "tcp"}
if xTransport.mainProto == "tcp" {
protos = []string{"tcp", "udp"}
if xTransport.ignoreSystemDNS {
if xTransport.internalResolverReady {
for _, proto := range protos {
foundIP, ttl, err = xTransport.resolveUsingResolvers(proto, host, xTransport.internalResolvers)
if err == nil {
break
}
}
} else {
err = errors.New("Service is not usable yet")
dlog.Notice(err)
}
} else {
foundIP, ttl, err = xTransport.resolveUsingSystem(host)
if err != nil {
err = errors.New("System DNS is not usable yet")
dlog.Notice(err)
}
}
if err != nil {
for _, proto := range protos {
if err != nil {
dlog.Noticef("System DNS configuration not usable yet, exceptionally resolving [%s] using fallback resolvers over %s", host, proto)
} else {
dlog.Debugf("Resolving [%s] using fallback resolvers over %s", host, proto)
dlog.Noticef(
"Resolving server host [%s] using bootstrap resolvers over %s",
host,
proto,
)
}
foundIP, ttl, err = xTransport.resolveUsingResolvers(proto, host, xTransport.fallbackResolvers)
foundIP, ttl, err = xTransport.resolveUsingResolvers(proto, host, xTransport.bootstrapResolvers)
if err == nil {
break
}
}
}
if err != nil && xTransport.ignoreSystemDNS {
dlog.Noticef("Fallback resolvers didn't respond - Trying with the system resolver as a last resort")
dlog.Noticef("Bootstrap resolvers didn't respond - Trying with the system resolver as a last resort")
foundIP, ttl, err = xTransport.resolveUsingSystem(host)
}
if ttl < MinResolverIPTTL {
@ -333,16 +462,50 @@ func (xTransport *XTransport) resolveAndUpdateCache(host string) error {
return err
}
}
if foundIP == nil {
if !xTransport.useIPv4 && xTransport.useIPv6 {
dlog.Warnf("no IPv6 address found for [%s]", host)
} else if xTransport.useIPv4 && !xTransport.useIPv6 {
dlog.Warnf("no IPv4 address found for [%s]", host)
} else {
dlog.Errorf("no IP address found for [%s]", host)
}
}
xTransport.saveCachedIP(host, foundIP, ttl)
dlog.Debugf("[%s] IP address [%s] added to the cache, valid for %v", host, foundIP, ttl)
return nil
}
func (xTransport *XTransport) Fetch(method string, url *url.URL, accept string, contentType string, body *[]byte, timeout time.Duration) ([]byte, *tls.ConnectionState, time.Duration, error) {
func (xTransport *XTransport) Fetch(
method string,
url *url.URL,
accept string,
contentType string,
body *[]byte,
timeout time.Duration,
compress bool,
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
if timeout <= 0 {
timeout = xTransport.timeout
}
client := http.Client{Transport: xTransport.transport, Timeout: timeout}
client := http.Client{
Transport: xTransport.transport,
Timeout: timeout,
}
host, port := ExtractHostAndPort(url.Host, 443)
hasAltSupport := false
if xTransport.h3Transport != nil {
xTransport.altSupport.RLock()
var altPort uint16
altPort, hasAltSupport = xTransport.altSupport.cache[url.Host]
xTransport.altSupport.RUnlock()
if hasAltSupport {
if int(altPort) == port {
client.Transport = xTransport.h3Transport
dlog.Debugf("Using HTTP/3 transport for [%s]", url.Host)
}
}
}
header := map[string][]string{"User-Agent": {"dnscrypt-proxy"}}
if len(accept) > 0 {
header["Accept"] = []string{accept}
@ -359,13 +522,18 @@ func (xTransport *XTransport) Fetch(method string, url *url.URL, accept string,
url2.RawQuery = qs.Encode()
url = &url2
}
host, _ := ExtractHostAndPort(url.Host, 0)
if xTransport.proxyDialer == nil && strings.HasSuffix(host, ".onion") {
return nil, nil, 0, errors.New("Onion service is not reachable without Tor")
return nil, 0, nil, 0, errors.New("Onion service is not reachable without Tor")
}
if err := xTransport.resolveAndUpdateCache(host); err != nil {
dlog.Errorf("Unable to resolve [%v] - Make sure that the system resolver works, or that `fallback_resolver` has been set to a resolver that can be reached", host)
return nil, nil, 0, err
dlog.Errorf(
"Unable to resolve [%v] - Make sure that the system resolver works, or that `bootstrap_resolvers` has been set to resolvers that can be reached",
host,
)
return nil, 0, nil, 0, err
}
if compress && body == nil {
header["Accept-Encoding"] = []string{"gzip"}
}
req := &http.Request{
Method: method,
@ -375,7 +543,7 @@ func (xTransport *XTransport) Fetch(method string, url *url.URL, accept string,
}
if body != nil {
req.ContentLength = int64(len(*body))
req.Body = ioutil.NopCloser(bytes.NewReader(*body))
req.Body = io.NopCloser(bytes.NewReader(*body))
}
start := time.Now()
resp, err := client.Do(req)
@ -387,36 +555,103 @@ func (xTransport *XTransport) Fetch(method string, url *url.URL, accept string,
err = errors.New(resp.Status)
}
} else {
(*xTransport.transport).CloseIdleConnections()
dlog.Debugf("HTTP client error: [%v] - closing idle connections", err)
xTransport.transport.CloseIdleConnections()
}
statusCode := 503
if resp != nil {
statusCode = resp.StatusCode
}
if err != nil {
dlog.Debugf("[%s]: [%s]", req.URL, err)
if xTransport.tlsCipherSuite != nil && strings.Contains(err.Error(), "handshake failure") {
dlog.Warnf("TLS handshake failure - Try changing or deleting the tls_cipher_suite value in the configuration file")
dlog.Warnf(
"TLS handshake failure - Try changing or deleting the tls_cipher_suite value in the configuration file",
)
xTransport.tlsCipherSuite = nil
xTransport.rebuildTransport()
}
return nil, nil, 0, err
return nil, statusCode, nil, rtt, err
}
if xTransport.h3Transport != nil && !hasAltSupport {
if alt, found := resp.Header["Alt-Svc"]; found {
dlog.Debugf("Alt-Svc [%s]: [%s]", url.Host, alt)
altPort := uint16(port & 0xffff)
for i, xalt := range alt {
for j, v := range strings.Split(xalt, ";") {
if i >= 8 || j >= 16 {
break
}
v = strings.TrimSpace(v)
if strings.HasPrefix(v, "h3=\":") {
v = strings.TrimPrefix(v, "h3=\":")
v = strings.TrimSuffix(v, "\"")
if xAltPort, err := strconv.ParseUint(v, 10, 16); err == nil && xAltPort <= 65535 {
altPort = uint16(xAltPort)
dlog.Debugf("Using HTTP/3 for [%s]", url.Host)
break
}
}
}
}
xTransport.altSupport.Lock()
xTransport.altSupport.cache[url.Host] = altPort
dlog.Debugf("Caching altPort for [%v]", url.Host)
xTransport.altSupport.Unlock()
}
}
tls := resp.TLS
bin, err := ioutil.ReadAll(io.LimitReader(resp.Body, MaxHTTPBodyLength))
var bodyReader io.ReadCloser = resp.Body
if compress && resp.Header.Get("Content-Encoding") == "gzip" {
bodyReader, err = gzip.NewReader(io.LimitReader(resp.Body, MaxHTTPBodyLength))
if err != nil {
return nil, statusCode, tls, rtt, err
}
defer bodyReader.Close()
}
bin, err := io.ReadAll(io.LimitReader(bodyReader, MaxHTTPBodyLength))
if err != nil {
return nil, tls, 0, err
return nil, statusCode, tls, rtt, err
}
resp.Body.Close()
return bin, tls, rtt, err
return bin, statusCode, tls, rtt, err
}
func (xTransport *XTransport) Get(url *url.URL, accept string, timeout time.Duration) ([]byte, *tls.ConnectionState, time.Duration, error) {
return xTransport.Fetch("GET", url, accept, "", nil, timeout)
func (xTransport *XTransport) GetWithCompression(
url *url.URL,
accept string,
timeout time.Duration,
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
return xTransport.Fetch("GET", url, accept, "", nil, timeout, true)
}
func (xTransport *XTransport) Post(url *url.URL, accept string, contentType string, body *[]byte, timeout time.Duration) ([]byte, *tls.ConnectionState, time.Duration, error) {
return xTransport.Fetch("POST", url, accept, contentType, body, timeout)
func (xTransport *XTransport) Get(
url *url.URL,
accept string,
timeout time.Duration,
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
return xTransport.Fetch("GET", url, accept, "", nil, timeout, false)
}
func (xTransport *XTransport) DoHQuery(useGet bool, url *url.URL, body []byte, timeout time.Duration) ([]byte, *tls.ConnectionState, time.Duration, error) {
dataType := "application/dns-message"
func (xTransport *XTransport) Post(
url *url.URL,
accept string,
contentType string,
body *[]byte,
timeout time.Duration,
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
return xTransport.Fetch("POST", url, accept, contentType, body, timeout, false)
}
func (xTransport *XTransport) dohLikeQuery(
dataType string,
useGet bool,
url *url.URL,
body []byte,
timeout time.Duration,
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
if useGet {
qs := url.Query()
encBody := base64.RawURLEncoding.EncodeToString(body)
@ -427,3 +662,21 @@ func (xTransport *XTransport) DoHQuery(useGet bool, url *url.URL, body []byte, t
}
return xTransport.Post(url, dataType, dataType, &body, timeout)
}
func (xTransport *XTransport) DoHQuery(
useGet bool,
url *url.URL,
body []byte,
timeout time.Duration,
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
return xTransport.dohLikeQuery("application/dns-message", useGet, url, body, timeout)
}
func (xTransport *XTransport) ObliviousDoHQuery(
useGet bool,
url *url.URL,
body []byte,
timeout time.Duration,
) ([]byte, int, *tls.ConnectionState, time.Duration, error) {
return xTransport.dohLikeQuery("application/oblivious-dns-message", useGet, url, body, timeout)
}

64
go.mod
View File

@ -1,27 +1,51 @@
module github.com/DNSCrypt/dnscrypt-proxy
module github.com/dnscrypt/dnscrypt-proxy
go 1.15
go 1.22
require (
github.com/BurntSushi/toml v0.3.1
github.com/VividCortex/ewma v1.1.1
github.com/BurntSushi/toml v1.3.2
github.com/VividCortex/ewma v1.2.0
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185
github.com/hashicorp/go-immutable-radix v1.3.0
github.com/hashicorp/golang-lru v0.5.4
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95
github.com/jedisct1/dlog v0.0.0-20210101122416-354ffe815216
github.com/jedisct1/go-clocksmith v0.0.0-20210101121932-da382b963868
github.com/jedisct1/go-dnsstamps v0.0.0-20210101121956-16fbdadcf8f5
github.com/jedisct1/go-minisign v0.0.0-20210101122305-335e26e05d5b
github.com/jedisct1/xsecretbox v0.0.0-20210102102453-4ecb2081017a
github.com/hashicorp/go-immutable-radix v1.3.1
github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb
github.com/jedisct1/dlog v0.0.0-20230811132706-443b333ff1b3
github.com/jedisct1/go-clocksmith v0.0.0-20230211133011-392c1afea73e
github.com/jedisct1/go-dnsstamps v0.0.0-20240423203910-07a0735c7774
github.com/jedisct1/go-hpke-compact v0.0.0-20230811132953-4ee502b61f80
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267
github.com/jedisct1/xsecretbox v0.0.0-20230811132812-b950633f9f1f
github.com/k-sone/critbitgo v1.4.0
github.com/kardianos/service v1.2.0
github.com/miekg/dns v1.1.35
github.com/powerman/check v1.3.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v2 v2.4.0 // indirect
github.com/kardianos/service v1.2.2
github.com/miekg/dns v1.1.59
github.com/opencoff/go-sieve v0.2.1
github.com/powerman/check v1.7.0
github.com/quic-go/quic-go v0.43.1
golang.org/x/crypto v0.23.0
golang.org/x/net v0.25.0
golang.org/x/sys v0.20.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/go-syslog v1.0.0 // indirect
github.com/hashicorp/golang-lru v0.5.0 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/powerman/deepequal v0.1.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.19.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
)

226
go.sum
View File

@ -1,157 +1,127 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
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/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA=
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de h1:F7WD09S8QB4LrkEpka0dFPLSotH11HRpCsLIbIcJ7sU=
github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 h1:S4qyfL2sEm5Budr4KVMyEniCy+PbS55651I/a+Kn/NQ=
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=
github.com/jedisct1/dlog v0.0.0-20210101122416-354ffe815216 h1:31uZUKhZFZGKTg8GsKXBNIcK9TxjXEk2hg+rSKgGDI4=
github.com/jedisct1/dlog v0.0.0-20210101122416-354ffe815216/go.mod h1:CIy7i6SX94LqHB4I4V8JTHovTxZJoQfH1DH31XYNfcs=
github.com/jedisct1/go-clocksmith v0.0.0-20210101121932-da382b963868 h1:QZ79mRbNwYYYmiVjyv+X0NKgYE6nyN1yo3gtEFdzpiE=
github.com/jedisct1/go-clocksmith v0.0.0-20210101121932-da382b963868/go.mod h1:SAINchklztk2jcLWJ4bpNF4KnwDUSUTX+cJbspWC2Rw=
github.com/jedisct1/go-dnsstamps v0.0.0-20210101121956-16fbdadcf8f5 h1:FnAupK0Gm6PJZDhI5sGkbNZQ7DT4+tG8opjmoXfBu/o=
github.com/jedisct1/go-dnsstamps v0.0.0-20210101121956-16fbdadcf8f5/go.mod h1:t35n6rsPE3nD3RXbc5hI5Ax1ci/SSYTpx0BdMXh/1aE=
github.com/jedisct1/go-minisign v0.0.0-20210101122305-335e26e05d5b h1:MQP3X6s3sznGmz6SAtP0HQGWx1qszD5bT7wK6EA2Rnc=
github.com/jedisct1/go-minisign v0.0.0-20210101122305-335e26e05d5b/go.mod h1:oPTyITpvr7hPx/9w76gWrgbZwbb+7gZ9/On8hFc+LNE=
github.com/jedisct1/xsecretbox v0.0.0-20210102102453-4ecb2081017a h1:ptZ+a2DzulAgKEfMuqH2Ckfv6oio5BX14fRG5aShvXw=
github.com/jedisct1/xsecretbox v0.0.0-20210102102453-4ecb2081017a/go.mod h1:WlwP8gEfKNJ4HoIq2vVUUS2mKV9/zUGFO1mJB5jLVsc=
github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb h1:PGufWXXDq9yaev6xX1YQauaO1MV90e6Mpoq1I7Lz/VM=
github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jedisct1/dlog v0.0.0-20230811132706-443b333ff1b3 h1:3wOfILZqpvhb7bNBGAVlXVyfoeXnmhitZ2qPVOx75Y0=
github.com/jedisct1/dlog v0.0.0-20230811132706-443b333ff1b3/go.mod h1:m25Jh2eJ0FXLWjMXFZItQ24W0HAIjosAe4Z0a+SZitU=
github.com/jedisct1/go-clocksmith v0.0.0-20230211133011-392c1afea73e h1:tzG4EjKgHIqKVkLIAC4pXTIapuM2BR05uXokEEysAXA=
github.com/jedisct1/go-clocksmith v0.0.0-20230211133011-392c1afea73e/go.mod h1:SAINchklztk2jcLWJ4bpNF4KnwDUSUTX+cJbspWC2Rw=
github.com/jedisct1/go-dnsstamps v0.0.0-20240423203910-07a0735c7774 h1:DobL5d8UxrYzlD0PbU/EVBAGHuDiFyH46gr6povMw50=
github.com/jedisct1/go-dnsstamps v0.0.0-20240423203910-07a0735c7774/go.mod h1:mEGEFZsGe4sG5Mb3Xi89pmsy+TZ0946ArbYMGKAM5uA=
github.com/jedisct1/go-hpke-compact v0.0.0-20230811132953-4ee502b61f80 h1:o6d/E+fxKQrSEOyR7H+Lb3iFMW3daWdq1nsTnQPWaF0=
github.com/jedisct1/go-hpke-compact v0.0.0-20230811132953-4ee502b61f80/go.mod h1:dwjlOg9tYzZlxyi9HAGJYKUlVe3RZIfXvcni4VUfqRA=
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY=
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E=
github.com/jedisct1/xsecretbox v0.0.0-20230811132812-b950633f9f1f h1:GlhJc4+6ZYS06ALFe46IxfnQqgFCQS2l/eQ9gWzqXuU=
github.com/jedisct1/xsecretbox v0.0.0-20230811132812-b950633f9f1f/go.mod h1:iO1pHSLNuqSfeHw1JrfSQykNiLe3mUsNlJBBBm7UJ8w=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k-sone/critbitgo v1.4.0 h1:l71cTyBGeh6X5ATh6Fibgw3+rtNT80BA0uNNWgkPrbE=
github.com/k-sone/critbitgo v1.4.0/go.mod h1:7E6pyoyADnFxlUBEKcnfS49b7SUAQGMK+OAp/UQvo0s=
github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/opencoff/go-sieve v0.2.1 h1:5Pv6rd3zRquNmXcYHFndjVoolTgcv0ua2XTdMQ+gw0M=
github.com/opencoff/go-sieve v0.2.1/go.mod h1:CndxLpW4R8fDq04XfBSCOZ+qWwDCcxjfUJbr0GPqWHY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/powerman/check v1.3.0 h1:GsncV4RbbC4nZEzLTVpUIdp1rgZil4N36D/XuPZUA60=
github.com/powerman/check v1.3.0/go.mod h1:k/8NCUQwepaKJKctBBKjQo84jvGEvKiumD9pDl87RB0=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/powerman/check v1.7.0 h1:PtRow0L73QgYSmXUBI5qe5MnDu3kowTAKQSHTbDH8Zs=
github.com/powerman/check v1.7.0/go.mod h1:pCQPDCCVj1ksGj9OaMqFBjvet5Jg8TbMB3UJj8Nx98g=
github.com/powerman/deepequal v0.1.0 h1:sVwtyTsBuYIvdbLR1O2wzRY63YgPqdGZmk/o80l+C/U=
github.com/powerman/deepequal v0.1.0/go.mod h1:3k7aG/slufBhUANdN67o/UPg8i5YaiJ6FmibWX0cn04=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ=
github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 h1:joucsQqXmyBVxViHCPFjG3hx8JzIFSaym3l3MM/Jsdg=
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -10,16 +10,16 @@
# you would like enabled uncommented. Then run the script to build the #
# dnscrypt-blocklist-domains.txt file: #
# #
# $ generate-domains-blocklist.py > dnscrypt-blacklist-domains.txt #
# $ generate-domains-blocklist.py -o dnscrypt-blocklist-domains.txt #
# #
# Domains that should never be blocked can be put into a file named #
# domains-allowlist.txt. #
# #
# That blocklist file can then be used in the dnscrypt-proxy.toml file: #
# #
# [blocklist] #
# [blocked_names] #
# #
# blocklist_file = 'dnscrypt-blocklist-domains.txt' #
# blocked_names_file = 'dnscrypt-blocklist-domains.txt' #
# #
##################################################################################
@ -53,11 +53,8 @@ https://pgl.yoyo.org/adservers/serverlist.php?hostformat=nohtml
# Basic tracking list by Disconnect
# https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt
# Sysctl list (ads)
# http://sysctl.org/cameleon/hosts
# KAD host file (fraud/adware) without controversies
# https://raw.githubusercontent.com/PolishFiltersTeam/KADhosts/master/KADhosts_without_controversies.txt
# KAD host file (fraud/adware)
# https://raw.githubusercontent.com/PolishFiltersTeam/KADhosts/master/KADomains.txt
# BarbBlock list (spurious and invalid DMCA takedowns)
https://paulgb.github.io/BarbBlock/blacklists/domain-list.txt
@ -95,15 +92,15 @@ https://hostfiles.frogeye.fr/firstparty-trackers.txt
# Steven Black hosts file
# https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
# A list of adserving and tracking sites maintained by @lightswitch05
https://www.github.developerdan.com/hosts/lists/ads-and-tracking-extended.txt
# A list of adserving and tracking sites maintained by @anudeepND
# https://raw.githubusercontent.com/anudeepND/blacklist/master/adservers.txt
https://raw.githubusercontent.com/anudeepND/blacklist/master/adservers.txt
# Anudeep's Blacklist (CoinMiner) - Blocks cryptojacking sites
# https://raw.githubusercontent.com/anudeepND/blacklist/master/CoinMiner.txt
# Block Spotify ads
# https://gitlab.com/CHEF-KOCH/cks-filterlist/-/raw/master/Anti-Corp/Spotify/Spotify-HOSTS.txt
### Spark < Blu Go < Blu < Basic < Ultimate
### (With pornware blocking) Porn < Unified
# Energized Ultimate
@ -115,11 +112,14 @@ https://hostfiles.frogeye.fr/firstparty-trackers.txt
# Energized BLU
# https://block.energized.pro/blu/formats/domains.txt
# OISD.NL - Blocks ads, phishing, malware, tracking and more. Tries to minimize false positives.
https://dbl.oisd.nl/
# OISD.NL - Blocks ads, phishing, malware, tracking and more. WARNING: this is a huge list.
# https://dblw.oisd.nl/
# OISD.NL (smaller subset) - Blocks ads, phishing, malware, tracking and more.
# https://hosts.oisd.nl/light
# OISD.NL (smaller subset) - Blocks ads, phishing, malware, tracking and more. Tries to miminize false positives.
https://dblw.oisd.nl/basic/
# OISD.NL (extra) - Blocks ads, phishing, malware, tracking and more. Protection over functionality.
# https://dblw.oisd.nl/extra/
# Captain Miao ad list - Block ads and trackers, especially Chinese and Android trackers
# https://raw.githubusercontent.com/jdlingyu/ad-wars/master/sha_ad_hosts
@ -134,6 +134,7 @@ https://dbl.oisd.nl/
# https://raw.githubusercontent.com/olbat/ut1-blacklists/master/blacklists/adult/domains
# https://block.energized.pro/porn/formats/domains.txt
# https://raw.githubusercontent.com/mhxion/pornaway/master/hosts/porn_sites.txt
# https://dblw.oisd.nl/nsfw/
# Block gambling sites
# https://raw.githubusercontent.com/Sinfonietta/hostfiles/master/gambling-hosts
@ -141,11 +142,13 @@ https://dbl.oisd.nl/
# Block dating websites
# https://raw.githubusercontent.com/olbat/ut1-blacklists/master/blacklists/dating/domains
# https://www.github.developerdan.com/hosts/lists/dating-services-extended.txt
# Block social media sites
# https://raw.githubusercontent.com/Sinfonietta/hostfiles/master/social-hosts
# https://block.energized.pro/extensions/social/formats/domains.txt
# https://raw.githubusercontent.com/olbat/ut1-blacklists/master/blacklists/social_networks/domains
# https://www.github.developerdan.com/hosts/lists/facebook-extended.txt
# Goodbye Ads - Specially designed for mobile ad protection
# https://raw.githubusercontent.com/jerryn70/GoodbyeAds/master/Hosts/GoodbyeAds.txt
@ -153,9 +156,5 @@ https://dbl.oisd.nl/
# NextDNS BitTorrent blocklist
# https://raw.githubusercontent.com/nextdns/bittorrent-blocklist/master/domains
# Block spying and tracking on Windows
# Block spying and tracking on Windows
# https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/dnscrypt/spy.txt
# GameIndustry.eu - Block spyware, advertising, analytics, tracking in games and associated clients
# https://www.gameindustry.eu/files/hosts.txt

View File

@ -20,11 +20,15 @@ except (ImportError, ModuleNotFoundError):
URLLIB_NEW = True
log_info = sys.stderr
log_err = sys.stderr
def parse_trusted_list(content):
rx_comment = re.compile(r"^(#|$)")
rx_inline_comment = re.compile(r"\s*#\s*[a-z0-9-].*$")
rx_trusted = re.compile(r"^([*a-z0-9.-]+)\s*(@\S+)?$")
rx_timed = re.compile(r".+\s*(@\S+)?$")
rx_timed = re.compile(r".+\s*@\S+$")
names = set()
time_restrictions = {}
@ -61,6 +65,7 @@ def parse_list(content, trusted=False):
r"^@*\|\|([a-z0-9][a-z0-9.-]*[.][a-z]{2,})\^?(\$(popup|third-party))?$"
)
rx_l = re.compile(r"^([a-z0-9][a-z0-9.-]*[.][a-z]{2,})$")
rx_lw = re.compile(r"^[*][.]([a-z0-9][a-z0-9.-]*[.][a-z]{2,})$")
rx_h = re.compile(
r"^[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}\s+([a-z0-9][a-z0-9.-]*[.][a-z]{2,})$"
)
@ -71,7 +76,7 @@ def parse_list(content, trusted=False):
names = set()
time_restrictions = {}
globs = set()
rx_set = [rx_u, rx_l, rx_h, rx_mdl, rx_b, rx_dq]
rx_set = [rx_u, rx_l, rx_lw, rx_h, rx_mdl, rx_b, rx_dq]
for line in content.splitlines():
line = str.lower(str.strip(line))
if rx_comment.match(line):
@ -88,7 +93,8 @@ def parse_list(content, trusted=False):
def print_restricted_name(output_fd, name, time_restrictions):
if name in time_restrictions:
print("{}\t{}".format(name, time_restrictions[name]), file=output_fd, end="\n")
print("{}\t{}".format(
name, time_restrictions[name]), file=output_fd, end="\n")
else:
print(
"# ignored: [{}] was in the time-restricted list, "
@ -99,7 +105,7 @@ def print_restricted_name(output_fd, name, time_restrictions):
def load_from_url(url):
sys.stderr.write("Loading data from [{}]\n".format(url))
log_info.write("Loading data from [{}]\n".format(url))
req = urllib.Request(url=url, headers={"User-Agent": "dnscrypt-proxy"})
trusted = False
@ -116,7 +122,8 @@ def load_from_url(url):
except urllib.URLError as err:
raise Exception("[{}] could not be loaded: {}\n".format(url, err))
if trusted is False and response.getcode() != 200:
raise Exception("[{}] returned HTTP code {}\n".format(url, response.getcode()))
raise Exception("[{}] returned HTTP code {}\n".format(
url, response.getcode()))
content = response.read()
if URLLIB_NEW:
content = content.decode("utf-8", errors="replace")
@ -202,7 +209,7 @@ def blocklists_from_config_file(
all_names |= names
all_globs |= globs
except Exception as e:
sys.stderr.write(str(e))
log_err.write(str(e))
if not ignore_retrieval_failure:
exit(1)
@ -258,10 +265,12 @@ def blocklists_from_config_file(
list_names.sort(key=name_cmp)
if ignored:
print("# Ignored duplicates: {}".format(ignored), file=output_fd, end="\n")
print("# Ignored duplicates: {}".format(
ignored), file=output_fd, end="\n")
if glob_ignored:
print(
"# Ignored due to overlapping local patterns: {}".format(glob_ignored),
"# Ignored due to overlapping local patterns: {}".format(
glob_ignored),
file=output_fd,
end="\n",
)
@ -323,7 +332,9 @@ args = argp.parse_args()
whitelist = args.whitelist
if whitelist:
print("The option to provide a set of names to exclude from the blocklist has been changed from -w to -a\r\n")
print(
"The option to provide a set of names to exclude from the blocklist has been changed from -w to -a\n"
)
argp.print_help()
exit(1)
@ -332,6 +343,8 @@ allowlist = args.allowlist
time_restricted = args.time_restricted
ignore_retrieval_failure = args.ignore_retrieval_failure
output_file = args.output_file
if output_file:
log_info = sys.stdout
blocklists_from_config_file(
conf, allowlist, time_restricted, ignore_retrieval_failure, output_file

View File

@ -1,5 +1,2 @@
TAGS
tags
.*.swp
tomlcheck/tomlcheck
toml.test
/toml.test
/toml-test

View File

@ -1,15 +0,0 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- tip
install:
- go install ./...
- go get github.com/BurntSushi/toml-test
script:
- export PATH="$PATH:$HOME/gopath/bin"
- make test

View File

@ -1,3 +0,0 @@
Compatible with TOML version
[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)

View File

@ -1,19 +0,0 @@
install:
go install ./...
test: install
go test -v
toml-test toml-test-decoder
toml-test -encoder toml-test-encoder
fmt:
gofmt -w *.go */*.go
colcheck *.go */*.go
tags:
find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
push:
git push origin master
git push github master

View File

@ -1,46 +1,26 @@
## TOML parser and encoder for Go with reflection
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
reflection interface similar to Go's standard library `json` and `xml`
packages. This package also supports the `encoding.TextUnmarshaler` and
`encoding.TextMarshaler` interfaces so that you can define custom data
representations. (There is an example of this below.)
reflection interface similar to Go's standard library `json` and `xml` packages.
Spec: https://github.com/toml-lang/toml
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
Compatible with TOML version
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
Documentation: https://godocs.io/github.com/BurntSushi/toml
Documentation: https://godoc.org/github.com/BurntSushi/toml
See the [releases page](https://github.com/BurntSushi/toml/releases) for a
changelog; this information is also in the git tag annotations (e.g. `git show
v0.4.0`).
Installation:
This library requires Go 1.13 or newer; add it to your go.mod with:
```bash
go get github.com/BurntSushi/toml
```
% go get github.com/BurntSushi/toml@latest
Try the toml validator:
It also comes with a TOML validator CLI tool:
```bash
go get github.com/BurntSushi/toml/cmd/tomlv
tomlv some-toml-file.toml
```
[![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml)
### Testing
This package passes all tests in
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
and the encoder.
% go install github.com/BurntSushi/toml/cmd/tomlv@latest
% tomlv some-toml-file.toml
### Examples
This package works similarly to how the Go standard library handles `XML`
and `JSON`. Namely, data is loaded into Go values via reflection.
For the simplest example, consider some TOML file as just a list of keys
and values:
For the simplest example, consider some TOML file as just a list of keys and
values:
```toml
Age = 25
@ -50,29 +30,23 @@ Perfection = [ 6, 28, 496, 8128 ]
DOB = 1987-07-05T05:45:00Z
```
Which could be defined in Go as:
Which can be decoded with:
```go
type Config struct {
Age int
Cats []string
Pi float64
Perfection []int
DOB time.Time // requires `import time`
Age int
Cats []string
Pi float64
Perfection []int
DOB time.Time
}
```
And then decoded with:
```go
var conf Config
if _, err := toml.Decode(tomlData, &conf); err != nil {
// handle error
}
_, err := toml.Decode(tomlData, &conf)
```
You can also use struct tags if your struct field name doesn't map to a TOML
key value directly:
You can also use struct tags if your struct field name doesn't map to a TOML key
value directly:
```toml
some_key_NAME = "wat"
@ -80,139 +54,67 @@ some_key_NAME = "wat"
```go
type TOML struct {
ObscureKey string `toml:"some_key_NAME"`
ObscureKey string `toml:"some_key_NAME"`
}
```
### Using the `encoding.TextUnmarshaler` interface
Beware that like other decoders **only exported fields** are considered when
encoding and decoding; private fields are silently ignored.
Here's an example that automatically parses duration strings into
`time.Duration` values:
### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces
Here's an example that automatically parses values in a `mail.Address`:
```toml
[[song]]
name = "Thunder Road"
duration = "4m49s"
[[song]]
name = "Stairway to Heaven"
duration = "8m03s"
```
Which can be decoded with:
```go
type song struct {
Name string
Duration duration
}
type songs struct {
Song []song
}
var favorites songs
if _, err := toml.Decode(blob, &favorites); err != nil {
log.Fatal(err)
}
for _, s := range favorites.Song {
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
}
```
And you'll also need a `duration` type that satisfies the
`encoding.TextUnmarshaler` interface:
```go
type duration struct {
time.Duration
}
func (d *duration) UnmarshalText(text []byte) error {
var err error
d.Duration, err = time.ParseDuration(string(text))
return err
}
```
### More complex usage
Here's an example of how to load the example from the official spec page:
```toml
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
contacts = [
"Donald Duck <donald@duckburg.com>",
"Scrooge McDuck <scrooge@duckburg.com>",
]
```
And the corresponding Go types are:
Can be decoded with:
```go
type tomlConfig struct {
Title string
Owner ownerInfo
DB database `toml:"database"`
Servers map[string]server
Clients clients
// Create address type which satisfies the encoding.TextUnmarshaler interface.
type address struct {
*mail.Address
}
type ownerInfo struct {
Name string
Org string `toml:"organization"`
Bio string
DOB time.Time
func (a *address) UnmarshalText(text []byte) error {
var err error
a.Address, err = mail.ParseAddress(string(text))
return err
}
type database struct {
Server string
Ports []int
ConnMax int `toml:"connection_max"`
Enabled bool
}
// Decode it.
func decode() {
blob := `
contacts = [
"Donald Duck <donald@duckburg.com>",
"Scrooge McDuck <scrooge@duckburg.com>",
]
`
type server struct {
IP string
DC string
}
var contacts struct {
Contacts []address
}
type clients struct {
Data [][]interface{}
Hosts []string
_, err := toml.Decode(blob, &contacts)
if err != nil {
log.Fatal(err)
}
for _, c := range contacts.Contacts {
fmt.Printf("%#v\n", c.Address)
}
// Output:
// &mail.Address{Name:"Donald Duck", Address:"donald@duckburg.com"}
// &mail.Address{Name:"Scrooge McDuck", Address:"scrooge@duckburg.com"}
}
```
Note that a case insensitive match will be tried if an exact match can't be
found.
To target TOML specifically you can implement `UnmarshalTOML` TOML interface in
a similar way.
A working example of the above can be found in `_examples/example.{go,toml}`.
### More complex usage
See the [`_example/`](/_example) directory for a more complex example.

View File

@ -1,157 +1,199 @@
package toml
import (
"bytes"
"encoding"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"reflect"
"strconv"
"strings"
"time"
)
func e(format string, args ...interface{}) error {
return fmt.Errorf("toml: "+format, args...)
}
// Unmarshaler is the interface implemented by objects that can unmarshal a
// TOML description of themselves.
type Unmarshaler interface {
UnmarshalTOML(interface{}) error
}
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
func Unmarshal(p []byte, v interface{}) error {
_, err := Decode(string(p), v)
// Unmarshal decodes the contents of data in TOML format into a pointer v.
//
// See [Decoder] for a description of the decoding process.
func Unmarshal(data []byte, v interface{}) error {
_, err := NewDecoder(bytes.NewReader(data)).Decode(v)
return err
}
// Decode the TOML data in to the pointer v.
//
// See [Decoder] for a description of the decoding process.
func Decode(data string, v interface{}) (MetaData, error) {
return NewDecoder(strings.NewReader(data)).Decode(v)
}
// DecodeFile reads the contents of a file and decodes it with [Decode].
func DecodeFile(path string, v interface{}) (MetaData, error) {
fp, err := os.Open(path)
if err != nil {
return MetaData{}, err
}
defer fp.Close()
return NewDecoder(fp).Decode(v)
}
// Primitive is a TOML value that hasn't been decoded into a Go value.
// When using the various `Decode*` functions, the type `Primitive` may
// be given to any value, and its decoding will be delayed.
//
// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
// This type can be used for any value, which will cause decoding to be delayed.
// You can use [PrimitiveDecode] to "manually" decode these values.
//
// The underlying representation of a `Primitive` value is subject to change.
// Do not rely on it.
// NOTE: The underlying representation of a `Primitive` value is subject to
// change. Do not rely on it.
//
// N.B. Primitive values are still parsed, so using them will only avoid
// the overhead of reflection. They can be useful when you don't know the
// exact type of TOML data until run time.
// NOTE: Primitive values are still parsed, so using them will only avoid the
// overhead of reflection. They can be useful when you don't know the exact type
// of TOML data until runtime.
type Primitive struct {
undecoded interface{}
context Key
}
// DEPRECATED!
// The significand precision for float32 and float64 is 24 and 53 bits; this is
// the range a natural number can be stored in a float without loss of data.
const (
maxSafeFloat32Int = 16777215 // 2^24-1
maxSafeFloat64Int = int64(9007199254740991) // 2^53-1
)
// Decoder decodes TOML data.
//
// Use MetaData.PrimitiveDecode instead.
func PrimitiveDecode(primValue Primitive, v interface{}) error {
md := MetaData{decoded: make(map[string]bool)}
return md.unify(primValue.undecoded, rvalue(v))
// TOML tables correspond to Go structs or maps; they can be used
// interchangeably, but structs offer better type safety.
//
// TOML table arrays correspond to either a slice of structs or a slice of maps.
//
// TOML datetimes correspond to [time.Time]. Local datetimes are parsed in the
// local timezone.
//
// [time.Duration] types are treated as nanoseconds if the TOML value is an
// integer, or they're parsed with time.ParseDuration() if they're strings.
//
// All other TOML types (float, string, int, bool and array) correspond to the
// obvious Go types.
//
// An exception to the above rules is if a type implements the TextUnmarshaler
// interface, in which case any primitive TOML value (floats, strings, integers,
// booleans, datetimes) will be converted to a []byte and given to the value's
// UnmarshalText method. See the Unmarshaler example for a demonstration with
// email addresses.
//
// # Key mapping
//
// TOML keys can map to either keys in a Go map or field names in a Go struct.
// The special `toml` struct tag can be used to map TOML keys to struct fields
// that don't match the key name exactly (see the example). A case insensitive
// match to struct names will be tried if an exact match can't be found.
//
// The mapping between TOML values and Go values is loose. That is, there may
// exist TOML values that cannot be placed into your representation, and there
// may be parts of your representation that do not correspond to TOML values.
// This loose mapping can be made stricter by using the IsDefined and/or
// Undecoded methods on the MetaData returned.
//
// This decoder does not handle cyclic types. Decode will not terminate if a
// cyclic type is passed.
type Decoder struct {
r io.Reader
}
// PrimitiveDecode is just like the other `Decode*` functions, except it
// decodes a TOML value that has already been parsed. Valid primitive values
// can *only* be obtained from values filled by the decoder functions,
// including this method. (i.e., `v` may contain more `Primitive`
// values.)
// NewDecoder creates a new Decoder.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
var (
unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
primitiveType = reflect.TypeOf((*Primitive)(nil)).Elem()
)
// Decode TOML data in to the pointer `v`.
func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
s := "%q"
if reflect.TypeOf(v) == nil {
s = "%v"
}
return MetaData{}, fmt.Errorf("toml: cannot decode to non-pointer "+s, reflect.TypeOf(v))
}
if rv.IsNil() {
return MetaData{}, fmt.Errorf("toml: cannot decode to nil value of %q", reflect.TypeOf(v))
}
// Check if this is a supported type: struct, map, interface{}, or something
// that implements UnmarshalTOML or UnmarshalText.
rv = indirect(rv)
rt := rv.Type()
if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map &&
!(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) &&
!rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) {
return MetaData{}, fmt.Errorf("toml: cannot decode to type %s", rt)
}
// TODO: parser should read from io.Reader? Or at the very least, make it
// read from []byte rather than string
data, err := ioutil.ReadAll(dec.r)
if err != nil {
return MetaData{}, err
}
p, err := parse(string(data))
if err != nil {
return MetaData{}, err
}
md := MetaData{
mapping: p.mapping,
keyInfo: p.keyInfo,
keys: p.ordered,
decoded: make(map[string]struct{}, len(p.ordered)),
context: nil,
data: data,
}
return md, md.unify(p.mapping, rv)
}
// PrimitiveDecode is just like the other Decode* functions, except it decodes a
// TOML value that has already been parsed. Valid primitive values can *only* be
// obtained from values filled by the decoder functions, including this method.
// (i.e., v may contain more [Primitive] values.)
//
// Meta data for primitive values is included in the meta data returned by
// the `Decode*` functions with one exception: keys returned by the Undecoded
// method will only reflect keys that were decoded. Namely, any keys hidden
// behind a Primitive will be considered undecoded. Executing this method will
// update the undecoded keys in the meta data. (See the example.)
// Meta data for primitive values is included in the meta data returned by the
// Decode* functions with one exception: keys returned by the Undecoded method
// will only reflect keys that were decoded. Namely, any keys hidden behind a
// Primitive will be considered undecoded. Executing this method will update the
// undecoded keys in the meta data. (See the example.)
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
md.context = primValue.context
defer func() { md.context = nil }()
return md.unify(primValue.undecoded, rvalue(v))
}
// Decode will decode the contents of `data` in TOML format into a pointer
// `v`.
//
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
// used interchangeably.)
//
// TOML arrays of tables correspond to either a slice of structs or a slice
// of maps.
//
// TOML datetimes correspond to Go `time.Time` values.
//
// All other TOML types (float, string, int, bool and array) correspond
// to the obvious Go types.
//
// An exception to the above rules is if a type implements the
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
// (floats, strings, integers, booleans and datetimes) will be converted to
// a byte string and given to the value's UnmarshalText method. See the
// Unmarshaler example for a demonstration with time duration strings.
//
// Key mapping
//
// TOML keys can map to either keys in a Go map or field names in a Go
// struct. The special `toml` struct tag may be used to map TOML keys to
// struct fields that don't match the key name exactly. (See the example.)
// A case insensitive match to struct names will be tried if an exact match
// can't be found.
//
// The mapping between TOML values and Go values is loose. That is, there
// may exist TOML values that cannot be placed into your representation, and
// there may be parts of your representation that do not correspond to
// TOML values. This loose mapping can be made stricter by using the IsDefined
// and/or Undecoded methods on the MetaData returned.
//
// This decoder will not handle cyclic types. If a cyclic type is passed,
// `Decode` will not terminate.
func Decode(data string, v interface{}) (MetaData, error) {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
}
if rv.IsNil() {
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
}
p, err := parse(data)
if err != nil {
return MetaData{}, err
}
md := MetaData{
p.mapping, p.types, p.ordered,
make(map[string]bool, len(p.ordered)), nil,
}
return md, md.unify(p.mapping, indirect(rv))
}
// DecodeFile is just like Decode, except it will automatically read the
// contents of the file at `fpath` and decode it for you.
func DecodeFile(fpath string, v interface{}) (MetaData, error) {
bs, err := ioutil.ReadFile(fpath)
if err != nil {
return MetaData{}, err
}
return Decode(string(bs), v)
}
// DecodeReader is just like Decode, except it will consume all bytes
// from the reader and decode it for you.
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
bs, err := ioutil.ReadAll(r)
if err != nil {
return MetaData{}, err
}
return Decode(string(bs), v)
}
// unify performs a sort of type unification based on the structure of `rv`,
// which is the client representation.
//
// Any type mismatch produces an error. Finding a type that we don't know
// how to handle produces an unsupported type error.
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
// Special case. Look for a `Primitive` value.
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
// TODO: #76 would make this superfluous after implemented.
if rv.Type() == primitiveType {
// Save the undecoded data and the key context into the primitive
// value.
context := make(Key, len(md.context))
@ -163,36 +205,24 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
return nil
}
// Special case. Unmarshaler Interface support.
if rv.CanAddr() {
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
return v.UnmarshalTOML(data)
}
rvi := rv.Interface()
if v, ok := rvi.(Unmarshaler); ok {
return v.UnmarshalTOML(data)
}
// Special case. Handle time.Time values specifically.
// TODO: Remove this code when we decide to drop support for Go 1.1.
// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
// interfaces.
if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
return md.unifyDatetime(data, rv)
}
// Special case. Look for a value satisfying the TextUnmarshaler interface.
if v, ok := rv.Interface().(TextUnmarshaler); ok {
if v, ok := rvi.(encoding.TextUnmarshaler); ok {
return md.unifyText(data, v)
}
// BUG(burntsushi)
// TODO:
// The behavior here is incorrect whenever a Go type satisfies the
// encoding.TextUnmarshaler interface but also corresponds to a TOML
// hash or array. In particular, the unmarshaler should only be applied
// to primitive TOML values. But at this point, it will be applied to
// all kinds of values and produce an incorrect error whenever those values
// are hashes or arrays (including arrays of tables).
// encoding.TextUnmarshaler interface but also corresponds to a TOML hash or
// array. In particular, the unmarshaler should only be applied to primitive
// TOML values. But at this point, it will be applied to all kinds of values
// and produce an incorrect error whenever those values are hashes or arrays
// (including arrays of tables).
k := rv.Kind()
// laziness
if k >= reflect.Int && k <= reflect.Uint64 {
return md.unifyInt(data, rv)
}
@ -218,17 +248,14 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
case reflect.Bool:
return md.unifyBool(data, rv)
case reflect.Interface:
// we only support empty interfaces.
if rv.NumMethod() > 0 {
return e("unsupported type %s", rv.Type())
if rv.NumMethod() > 0 { /// Only empty interfaces are supported.
return md.e("unsupported type %s", rv.Type())
}
return md.unifyAnything(data, rv)
case reflect.Float32:
fallthrough
case reflect.Float64:
case reflect.Float32, reflect.Float64:
return md.unifyFloat64(data, rv)
}
return e("unsupported type %s", rv.Kind())
return md.e("unsupported type %s", rv.Kind())
}
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
@ -237,7 +264,7 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
if mapping == nil {
return nil
}
return e("type mismatch for %s: expected table but found %T",
return md.e("type mismatch for %s: expected table but found %T",
rv.Type().String(), mapping)
}
@ -259,17 +286,18 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
for _, i := range f.index {
subv = indirect(subv.Field(i))
}
if isUnifiable(subv) {
md.decoded[md.context.add(key).String()] = true
md.decoded[md.context.add(key).String()] = struct{}{}
md.context = append(md.context, key)
if err := md.unify(datum, subv); err != nil {
err := md.unify(datum, subv)
if err != nil {
return err
}
md.context = md.context[0 : len(md.context)-1]
} else if f.name != "" {
// Bad user! No soup for you!
return e("cannot write unexported field %s.%s",
rv.Type().String(), f.name)
return md.e("cannot write unexported field %s.%s", rv.Type().String(), f.name)
}
}
}
@ -277,28 +305,43 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
}
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
keyType := rv.Type().Key().Kind()
if keyType != reflect.String && keyType != reflect.Interface {
return fmt.Errorf("toml: cannot decode to a map with non-string key type (%s in %q)",
keyType, rv.Type())
}
tmap, ok := mapping.(map[string]interface{})
if !ok {
if tmap == nil {
return nil
}
return badtype("map", mapping)
return md.badtype("map", mapping)
}
if rv.IsNil() {
rv.Set(reflect.MakeMap(rv.Type()))
}
for k, v := range tmap {
md.decoded[md.context.add(k).String()] = true
md.decoded[md.context.add(k).String()] = struct{}{}
md.context = append(md.context, k)
rvkey := indirect(reflect.New(rv.Type().Key()))
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
if err := md.unify(v, rvval); err != nil {
err := md.unify(v, indirect(rvval))
if err != nil {
return err
}
md.context = md.context[0 : len(md.context)-1]
rvkey.SetString(k)
rvkey := indirect(reflect.New(rv.Type().Key()))
switch keyType {
case reflect.Interface:
rvkey.Set(reflect.ValueOf(k))
case reflect.String:
rvkey.SetString(k)
}
rv.SetMapIndex(rvkey, rvval)
}
return nil
@ -310,12 +353,10 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
if !datav.IsValid() {
return nil
}
return badtype("slice", data)
return md.badtype("slice", data)
}
sliceLen := datav.Len()
if sliceLen != rv.Len() {
return e("expected array length %d; got TOML array of length %d",
rv.Len(), sliceLen)
if l := datav.Len(); l != rv.Len() {
return md.e("expected array length %d; got TOML array of length %d", rv.Len(), l)
}
return md.unifySliceArray(datav, rv)
}
@ -326,7 +367,7 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
if !datav.IsValid() {
return nil
}
return badtype("slice", data)
return md.badtype("slice", data)
}
n := datav.Len()
if rv.IsNil() || rv.Cap() < n {
@ -337,37 +378,45 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
}
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
sliceLen := data.Len()
for i := 0; i < sliceLen; i++ {
v := data.Index(i).Interface()
sliceval := indirect(rv.Index(i))
if err := md.unify(v, sliceval); err != nil {
l := data.Len()
for i := 0; i < l; i++ {
err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i)))
if err != nil {
return err
}
}
return nil
}
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
if _, ok := data.(time.Time); ok {
rv.Set(reflect.ValueOf(data))
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
_, ok := rv.Interface().(json.Number)
if ok {
if i, ok := data.(int64); ok {
rv.SetString(strconv.FormatInt(i, 10))
} else if f, ok := data.(float64); ok {
rv.SetString(strconv.FormatFloat(f, 'f', -1, 64))
} else {
return md.badtype("string", data)
}
return nil
}
return badtype("time.Time", data)
}
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
if s, ok := data.(string); ok {
rv.SetString(s)
return nil
}
return badtype("string", data)
return md.badtype("string", data)
}
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
rvk := rv.Kind()
if num, ok := data.(float64); ok {
switch rv.Kind() {
switch rvk {
case reflect.Float32:
if num < -math.MaxFloat32 || num > math.MaxFloat32 {
return md.parseErr(errParseRange{i: num, size: rvk.String()})
}
fallthrough
case reflect.Float64:
rv.SetFloat(num)
@ -376,54 +425,60 @@ func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
}
return nil
}
return badtype("float", data)
if num, ok := data.(int64); ok {
if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) ||
(rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) {
return md.parseErr(errParseRange{i: num, size: rvk.String()})
}
rv.SetFloat(float64(num))
return nil
}
return md.badtype("float", data)
}
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
if num, ok := data.(int64); ok {
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
switch rv.Kind() {
case reflect.Int, reflect.Int64:
// No bounds checking necessary.
case reflect.Int8:
if num < math.MinInt8 || num > math.MaxInt8 {
return e("value %d is out of range for int8", num)
}
case reflect.Int16:
if num < math.MinInt16 || num > math.MaxInt16 {
return e("value %d is out of range for int16", num)
}
case reflect.Int32:
if num < math.MinInt32 || num > math.MaxInt32 {
return e("value %d is out of range for int32", num)
}
_, ok := rv.Interface().(time.Duration)
if ok {
// Parse as string duration, and fall back to regular integer parsing
// (as nanosecond) if this is not a string.
if s, ok := data.(string); ok {
dur, err := time.ParseDuration(s)
if err != nil {
return md.parseErr(errParseDuration{s})
}
rv.SetInt(num)
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
unum := uint64(num)
switch rv.Kind() {
case reflect.Uint, reflect.Uint64:
// No bounds checking necessary.
case reflect.Uint8:
if num < 0 || unum > math.MaxUint8 {
return e("value %d is out of range for uint8", num)
}
case reflect.Uint16:
if num < 0 || unum > math.MaxUint16 {
return e("value %d is out of range for uint16", num)
}
case reflect.Uint32:
if num < 0 || unum > math.MaxUint32 {
return e("value %d is out of range for uint32", num)
}
}
rv.SetUint(unum)
} else {
panic("unreachable")
rv.SetInt(int64(dur))
return nil
}
return nil
}
return badtype("integer", data)
num, ok := data.(int64)
if !ok {
return md.badtype("integer", data)
}
rvk := rv.Kind()
switch {
case rvk >= reflect.Int && rvk <= reflect.Int64:
if (rvk == reflect.Int8 && (num < math.MinInt8 || num > math.MaxInt8)) ||
(rvk == reflect.Int16 && (num < math.MinInt16 || num > math.MaxInt16)) ||
(rvk == reflect.Int32 && (num < math.MinInt32 || num > math.MaxInt32)) {
return md.parseErr(errParseRange{i: num, size: rvk.String()})
}
rv.SetInt(num)
case rvk >= reflect.Uint && rvk <= reflect.Uint64:
unum := uint64(num)
if rvk == reflect.Uint8 && (num < 0 || unum > math.MaxUint8) ||
rvk == reflect.Uint16 && (num < 0 || unum > math.MaxUint16) ||
rvk == reflect.Uint32 && (num < 0 || unum > math.MaxUint32) {
return md.parseErr(errParseRange{i: num, size: rvk.String()})
}
rv.SetUint(unum)
default:
panic("unreachable")
}
return nil
}
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
@ -431,7 +486,7 @@ func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
rv.SetBool(b)
return nil
}
return badtype("boolean", data)
return md.badtype("boolean", data)
}
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
@ -439,10 +494,16 @@ func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
return nil
}
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) error {
var s string
switch sdata := data.(type) {
case TextMarshaler:
case Marshaler:
text, err := sdata.MarshalTOML()
if err != nil {
return err
}
s = string(text)
case encoding.TextMarshaler:
text, err := sdata.MarshalText()
if err != nil {
return err
@ -459,7 +520,7 @@ func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
case float64:
s = fmt.Sprintf("%f", sdata)
default:
return badtype("primitive (string-like)", data)
return md.badtype("primitive (string-like)", data)
}
if err := v.UnmarshalText([]byte(s)); err != nil {
return err
@ -467,22 +528,54 @@ func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
return nil
}
func (md *MetaData) badtype(dst string, data interface{}) error {
return md.e("incompatible types: TOML value has type %T; destination has type %s", data, dst)
}
func (md *MetaData) parseErr(err error) error {
k := md.context.String()
return ParseError{
LastKey: k,
Position: md.keyInfo[k].pos,
Line: md.keyInfo[k].pos.Line,
err: err,
input: string(md.data),
}
}
func (md *MetaData) e(format string, args ...interface{}) error {
f := "toml: "
if len(md.context) > 0 {
f = fmt.Sprintf("toml: (last key %q): ", md.context)
p := md.keyInfo[md.context.String()].pos
if p.Line > 0 {
f = fmt.Sprintf("toml: line %d (last key %q): ", p.Line, md.context)
}
}
return fmt.Errorf(f+format, args...)
}
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
func rvalue(v interface{}) reflect.Value {
return indirect(reflect.ValueOf(v))
}
// indirect returns the value pointed to by a pointer.
// Pointers are followed until the value is not a pointer.
// New values are allocated for each nil pointer.
//
// An exception to this rule is if the value satisfies an interface of
// interest to us (like encoding.TextUnmarshaler).
// Pointers are followed until the value is not a pointer. New values are
// allocated for each nil pointer.
//
// An exception to this rule is if the value satisfies an interface of interest
// to us (like encoding.TextUnmarshaler).
func indirect(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Ptr {
if v.CanSet() {
pv := v.Addr()
if _, ok := pv.Interface().(TextUnmarshaler); ok {
pvi := pv.Interface()
if _, ok := pvi.(encoding.TextUnmarshaler); ok {
return pv
}
if _, ok := pvi.(Unmarshaler); ok {
return pv
}
}
@ -498,12 +591,12 @@ func isUnifiable(rv reflect.Value) bool {
if rv.CanSet() {
return true
}
if _, ok := rv.Interface().(TextUnmarshaler); ok {
rvi := rv.Interface()
if _, ok := rvi.(encoding.TextUnmarshaler); ok {
return true
}
if _, ok := rvi.(Unmarshaler); ok {
return true
}
return false
}
func badtype(expected string, data interface{}) error {
return e("cannot load TOML value of type %T into a Go %s", data, expected)
}

19
vendor/github.com/BurntSushi/toml/decode_go116.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
//go:build go1.16
// +build go1.16
package toml
import (
"io/fs"
)
// DecodeFS reads the contents of a file from [fs.FS] and decodes it with
// [Decode].
func DecodeFS(fsys fs.FS, path string, v interface{}) (MetaData, error) {
fp, err := fsys.Open(path)
if err != nil {
return MetaData{}, err
}
defer fp.Close()
return NewDecoder(fp).Decode(v)
}

29
vendor/github.com/BurntSushi/toml/deprecated.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
package toml
import (
"encoding"
"io"
)
// TextMarshaler is an alias for encoding.TextMarshaler.
//
// Deprecated: use encoding.TextMarshaler
type TextMarshaler encoding.TextMarshaler
// TextUnmarshaler is an alias for encoding.TextUnmarshaler.
//
// Deprecated: use encoding.TextUnmarshaler
type TextUnmarshaler encoding.TextUnmarshaler
// PrimitiveDecode is an alias for MetaData.PrimitiveDecode().
//
// Deprecated: use MetaData.PrimitiveDecode.
func PrimitiveDecode(primValue Primitive, v interface{}) error {
md := MetaData{decoded: make(map[string]struct{})}
return md.unify(primValue.undecoded, rvalue(v))
}
// DecodeReader is an alias for NewDecoder(r).Decode(v).
//
// Deprecated: use NewDecoder(reader).Decode(&value).
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { return NewDecoder(r).Decode(v) }

View File

@ -1,27 +1,11 @@
/*
Package toml provides facilities for decoding and encoding TOML configuration
files via reflection. There is also support for delaying decoding with
the Primitive type, and querying the set of keys in a TOML document with the
MetaData type.
The specification implemented: https://github.com/toml-lang/toml
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
whether a file is a valid TOML document. It can also be used to print the
type of each key in a TOML document.
Testing
There are two important types of tests used for this package. The first is
contained inside '*_test.go' files and uses the standard Go unit testing
framework. These tests are primarily devoted to holistically testing the
decoder and encoder.
The second type of testing is used to verify the implementation's adherence
to the TOML specification. These tests have been factored into their own
project: https://github.com/BurntSushi/toml-test
The reason the tests are in a separate project is so that they can be used by
any implementation of TOML. Namely, it is language agnostic.
*/
// Package toml implements decoding and encoding of TOML files.
//
// This package supports TOML v1.0.0, as specified at https://toml.io
//
// There is also support for delaying decoding with the Primitive type, and
// querying the set of keys in a TOML document with the MetaData type.
//
// The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator,
// and can be used to verify if TOML document is valid. It can also be used to
// print the type of each key.
package toml

View File

@ -2,57 +2,127 @@ package toml
import (
"bufio"
"encoding"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/BurntSushi/toml/internal"
)
type tomlEncodeError struct{ error }
var (
errArrayMixedElementTypes = errors.New(
"toml: cannot encode array with mixed element types")
errArrayNilElement = errors.New(
"toml: cannot encode array with nil element")
errNonString = errors.New(
"toml: cannot encode a map with non-string key type")
errAnonNonStruct = errors.New(
"toml: cannot encode an anonymous field that is not a struct")
errArrayNoTable = errors.New(
"toml: TOML array element cannot contain a table")
errNoKey = errors.New(
"toml: top-level values must be Go maps or structs")
errAnything = errors.New("") // used in testing
errArrayNilElement = errors.New("toml: cannot encode array with nil element")
errNonString = errors.New("toml: cannot encode a map with non-string key type")
errNoKey = errors.New("toml: top-level values must be Go maps or structs")
errAnything = errors.New("") // used in testing
)
var quotedReplacer = strings.NewReplacer(
"\t", "\\t",
"\n", "\\n",
"\r", "\\r",
var dblQuotedReplacer = strings.NewReplacer(
"\"", "\\\"",
"\\", "\\\\",
"\x00", `\u0000`,
"\x01", `\u0001`,
"\x02", `\u0002`,
"\x03", `\u0003`,
"\x04", `\u0004`,
"\x05", `\u0005`,
"\x06", `\u0006`,
"\x07", `\u0007`,
"\b", `\b`,
"\t", `\t`,
"\n", `\n`,
"\x0b", `\u000b`,
"\f", `\f`,
"\r", `\r`,
"\x0e", `\u000e`,
"\x0f", `\u000f`,
"\x10", `\u0010`,
"\x11", `\u0011`,
"\x12", `\u0012`,
"\x13", `\u0013`,
"\x14", `\u0014`,
"\x15", `\u0015`,
"\x16", `\u0016`,
"\x17", `\u0017`,
"\x18", `\u0018`,
"\x19", `\u0019`,
"\x1a", `\u001a`,
"\x1b", `\u001b`,
"\x1c", `\u001c`,
"\x1d", `\u001d`,
"\x1e", `\u001e`,
"\x1f", `\u001f`,
"\x7f", `\u007f`,
)
// Encoder controls the encoding of Go values to a TOML document to some
// io.Writer.
//
// The indentation level can be controlled with the Indent field.
type Encoder struct {
// A single indentation level. By default it is two spaces.
Indent string
var (
marshalToml = reflect.TypeOf((*Marshaler)(nil)).Elem()
marshalText = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
)
// hasWritten is whether we have written any output to w yet.
hasWritten bool
w *bufio.Writer
// Marshaler is the interface implemented by types that can marshal themselves
// into valid TOML.
type Marshaler interface {
MarshalTOML() ([]byte, error)
}
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
// given. By default, a single indentation level is 2 spaces.
// Encoder encodes a Go to a TOML document.
//
// The mapping between Go values and TOML values should be precisely the same as
// for [Decode].
//
// time.Time is encoded as a RFC 3339 string, and time.Duration as its string
// representation.
//
// The [Marshaler] and [encoding.TextMarshaler] interfaces are supported to
// encoding the value as custom TOML.
//
// If you want to write arbitrary binary data then you will need to use
// something like base64 since TOML does not have any binary types.
//
// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes
// are encoded first.
//
// Go maps will be sorted alphabetically by key for deterministic output.
//
// The toml struct tag can be used to provide the key name; if omitted the
// struct field name will be used. If the "omitempty" option is present the
// following value will be skipped:
//
// - arrays, slices, maps, and string with len of 0
// - struct with all zero values
// - bool false
//
// If omitzero is given all int and float types with a value of 0 will be
// skipped.
//
// Encoding Go values without a corresponding TOML representation will return an
// error. Examples of this includes maps with non-string keys, slices with nil
// elements, embedded non-struct types, and nested slices containing maps or
// structs. (e.g. [][]map[string]string is not allowed but []map[string]string
// is okay, as is []map[string][]string).
//
// NOTE: only exported keys are encoded due to the use of reflection. Unexported
// keys are silently discarded.
type Encoder struct {
// String to use for a single indentation level; default is two spaces.
Indent string
w *bufio.Writer
hasWritten bool // written any output to w yet?
}
// NewEncoder create a new Encoder.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{
w: bufio.NewWriter(w),
@ -60,32 +130,14 @@ func NewEncoder(w io.Writer) *Encoder {
}
}
// Encode writes a TOML representation of the Go value to the underlying
// io.Writer. If the value given cannot be encoded to a valid TOML document,
// then an error is returned.
// Encode writes a TOML representation of the Go value to the [Encoder]'s writer.
//
// The mapping between Go values and TOML values should be precisely the same
// as for the Decode* functions. Similarly, the TextMarshaler interface is
// supported by encoding the resulting bytes as strings. (If you want to write
// arbitrary binary data then you will need to use something like base64 since
// TOML does not have any binary types.)
//
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
// sub-hashes are encoded first.
//
// If a Go map is encoded, then its keys are sorted alphabetically for
// deterministic output. More control over this behavior may be provided if
// there is demand for it.
//
// Encoding Go values without a corresponding TOML representation---like map
// types with non-string keys---will cause an error to be returned. Similarly
// for mixed arrays/slices, arrays/slices with nil elements, embedded
// non-struct types and nested slices containing maps or structs.
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
// and so is []map[string][]string.)
// An error is returned if the value given cannot be encoded to a valid TOML
// document.
func (enc *Encoder) Encode(v interface{}) error {
rv := eindirect(reflect.ValueOf(v))
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
err := enc.safeEncode(Key([]string{}), rv)
if err != nil {
return err
}
return enc.w.Flush()
@ -106,13 +158,15 @@ func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
}
func (enc *Encoder) encode(key Key, rv reflect.Value) {
// Special case. Time needs to be in ISO8601 format.
// Special case. If we can marshal the type to text, then we used that.
// Basically, this prevents the encoder for handling these types as
// generic structs (or whatever the underlying type of a TextMarshaler is).
switch rv.Interface().(type) {
case time.Time, TextMarshaler:
enc.keyEqElement(key, rv)
// If we can marshal the type to text, then we use that. This prevents the
// encoder for handling these types as generic structs (or whatever the
// underlying type of a TextMarshaler is).
switch {
case isMarshaler(rv):
enc.writeKeyValue(key, rv, false)
return
case rv.Type() == primitiveType: // TODO: #76 would make this superfluous after implemented.
enc.encode(key, reflect.ValueOf(rv.Interface().(Primitive).undecoded))
return
}
@ -123,12 +177,12 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64,
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
enc.keyEqElement(key, rv)
enc.writeKeyValue(key, rv, false)
case reflect.Array, reflect.Slice:
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
enc.eArrayOfTables(key, rv)
} else {
enc.keyEqElement(key, rv)
enc.writeKeyValue(key, rv, false)
}
case reflect.Interface:
if rv.IsNil() {
@ -148,55 +202,114 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
case reflect.Struct:
enc.eTable(key, rv)
default:
panic(e("unsupported type for key '%s': %s", key, k))
encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k))
}
}
// eElement encodes any value that can be an array element (primitives and
// arrays).
// eElement encodes any value that can be an array element.
func (enc *Encoder) eElement(rv reflect.Value) {
switch v := rv.Interface().(type) {
case time.Time:
// Special case time.Time as a primitive. Has to come before
// TextMarshaler below because time.Time implements
// encoding.TextMarshaler, but we need to always use UTC.
enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
return
case TextMarshaler:
// Special case. Use text marshaler if it's available for this value.
if s, err := v.MarshalText(); err != nil {
encPanic(err)
} else {
enc.writeQuoted(string(s))
case time.Time: // Using TextMarshaler adds extra quotes, which we don't want.
format := time.RFC3339Nano
switch v.Location() {
case internal.LocalDatetime:
format = "2006-01-02T15:04:05.999999999"
case internal.LocalDate:
format = "2006-01-02"
case internal.LocalTime:
format = "15:04:05.999999999"
}
switch v.Location() {
default:
enc.wf(v.Format(format))
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
enc.wf(v.In(time.UTC).Format(format))
}
return
case Marshaler:
s, err := v.MarshalTOML()
if err != nil {
encPanic(err)
}
if s == nil {
encPanic(errors.New("MarshalTOML returned nil and no error"))
}
enc.w.Write(s)
return
case encoding.TextMarshaler:
s, err := v.MarshalText()
if err != nil {
encPanic(err)
}
if s == nil {
encPanic(errors.New("MarshalText returned nil and no error"))
}
enc.writeQuoted(string(s))
return
case time.Duration:
enc.writeQuoted(v.String())
return
case json.Number:
n, _ := rv.Interface().(json.Number)
if n == "" { /// Useful zero value.
enc.w.WriteByte('0')
return
} else if v, err := n.Int64(); err == nil {
enc.eElement(reflect.ValueOf(v))
return
} else if v, err := n.Float64(); err == nil {
enc.eElement(reflect.ValueOf(v))
return
}
encPanic(fmt.Errorf("unable to convert %q to int64 or float64", n))
}
switch rv.Kind() {
case reflect.Bool:
enc.wf(strconv.FormatBool(rv.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
enc.wf(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64:
enc.wf(strconv.FormatUint(rv.Uint(), 10))
case reflect.Float32:
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
case reflect.Float64:
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
case reflect.Array, reflect.Slice:
enc.eArrayOrSliceElement(rv)
case reflect.Interface:
case reflect.Ptr:
enc.eElement(rv.Elem())
return
case reflect.String:
enc.writeQuoted(rv.String())
case reflect.Bool:
enc.wf(strconv.FormatBool(rv.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
enc.wf(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
enc.wf(strconv.FormatUint(rv.Uint(), 10))
case reflect.Float32:
f := rv.Float()
if math.IsNaN(f) {
enc.wf("nan")
} else if math.IsInf(f, 0) {
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
} else {
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
}
case reflect.Float64:
f := rv.Float()
if math.IsNaN(f) {
enc.wf("nan")
} else if math.IsInf(f, 0) {
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
} else {
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64)))
}
case reflect.Array, reflect.Slice:
enc.eArrayOrSliceElement(rv)
case reflect.Struct:
enc.eStruct(nil, rv, true)
case reflect.Map:
enc.eMap(nil, rv, true)
case reflect.Interface:
enc.eElement(rv.Elem())
default:
panic(e("unexpected primitive type: %s", rv.Kind()))
encPanic(fmt.Errorf("unexpected type: %T", rv.Interface()))
}
}
// By the TOML spec, all floats must have a decimal with at least one
// number on either side.
// By the TOML spec, all floats must have a decimal with at least one number on
// either side.
func floatAddDecimal(fstr string) string {
if !strings.Contains(fstr, ".") {
return fstr + ".0"
@ -205,14 +318,14 @@ func floatAddDecimal(fstr string) string {
}
func (enc *Encoder) writeQuoted(s string) {
enc.wf("\"%s\"", quotedReplacer.Replace(s))
enc.wf("\"%s\"", dblQuotedReplacer.Replace(s))
}
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
length := rv.Len()
enc.wf("[")
for i := 0; i < length; i++ {
elem := rv.Index(i)
elem := eindirect(rv.Index(i))
enc.eElement(elem)
if i != length-1 {
enc.wf(", ")
@ -226,44 +339,43 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
encPanic(errNoKey)
}
for i := 0; i < rv.Len(); i++ {
trv := rv.Index(i)
trv := eindirect(rv.Index(i))
if isNil(trv) {
continue
}
panicIfInvalidKey(key)
enc.newline()
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
enc.wf("%s[[%s]]", enc.indentStr(key), key)
enc.newline()
enc.eMapOrStruct(key, trv)
enc.eMapOrStruct(key, trv, false)
}
}
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
panicIfInvalidKey(key)
if len(key) == 1 {
// Output an extra newline between top-level tables.
// (The newline isn't written if nothing else has been written though.)
enc.newline()
}
if len(key) > 0 {
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
enc.wf("%s[%s]", enc.indentStr(key), key)
enc.newline()
}
enc.eMapOrStruct(key, rv)
enc.eMapOrStruct(key, rv, false)
}
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
switch rv := eindirect(rv); rv.Kind() {
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) {
switch rv.Kind() {
case reflect.Map:
enc.eMap(key, rv)
enc.eMap(key, rv, inline)
case reflect.Struct:
enc.eStruct(key, rv)
enc.eStruct(key, rv, inline)
default:
// Should never happen?
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
}
}
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
rt := rv.Type()
if rt.Key().Kind() != reflect.String {
encPanic(errNonString)
@ -274,68 +386,100 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value) {
var mapKeysDirect, mapKeysSub []string
for _, mapKey := range rv.MapKeys() {
k := mapKey.String()
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
if typeIsTable(tomlTypeOfGo(eindirect(rv.MapIndex(mapKey)))) {
mapKeysSub = append(mapKeysSub, k)
} else {
mapKeysDirect = append(mapKeysDirect, k)
}
}
var writeMapKeys = func(mapKeys []string) {
var writeMapKeys = func(mapKeys []string, trailC bool) {
sort.Strings(mapKeys)
for _, mapKey := range mapKeys {
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
if isNil(mrv) {
// Don't write anything for nil fields.
for i, mapKey := range mapKeys {
val := eindirect(rv.MapIndex(reflect.ValueOf(mapKey)))
if isNil(val) {
continue
}
enc.encode(key.add(mapKey), mrv)
if inline {
enc.writeKeyValue(Key{mapKey}, val, true)
if trailC || i != len(mapKeys)-1 {
enc.wf(", ")
}
} else {
enc.encode(key.add(mapKey), val)
}
}
}
writeMapKeys(mapKeysDirect)
writeMapKeys(mapKeysSub)
if inline {
enc.wf("{")
}
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
writeMapKeys(mapKeysSub, false)
if inline {
enc.wf("}")
}
}
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
const is32Bit = (32 << (^uint(0) >> 63)) == 32
func pointerTo(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Ptr {
return pointerTo(t.Elem())
}
return t
}
func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
// Write keys for fields directly under this key first, because if we write
// a field that creates a new table, then all keys under it will be in that
// a field that creates a new table then all keys under it will be in that
// table (not the one we're writing here).
rt := rv.Type()
var fieldsDirect, fieldsSub [][]int
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
//
// Fields is a [][]int: for fieldsDirect this always has one entry (the
// struct index). For fieldsSub it contains two entries: the parent field
// index from tv, and the field indexes for the fields of the sub.
var (
rt = rv.Type()
fieldsDirect, fieldsSub [][]int
addFields func(rt reflect.Type, rv reflect.Value, start []int)
)
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
// skip unexported fields
if f.PkgPath != "" && !f.Anonymous {
isEmbed := f.Anonymous && pointerTo(f.Type).Kind() == reflect.Struct
if f.PkgPath != "" && !isEmbed { /// Skip unexported fields.
continue
}
frv := rv.Field(i)
if f.Anonymous {
t := f.Type
switch t.Kind() {
case reflect.Struct:
// Treat anonymous struct fields with
// tag names as though they are not
// anonymous, like encoding/json does.
if getOptions(f.Tag).name == "" {
addFields(t, frv, f.Index)
continue
}
case reflect.Ptr:
if t.Elem().Kind() == reflect.Struct &&
getOptions(f.Tag).name == "" {
if !frv.IsNil() {
addFields(t.Elem(), frv.Elem(), f.Index)
}
continue
}
// Fall through to the normal field encoding logic below
// for non-struct anonymous fields.
opts := getOptions(f.Tag)
if opts.skip {
continue
}
frv := eindirect(rv.Field(i))
if is32Bit {
// Copy so it works correct on 32bit archs; not clear why this
// is needed. See #314, and https://www.reddit.com/r/golang/comments/pnx8v4
// This also works fine on 64bit, but 32bit archs are somewhat
// rare and this is a wee bit faster.
copyStart := make([]int, len(start))
copy(copyStart, start)
start = copyStart
}
// Treat anonymous struct fields with tag names as though they are
// not anonymous, like encoding/json does.
//
// Non-struct anonymous fields use the normal encoding logic.
if isEmbed {
if getOptions(f.Tag).name == "" && frv.Kind() == reflect.Struct {
addFields(frv.Type(), frv, append(start, f.Index...))
continue
}
}
if typeIsHash(tomlTypeOfGo(frv)) {
if typeIsTable(tomlTypeOfGo(frv)) {
fieldsSub = append(fieldsSub, append(start, f.Index...))
} else {
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
@ -344,48 +488,81 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
}
addFields(rt, rv, nil)
var writeFields = func(fields [][]int) {
writeFields := func(fields [][]int) {
for _, fieldIndex := range fields {
sft := rt.FieldByIndex(fieldIndex)
sf := rv.FieldByIndex(fieldIndex)
if isNil(sf) {
// Don't write anything for nil fields.
continue
}
fieldType := rt.FieldByIndex(fieldIndex)
fieldVal := rv.FieldByIndex(fieldIndex)
opts := getOptions(sft.Tag)
opts := getOptions(fieldType.Tag)
if opts.skip {
continue
}
keyName := sft.Name
if opts.omitempty && isEmpty(fieldVal) {
continue
}
fieldVal = eindirect(fieldVal)
if isNil(fieldVal) { /// Don't write anything for nil fields.
continue
}
keyName := fieldType.Name
if opts.name != "" {
keyName = opts.name
}
if opts.omitempty && isEmpty(sf) {
continue
}
if opts.omitzero && isZero(sf) {
if opts.omitzero && isZero(fieldVal) {
continue
}
enc.encode(key.add(keyName), sf)
if inline {
enc.writeKeyValue(Key{keyName}, fieldVal, true)
if fieldIndex[0] != len(fields)-1 {
enc.wf(", ")
}
} else {
enc.encode(key.add(keyName), fieldVal)
}
}
}
if inline {
enc.wf("{")
}
writeFields(fieldsDirect)
writeFields(fieldsSub)
if inline {
enc.wf("}")
}
}
// tomlTypeName returns the TOML type name of the Go value's type. It is
// used to determine whether the types of array elements are mixed (which is
// forbidden). If the Go value is nil, then it is illegal for it to be an array
// element, and valueIsNil is returned as true.
// Returns the TOML type of a Go value. The type may be `nil`, which means
// no concrete TOML type could be found.
// tomlTypeOfGo returns the TOML type name of the Go value's type.
//
// It is used to determine whether the types of array elements are mixed (which
// is forbidden). If the Go value is nil, then it is illegal for it to be an
// array element, and valueIsNil is returned as true.
//
// The type may be `nil`, which means no concrete TOML type could be found.
func tomlTypeOfGo(rv reflect.Value) tomlType {
if isNil(rv) || !rv.IsValid() {
return nil
}
if rv.Kind() == reflect.Struct {
if rv.Type() == timeType {
return tomlDatetime
}
if isMarshaler(rv) {
return tomlString
}
return tomlHash
}
if isMarshaler(rv) {
return tomlString
}
switch rv.Kind() {
case reflect.Bool:
return tomlBool
@ -397,7 +574,7 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
case reflect.Float32, reflect.Float64:
return tomlFloat
case reflect.Array, reflect.Slice:
if typeEqual(tomlHash, tomlArrayType(rv)) {
if isTableArray(rv) {
return tomlArrayHash
}
return tomlArray
@ -407,54 +584,35 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
return tomlString
case reflect.Map:
return tomlHash
case reflect.Struct:
switch rv.Interface().(type) {
case time.Time:
return tomlDatetime
case TextMarshaler:
return tomlString
default:
return tomlHash
}
default:
panic("unexpected reflect.Kind: " + rv.Kind().String())
encPanic(errors.New("unsupported type: " + rv.Kind().String()))
panic("unreachable")
}
}
// tomlArrayType returns the element type of a TOML array. The type returned
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
// slize). This function may also panic if it finds a type that cannot be
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
// nested arrays of tables).
func tomlArrayType(rv reflect.Value) tomlType {
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
return nil
}
firstType := tomlTypeOfGo(rv.Index(0))
if firstType == nil {
encPanic(errArrayNilElement)
func isMarshaler(rv reflect.Value) bool {
return rv.Type().Implements(marshalText) || rv.Type().Implements(marshalToml)
}
// isTableArray reports if all entries in the array or slice are a table.
func isTableArray(arr reflect.Value) bool {
if isNil(arr) || !arr.IsValid() || arr.Len() == 0 {
return false
}
rvlen := rv.Len()
for i := 1; i < rvlen; i++ {
elem := rv.Index(i)
switch elemType := tomlTypeOfGo(elem); {
case elemType == nil:
ret := true
for i := 0; i < arr.Len(); i++ {
tt := tomlTypeOfGo(eindirect(arr.Index(i)))
// Don't allow nil.
if tt == nil {
encPanic(errArrayNilElement)
case !typeEqual(firstType, elemType):
encPanic(errArrayMixedElementTypes)
}
if ret && !typeEqual(tomlHash, tt) {
ret = false
}
}
// If we have a nested array, then we must make sure that the nested
// array contains ONLY primitives.
// This checks arbitrarily nested arrays.
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
nest := tomlArrayType(eindirect(rv.Index(0)))
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
encPanic(errArrayNoTable)
}
}
return firstType
return ret
}
type tagOptions struct {
@ -499,8 +657,26 @@ func isEmpty(rv reflect.Value) bool {
switch rv.Kind() {
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
return rv.Len() == 0
case reflect.Struct:
if rv.Type().Comparable() {
return reflect.Zero(rv.Type()).Interface() == rv.Interface()
}
// Need to also check if all the fields are empty, otherwise something
// like this with uncomparable types will always return true:
//
// type a struct{ field b }
// type b struct{ s []string }
// s := a{field: b{s: []string{"AAA"}}}
for i := 0; i < rv.NumField(); i++ {
if !isEmpty(rv.Field(i)) {
return false
}
}
return true
case reflect.Bool:
return !rv.Bool()
case reflect.Ptr:
return rv.IsNil()
}
return false
}
@ -511,18 +687,34 @@ func (enc *Encoder) newline() {
}
}
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
// Write a key/value pair:
//
// key = <any value>
//
// This is also used for "k = v" in inline tables; so something like this will
// be written in three calls:
//
// ┌───────────────────┐
// │ ┌───┐ ┌────┐│
// v v v v vv
// key = {k = 1, k2 = 2}
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
/// Marshaler used on top-level document; call eElement() to just call
/// Marshal{TOML,Text}.
if len(key) == 0 {
encPanic(errNoKey)
enc.eElement(val)
return
}
panicIfInvalidKey(key)
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
enc.eElement(val)
enc.newline()
if !inline {
enc.newline()
}
}
func (enc *Encoder) wf(format string, v ...interface{}) {
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
_, err := fmt.Fprintf(enc.w, format, v...)
if err != nil {
encPanic(err)
}
enc.hasWritten = true
@ -536,13 +728,25 @@ func encPanic(err error) {
panic(tomlEncodeError{err})
}
// Resolve any level of pointers to the actual value (e.g. **string → string).
func eindirect(v reflect.Value) reflect.Value {
switch v.Kind() {
case reflect.Ptr, reflect.Interface:
return eindirect(v.Elem())
default:
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
if isMarshaler(v) {
return v
}
if v.CanAddr() { /// Special case for marshalers; see #358.
if pv := v.Addr(); isMarshaler(pv) {
return pv
}
}
return v
}
if v.IsNil() {
return v
}
return eindirect(v.Elem())
}
func isNil(rv reflect.Value) bool {
@ -553,16 +757,3 @@ func isNil(rv reflect.Value) bool {
return false
}
}
func panicIfInvalidKey(key Key) {
for _, k := range key {
if len(k) == 0 {
encPanic(e("Key '%s' is not a valid table name. Key names "+
"cannot be empty.", key.maybeQuotedAll()))
}
}
}
func isValidKeyName(s string) bool {
return len(s) != 0
}

View File

@ -1,19 +0,0 @@
// +build go1.2
package toml
// In order to support Go 1.1, we define our own TextMarshaler and
// TextUnmarshaler types. For Go 1.2+, we just alias them with the
// standard library interfaces.
import (
"encoding"
)
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
// so that Go 1.1 can be supported.
type TextMarshaler encoding.TextMarshaler
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
// here so that Go 1.1 can be supported.
type TextUnmarshaler encoding.TextUnmarshaler

View File

@ -1,18 +0,0 @@
// +build !go1.2
package toml
// These interfaces were introduced in Go 1.2, so we add them manually when
// compiling for Go 1.1.
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
// so that Go 1.1 can be supported.
type TextMarshaler interface {
MarshalText() (text []byte, err error)
}
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
// here so that Go 1.1 can be supported.
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}

279
vendor/github.com/BurntSushi/toml/error.go generated vendored Normal file
View File

@ -0,0 +1,279 @@
package toml
import (
"fmt"
"strings"
)
// ParseError is returned when there is an error parsing the TOML syntax such as
// invalid syntax, duplicate keys, etc.
//
// In addition to the error message itself, you can also print detailed location
// information with context by using [ErrorWithPosition]:
//
// toml: error: Key 'fruit' was already created and cannot be used as an array.
//
// At line 4, column 2-7:
//
// 2 | fruit = []
// 3 |
// 4 | [[fruit]] # Not allowed
// ^^^^^
//
// [ErrorWithUsage] can be used to print the above with some more detailed usage
// guidance:
//
// toml: error: newlines not allowed within inline tables
//
// At line 1, column 18:
//
// 1 | x = [{ key = 42 #
// ^
//
// Error help:
//
// Inline tables must always be on a single line:
//
// table = {key = 42, second = 43}
//
// It is invalid to split them over multiple lines like so:
//
// # INVALID
// table = {
// key = 42,
// second = 43
// }
//
// Use regular for this:
//
// [table]
// key = 42
// second = 43
type ParseError struct {
Message string // Short technical message.
Usage string // Longer message with usage guidance; may be blank.
Position Position // Position of the error
LastKey string // Last parsed key, may be blank.
// Line the error occurred.
//
// Deprecated: use [Position].
Line int
err error
input string
}
// Position of an error.
type Position struct {
Line int // Line number, starting at 1.
Start int // Start of error, as byte offset starting at 0.
Len int // Lenght in bytes.
}
func (pe ParseError) Error() string {
msg := pe.Message
if msg == "" { // Error from errorf()
msg = pe.err.Error()
}
if pe.LastKey == "" {
return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, msg)
}
return fmt.Sprintf("toml: line %d (last key %q): %s",
pe.Position.Line, pe.LastKey, msg)
}
// ErrorWithPosition returns the error with detailed location context.
//
// See the documentation on [ParseError].
func (pe ParseError) ErrorWithPosition() string {
if pe.input == "" { // Should never happen, but just in case.
return pe.Error()
}
var (
lines = strings.Split(pe.input, "\n")
col = pe.column(lines)
b = new(strings.Builder)
)
msg := pe.Message
if msg == "" {
msg = pe.err.Error()
}
// TODO: don't show control characters as literals? This may not show up
// well everywhere.
if pe.Position.Len == 1 {
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
msg, pe.Position.Line, col+1)
} else {
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
msg, pe.Position.Line, col, col+pe.Position.Len)
}
if pe.Position.Line > 2 {
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, lines[pe.Position.Line-3])
}
if pe.Position.Line > 1 {
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, lines[pe.Position.Line-2])
}
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, lines[pe.Position.Line-1])
fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col), strings.Repeat("^", pe.Position.Len))
return b.String()
}
// ErrorWithUsage returns the error with detailed location context and usage
// guidance.
//
// See the documentation on [ParseError].
func (pe ParseError) ErrorWithUsage() string {
m := pe.ErrorWithPosition()
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
for i := range lines {
if lines[i] != "" {
lines[i] = " " + lines[i]
}
}
return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
}
return m
}
func (pe ParseError) column(lines []string) int {
var pos, col int
for i := range lines {
ll := len(lines[i]) + 1 // +1 for the removed newline
if pos+ll >= pe.Position.Start {
col = pe.Position.Start - pos
if col < 0 { // Should never happen, but just in case.
col = 0
}
break
}
pos += ll
}
return col
}
type (
errLexControl struct{ r rune }
errLexEscape struct{ r rune }
errLexUTF8 struct{ b byte }
errLexInvalidNum struct{ v string }
errLexInvalidDate struct{ v string }
errLexInlineTableNL struct{}
errLexStringNL struct{}
errParseRange struct {
i interface{} // int or float
size string // "int64", "uint16", etc.
}
errParseDuration struct{ d string }
)
func (e errLexControl) Error() string {
return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
}
func (e errLexControl) Usage() string { return "" }
func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
func (e errLexEscape) Usage() string { return usageEscape }
func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
func (e errLexUTF8) Usage() string { return "" }
func (e errLexInvalidNum) Error() string { return fmt.Sprintf("invalid number: %q", e.v) }
func (e errLexInvalidNum) Usage() string { return "" }
func (e errLexInvalidDate) Error() string { return fmt.Sprintf("invalid date: %q", e.v) }
func (e errLexInvalidDate) Usage() string { return "" }
func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
func (e errLexStringNL) Error() string { return "strings cannot contain newlines" }
func (e errLexStringNL) Usage() string { return usageStringNewline }
func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
func (e errParseRange) Usage() string { return usageIntOverflow }
func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
func (e errParseDuration) Usage() string { return usageDuration }
const usageEscape = `
A '\' inside a "-delimited string is interpreted as an escape character.
The following escape sequences are supported:
\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
To prevent a '\' from being recognized as an escape character, use either:
- a ' or '''-delimited string; escape characters aren't processed in them; or
- write two backslashes to get a single backslash: '\\'.
If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
instead of '\' will usually also work: "C:/Users/martin".
`
const usageInlineNewline = `
Inline tables must always be on a single line:
table = {key = 42, second = 43}
It is invalid to split them over multiple lines like so:
# INVALID
table = {
key = 42,
second = 43
}
Use regular for this:
[table]
key = 42
second = 43
`
const usageStringNewline = `
Strings must always be on a single line, and cannot span more than one line:
# INVALID
string = "Hello,
world!"
Instead use """ or ''' to split strings over multiple lines:
string = """Hello,
world!"""
`
const usageIntOverflow = `
This number is too large; this may be an error in the TOML, but it can also be a
bug in the program that uses too small of an integer.
The maximum and minimum values are:
size lowest highest
int8 -128 127
int16 -32,768 32,767
int32 -2,147,483,648 2,147,483,647
int64 -9.2 × 10¹ 9.2 × 10¹
uint8 0 255
uint16 0 65535
uint32 0 4294967295
uint64 0 1.8 × 10¹
int refers to int32 on 32-bit systems and int64 on 64-bit systems.
`
const usageDuration = `
A duration must be as "number<unit>", without any spaces. Valid units are:
ns nanoseconds (billionth of a second)
us, µs microseconds (millionth of a second)
ms milliseconds (thousands of a second)
s seconds
m minutes
h hours
You can combine multiple units; for example "5m10s" for 5 minutes and 10
seconds.
`

36
vendor/github.com/BurntSushi/toml/internal/tz.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package internal
import "time"
// Timezones used for local datetime, date, and time TOML types.
//
// The exact way times and dates without a timezone should be interpreted is not
// well-defined in the TOML specification and left to the implementation. These
// defaults to current local timezone offset of the computer, but this can be
// changed by changing these variables before decoding.
//
// TODO:
// Ideally we'd like to offer people the ability to configure the used timezone
// by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit
// tricky: the reason we use three different variables for this is to support
// round-tripping without these specific TZ names we wouldn't know which
// format to use.
//
// There isn't a good way to encode this right now though, and passing this sort
// of information also ties in to various related issues such as string format
// encoding, encoding of comments, etc.
//
// So, for the time being, just put this in internal until we can write a good
// comprehensive API for doing all of this.
//
// The reason they're exported is because they're referred from in e.g.
// internal/tag.
//
// Note that this behaviour is valid according to the TOML spec as the exact
// behaviour is left up to implementations.
var (
localOffset = func() int { _, o := time.Now().Zone(); return o }()
LocalDatetime = time.FixedZone("datetime-local", localOffset)
LocalDate = time.FixedZone("date-local", localOffset)
LocalTime = time.FixedZone("time-local", localOffset)
)

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,40 @@
package toml
import "strings"
import (
"strings"
)
// MetaData allows access to meta information about TOML data that may not
// be inferrable via reflection. In particular, whether a key has been defined
// and the TOML type of a key.
// MetaData allows access to meta information about TOML data that's not
// accessible otherwise.
//
// It allows checking if a key is defined in the TOML data, whether any keys
// were undecoded, and the TOML type of a key.
type MetaData struct {
mapping map[string]interface{}
types map[string]tomlType
keys []Key
decoded map[string]bool
context Key // Used only during decoding.
keyInfo map[string]keyInfo
mapping map[string]interface{}
keys []Key
decoded map[string]struct{}
data []byte // Input file; for errors.
}
// IsDefined returns true if the key given exists in the TOML data. The key
// should be specified hierarchially. e.g.,
// IsDefined reports if the key exists in the TOML data.
//
// // access the TOML key 'a.b.c'
// IsDefined("a", "b", "c")
// The key should be specified hierarchically, for example to access the TOML
// key "a.b.c" you would use IsDefined("a", "b", "c"). Keys are case sensitive.
//
// IsDefined will return false if an empty key given. Keys are case sensitive.
// Returns false for an empty key.
func (md *MetaData) IsDefined(key ...string) bool {
if len(key) == 0 {
return false
}
var hash map[string]interface{}
var ok bool
var hashOrVal interface{} = md.mapping
var (
hash map[string]interface{}
ok bool
hashOrVal interface{} = md.mapping
)
for _, k := range key {
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
return false
@ -41,58 +48,20 @@ func (md *MetaData) IsDefined(key ...string) bool {
// Type returns a string representation of the type of the key specified.
//
// Type will return the empty string if given an empty key or a key that
// does not exist. Keys are case sensitive.
// Type will return the empty string if given an empty key or a key that does
// not exist. Keys are case sensitive.
func (md *MetaData) Type(key ...string) string {
fullkey := strings.Join(key, ".")
if typ, ok := md.types[fullkey]; ok {
return typ.typeString()
if ki, ok := md.keyInfo[Key(key).String()]; ok {
return ki.tomlType.typeString()
}
return ""
}
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
// to get values of this type.
type Key []string
func (k Key) String() string {
return strings.Join(k, ".")
}
func (k Key) maybeQuotedAll() string {
var ss []string
for i := range k {
ss = append(ss, k.maybeQuoted(i))
}
return strings.Join(ss, ".")
}
func (k Key) maybeQuoted(i int) string {
quote := false
for _, c := range k[i] {
if !isBareKeyChar(c) {
quote = true
break
}
}
if quote {
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
}
return k[i]
}
func (k Key) add(piece string) Key {
newKey := make(Key, len(k)+1)
copy(newKey, k)
newKey[len(k)] = piece
return newKey
}
// Keys returns a slice of every key in the TOML data, including key groups.
// Each key is itself a slice, where the first element is the top of the
// hierarchy and the last is the most specific.
//
// The list will have the same order as the keys appeared in the TOML data.
// Each key is itself a slice, where the first element is the top of the
// hierarchy and the last is the most specific. The list will have the same
// order as the keys appeared in the TOML data.
//
// All keys returned are non-empty.
func (md *MetaData) Keys() []Key {
@ -102,7 +71,7 @@ func (md *MetaData) Keys() []Key {
// Undecoded returns all keys that have not been decoded in the order in which
// they appear in the original TOML document.
//
// This includes keys that haven't been decoded because of a Primitive value.
// This includes keys that haven't been decoded because of a [Primitive] value.
// Once the Primitive value is decoded, the keys will be considered decoded.
//
// Also note that decoding into an empty interface will result in no decoding,
@ -113,9 +82,40 @@ func (md *MetaData) Keys() []Key {
func (md *MetaData) Undecoded() []Key {
undecoded := make([]Key, 0, len(md.keys))
for _, key := range md.keys {
if !md.decoded[key.String()] {
if _, ok := md.decoded[key.String()]; !ok {
undecoded = append(undecoded, key)
}
}
return undecoded
}
// Key represents any TOML key, including key groups. Use [MetaData.Keys] to get
// values of this type.
type Key []string
func (k Key) String() string {
ss := make([]string, len(k))
for i := range k {
ss[i] = k.maybeQuoted(i)
}
return strings.Join(ss, ".")
}
func (k Key) maybeQuoted(i int) string {
if k[i] == "" {
return `""`
}
for _, c := range k[i] {
if !isBareKeyChar(c, false) {
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
}
}
return k[i]
}
func (k Key) add(piece string) Key {
newKey := make(Key, len(k)+1)
copy(newKey, k)
newKey[len(k)] = piece
return newKey
}

Some files were not shown because too many files have changed in this diff Show More